From f8c8e8604adaa5fec4a399afd354af0104d48295 Mon Sep 17 00:00:00 2001 From: mrfishie Date: Fri, 10 Oct 2014 16:47:53 +1000 Subject: [PATCH 001/235] custom view extensions Adds the 'sails.config.views.extension' config property that allows for a custom view file extension easily --- lib/hooks/views/configure.js | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/lib/hooks/views/configure.js b/lib/hooks/views/configure.js index d9dfef172e..37e67793d1 100644 --- a/lib/hooks/views/configure.js +++ b/lib/hooks/views/configure.js @@ -23,14 +23,17 @@ module.exports = function configure ( sails ) { sails.config.views.engine = sails.config.viewEngine; } - // Normalize view engine config + // Normalize view engine config and allow defining a custom extension if (typeof sails.config.views.engine === 'string') { - var viewExt = sails.config.views.engine; + var viewExt = sails.config.views.extension || sails.config.views.engine; sails.config.views.engine = { + name: sails.config.views.engine, ext: viewExt }; } + var engineName = sails.config.views.engine.name || sails.config.views.engine.ext; + // Ensure valid config if (! (sails.config.views.engine && sails.config.views.engine.ext) ) { sails.log.error('Invalid view engine configuration. `config.views.engine` should'); @@ -56,24 +59,24 @@ module.exports = function configure ( sails ) { ); try { - fn = consolidate(appDependenciesPath)[sails.config.views.engine.ext]; + fn = consolidate(appDependenciesPath)[engineName]; if ( !_.isFunction(fn) ) { - sails.log.error(util.format('Invalid view engine (%s)-- are you sure it supports `consolidate`?', sails.config.views.engine.ext)); + sails.log.error(util.format('Invalid view engine (%s)-- are you sure it supports `consolidate`?', engineName)); throw new Error(); } } catch (e) { - sails.log.error('Your configured server-side view engine (' + sails.config.views.engine.ext + ') could not be found.'); + sails.log.error('Your configured server-side view engine (' + engineName + ') could not be found.'); sails.log.error('Usually, this just means you need to install a dependency.'); - sails.log.error('To install ' + sails.config.views.engine.ext + ', run: `npm install ' + sails.config.views.engine.ext + ' --save`'); + sails.log.error('To install ' + engineName + ', run: `npm install ' + engineName + ' --save`'); sails.log.error('Otherwise, please change your `engine` configuration in config/views.js.'); throw e; } // Save reference to view rendering function sails.config.views.engine.fn = fn; - sails.log.silly('Configured view engine, `' + sails.config.views.engine.ext + '`'); + sails.log.silly('Configured view engine, `' + engineName + '`'); } @@ -91,10 +94,9 @@ module.exports = function configure ( sails ) { sails.config.views.layout = 'layout.' + sails.config.views.engine.ext; } - if ( sails.config.views.engine.ext !== 'ejs' && - sails.config.views.layout ) { + if ( engineName !== 'ejs' && sails.config.views.layout ) { sails.log.warn('Sails\' built-in layout support only works with the `ejs` view engine.'); - sails.log.warn('You\'re using `'+ sails.config.views.engine.ext +'`.'); + sails.log.warn('You\'re using `'+ engineName +'`.'); sails.log.warn('Ignoring `sails.config.views.layout`...'); } }; From fae37f530d3014e77a807db6b247bc35ad986efd Mon Sep 17 00:00:00 2001 From: Konstantin Zolotarev Date: Tue, 14 Oct 2014 12:09:13 +0600 Subject: [PATCH 002/235] fix for problem with https in #2300 --- lib/app/getBaseurl.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/app/getBaseurl.js b/lib/app/getBaseurl.js index fe374db916..ab229ee331 100644 --- a/lib/app/getBaseurl.js +++ b/lib/app/getBaseurl.js @@ -8,7 +8,7 @@ module.exports = function getBaseurl() { var usingSSL = sails.config.ssl && sails.config.ssl.key && sails.config.ssl.cert; var port = sails.config.proxyPort || sails.config.port; var localAppURL = - (usingSSL ? 'https' : 'http') + '://' + + (usingSSL || port == 443 ? 'https' : 'http') + '://' + (sails.getHost() || 'localhost') + (port == 80 || port == 443 ? '' : ':' + port); From 492334274c5dc7cc6441bee10aaf862d69fe6d5d Mon Sep 17 00:00:00 2001 From: Konstantin Zolotarev Date: Tue, 14 Oct 2014 12:26:23 +0600 Subject: [PATCH 003/235] Added an ability to use 'http://somehost.com' in sails.config.proxyHost config --- lib/app/getBaseurl.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/app/getBaseurl.js b/lib/app/getBaseurl.js index ab229ee331..d32843dccc 100644 --- a/lib/app/getBaseurl.js +++ b/lib/app/getBaseurl.js @@ -6,10 +6,13 @@ module.exports = function getBaseurl() { var sails = this; var usingSSL = sails.config.ssl && sails.config.ssl.key && sails.config.ssl.cert; + var host = sails.getHost() || 'localhost'; var port = sails.config.proxyPort || sails.config.port; + if (!~host.indexOf('http')) { + host = (usingSSL || port == 443 ? 'https' : 'http') + '://' + host; + } var localAppURL = - (usingSSL || port == 443 ? 'https' : 'http') + '://' + - (sails.getHost() || 'localhost') + + host + (port == 80 || port == 443 ? '' : ':' + port); return localAppURL; From a2fff490acf830ced1293c8929962108a83728c3 Mon Sep 17 00:00:00 2001 From: Ryan Clardy Date: Tue, 21 Oct 2014 17:17:35 -0500 Subject: [PATCH 004/235] Resolve methodOverride deprecation warnings --- lib/hooks/http/index.js | 3 ++- package.json | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/hooks/http/index.js b/lib/hooks/http/index.js index eb3d861919..0345edb18f 100644 --- a/lib/hooks/http/index.js +++ b/lib/hooks/http/index.js @@ -5,6 +5,7 @@ var path = require('path'); var _ = require('lodash'); var express = require('express'); +var methodOverride = require('method-override'); module.exports = function(sails) { @@ -128,7 +129,7 @@ module.exports = function(sails) { // // Example override: // methodOverride: (function customMethodOverride (req, res, next) {})() - methodOverride: express.methodOverride + methodOverride: methodOverride } diff --git a/package.json b/package.json index 9f74f8a349..1f25f4ddd7 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "dependencies": { "waterline": "~0.10.11", "express": "3.16.0", + "method-override": "~2.3.0", "rc": "~0.5.0", "sails-stringfile": "~0.3.0", "async": "~0.2.9", From 5e76c138e68e501bcafdd75665e32dc86d117d84 Mon Sep 17 00:00:00 2001 From: Ryan Clardy Date: Tue, 21 Oct 2014 17:18:56 -0500 Subject: [PATCH 005/235] Resolve parseSignedCookie deprecation warnings --- lib/hooks/session/index.js | 2 +- lib/hooks/sockets/lib/authorization.js | 2 +- package.json | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/hooks/session/index.js b/lib/hooks/session/index.js index 9849233de4..a3e56e368a 100644 --- a/lib/hooks/session/index.js +++ b/lib/hooks/session/index.js @@ -7,7 +7,7 @@ var util = require('sails-util'), path = require('path'), generateSecret = require('./generateSecret'), cookie = require('express/node_modules/cookie'), - parseSignedCookie = require('express/node_modules/connect').utils.parseSignedCookie, + parseSignedCookie = require('cookie-parser').signedCookie, ConnectSession = require('express/node_modules/connect').middleware.session.Session; diff --git a/lib/hooks/sockets/lib/authorization.js b/lib/hooks/sockets/lib/authorization.js index d643471dbc..d77bc3ff06 100644 --- a/lib/hooks/sockets/lib/authorization.js +++ b/lib/hooks/sockets/lib/authorization.js @@ -8,7 +8,7 @@ module.exports = function(sails) { var util = require('util'), cookie = require('express/node_modules/cookie'), getSDKMetadata = require('./getSDKMetadata'), - parseSignedCookie = require('express/node_modules/connect').utils.parseSignedCookie, + parseSignedCookie = require('cookie-parser').signedCookie, ConnectSession = require('express/node_modules/connect').middleware.session.Session; diff --git a/package.json b/package.json index 1f25f4ddd7..7b76a4a42b 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "waterline": "~0.10.11", "express": "3.16.0", "method-override": "~2.3.0", + "cookie-parser": "~1.3.3", "rc": "~0.5.0", "sails-stringfile": "~0.3.0", "async": "~0.2.9", From ad968777433364a5d6b3fdd6cdd28f4d7a6a5c4c Mon Sep 17 00:00:00 2001 From: Alejandro Nanez Date: Sun, 9 Nov 2014 23:43:04 -0500 Subject: [PATCH 006/235] Removed unnecessary 'else' Since we're returning cb() on the if statement, the 'else' clause is unnecessary --- lib/app/private/bootstrap.js | 53 +++++++++++++++++------------------- 1 file changed, 25 insertions(+), 28 deletions(-) diff --git a/lib/app/private/bootstrap.js b/lib/app/private/bootstrap.js index 82e16b498e..f860520ffe 100644 --- a/lib/app/private/bootstrap.js +++ b/lib/app/private/bootstrap.js @@ -5,8 +5,6 @@ var util = require('util'); - - /** * runBootstrap * @@ -27,41 +25,40 @@ module.exports = function runBootstrap(cb) { if (!sails.config.bootstrap) { return cb(); } - else { - sails.log.verbose('Running the setup logic in `sails.config.bootstrap(cb)`...'); - // IF bootstrap takes too long, display warning message - // (just in case user forgot to call their bootstrap's `cb`) - var timeoutMs = sails.config.bootstrapTimeout || 2000; - var timer = setTimeout(function bootstrapTookTooLong() { - sails.log.warn(util.format( - 'Bootstrap is taking unusually long to execute its callback (%d miliseconds).\n'+ - 'Perhaps you forgot to call it? The callback is the first argument of the function, `cb`.', - timeoutMs)); - }, timeoutMs); + sails.log.verbose('Running the setup logic in `sails.config.bootstrap(cb)`...'); - var ranBootstrapFn = false; + // IF bootstrap takes too long, display warning message + // (just in case user forgot to call their bootstrap's `cb`) + var timeoutMs = sails.config.bootstrapTimeout || 2000; + var timer = setTimeout(function bootstrapTookTooLong() { + sails.log.warn(util.format( + 'Bootstrap is taking unusually long to execute its callback (%d miliseconds).\n'+ + 'Perhaps you forgot to call it? The callback is the first argument of the function, `cb`.', + timeoutMs)); + }, timeoutMs); - try { - return sails.config.bootstrap(function bootstrapDone(err) { - if (ranBootstrapFn) { - sails.log.error('You called the callback in `sails.config.boostrap` more than once!'); - return; - } - ranBootstrapFn = true; - clearTimeout(timer); - return cb(err); - }); - } - catch (e) { + var ranBootstrapFn = false; + + try { + return sails.config.bootstrap(function bootstrapDone(err) { if (ranBootstrapFn) { - sails.log.error('The bootstrap function threw an error after its callback was called ::',e); + sails.log.error('You called the callback in `sails.config.boostrap` more than once!'); return; } ranBootstrapFn = true; clearTimeout(timer); - return cb(e); + return cb(err); + }); + } + catch (e) { + if (ranBootstrapFn) { + sails.log.error('The bootstrap function threw an error after its callback was called ::',e); + return; } + ranBootstrapFn = true; + clearTimeout(timer); + return cb(e); } }; From 0f51c4be86c27ec29f7de5fe606b5e55556e67f8 Mon Sep 17 00:00:00 2001 From: Markus Padourek Date: Tue, 11 Nov 2014 12:24:08 +0000 Subject: [PATCH 007/235] Removed 'rc' from anchor version again --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9f74f8a349..e4e02542fa 100644 --- a/package.json +++ b/package.json @@ -69,7 +69,7 @@ "include-all": "~0.1.3", "skipper": "~0.5.3", "merge-defaults": "~0.1.0", - "anchor": "~0.10.0-rc", + "anchor": "~0.10.0", "mock-req": "0.1.0", "mock-res": "0.1.0", "semver": "~2.2.1", From 08db259a9c4956920dd87da335604cac91450f6d Mon Sep 17 00:00:00 2001 From: Markus Padourek Date: Tue, 11 Nov 2014 12:27:31 +0000 Subject: [PATCH 008/235] Added sails migrate to Roadmap again --- ROADMAP.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ROADMAP.md b/ROADMAP.md index 2f375c83e0..ba981d3d70 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -50,4 +50,4 @@ The backlog consists of features which are not currently in the immediate-term r Support for multiple blueprint prefixes | [@mnaughto](https://github.com/mnaughto) | https://github.com/balderdashy/sails/issues/2031 SPDY protocol support | [@mikermcneil](https://github.com/mikermcneil) | https://github.com/balderdashy/sails/issues/80 Sockets hook: drop-in Primus alternative | [@alejandroiglesias](https://github.com/alejandroiglesias) | https://github.com/balderdashy/sails/issues/945 - + Have a `sails migrate` or `sails create-db` command | [@globegitter](https://github.com/Globegitter) | For production environments it would be nice to have a save/secure command that creates the db automatically for you From 4a92e9381f40c0a71e1d53c0fb0abd35d941ff2d Mon Sep 17 00:00:00 2001 From: Brandon Weaver Date: Tue, 11 Nov 2014 16:39:26 -0800 Subject: [PATCH 009/235] Fixed typos Noticed a few typos reading through the source. --- lib/preinstall_npmcheck.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/preinstall_npmcheck.js b/lib/preinstall_npmcheck.js index a45db471a4..54e8ccc4af 100644 --- a/lib/preinstall_npmcheck.js +++ b/lib/preinstall_npmcheck.js @@ -32,15 +32,15 @@ var getNpmVersion = function(cb) { }; /* - * Check if version of first paramter >= version of 2nd parameter + * Check if version of first parameter >= version of 2nd parameter * ATTENTION: Only support '>=' * @param {string} v - Version to check (e.g. '10.5.2') * @param {string} v2 - Version to check against (e.g. '0.8.14') - * @retrun {boolean} + * @return {boolean} */ var checkVersion = function(v,v2) { - // Delete all of strings that dont necessary and make arrays + // Delete all unnecessary strings and make arrays v = v.replace(/(\s+)|([^(0-9\.)])/g,'').split('.'); v2 = v2.replace(/(\s+)|([^(0-9\.)])/g,'').split('.'); @@ -48,7 +48,7 @@ var checkVersion = function(v,v2) { return false; } - // Full with 0 if necessary + // Fill with 0 if necessary if(v2.length < 3) { if(v2.length == 2){ v2.push([0]); From e3b2055ae5b4bf6fd0f8eabd59a144368e80a20f Mon Sep 17 00:00:00 2001 From: Enkows Zhang Date: Fri, 14 Nov 2014 01:58:51 +0800 Subject: [PATCH 010/235] fix typo --- bin/sails-www.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/sails-www.js b/bin/sails-www.js index db7639c7f3..18fcd75280 100644 --- a/bin/sails-www.js +++ b/bin/sails-www.js @@ -23,7 +23,7 @@ module.exports = function() { var log = CaptainsLog(rconf.log); var wwwPath = nodepath.resolve(process.cwd(), './www'), - GRUNT_TASK_NAME = 'build'; + GRUNT_TASK_NAME = 'build', GRUNT_TASK_PROD_NAME = 'buildProd'; var sails = Sails(); From b9c51e2317d60c7a9f54507c0ce0df8e44e4aa53 Mon Sep 17 00:00:00 2001 From: Graeme Yeates Date: Sun, 16 Nov 2014 10:41:32 -0500 Subject: [PATCH 011/235] [minor] Use _.initial to simplify generate --- bin/sails-generate.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bin/sails-generate.js b/bin/sails-generate.js index 1a5538d19f..9a34cce645 100644 --- a/bin/sails-generate.js +++ b/bin/sails-generate.js @@ -43,8 +43,7 @@ module.exports = function() { // Pass the original CLI arguments down to the generator // (but first, remove commander's extra argument) // (also peel off the `generatorType` arg) - var cliArguments = Array.prototype.slice.call(arguments); - cliArguments.pop(); + var cliArguments = _.initial(arguments); scope.generatorType = cliArguments.shift(); scope.args = cliArguments; From 4e30cc1c78bdc62d57ddc3594be77ac56d5e87ef Mon Sep 17 00:00:00 2001 From: zander Date: Tue, 18 Nov 2014 07:56:10 +0800 Subject: [PATCH 012/235] Enable views hook for all methods. This fixes issue #2315. --- lib/hooks/views/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/hooks/views/index.js b/lib/hooks/views/index.js index 019131baf1..fe9e606c9e 100644 --- a/lib/hooks/views/index.js +++ b/lib/hooks/views/index.js @@ -56,11 +56,11 @@ module.exports = function (sails) { // (if applicable) if ( sails.hooks.i18n ) { sails.after('hook:i18n:loaded', function () { - sails.router.bind('/*', addResViewMethod, null, {}); + sails.router.bind('/*', addResViewMethod, 'all', {}); }); } else { - sails.router.bind('/*', addResViewMethod); + sails.router.bind('/*', addResViewMethod, 'all'); } }); From cb1f31f619812c76fabd6b3ff5e19f2ab79b6761 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Janto=C5=A1ovi=C4=8D?= Date: Wed, 19 Nov 2014 16:11:10 +0100 Subject: [PATCH 013/235] Fix arguments for publishAdd, publishRemove and publishUpdate --- lib/hooks/pubsub/index.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/hooks/pubsub/index.js b/lib/hooks/pubsub/index.js index d422e17d0d..95e1292850 100644 --- a/lib/hooks/pubsub/index.js +++ b/lib/hooks/pubsub/index.js @@ -809,11 +809,11 @@ module.exports = function(sails) { if (reverseAssociation.type == 'collection') { // If there was a previous value, alert the previously associated model if (previous[key]) { - ReferencedModel.publishRemove(previousPK, reverseAssociation.alias, id, {noReverse:true}); + ReferencedModel.publishRemove(previousPK, reverseAssociation.alias, id, req, {noReverse:true}); } // If there's a new value (i.e. it's not null), alert the newly associated model if (val) { - ReferencedModel.publishAdd(newPK, reverseAssociation.alias, id, {noReverse:true}); + ReferencedModel.publishAdd(newPK, reverseAssociation.alias, id, req, {noReverse:true}); } } // Otherwise do a publishUpdate @@ -867,11 +867,11 @@ module.exports = function(sails) { // Alert any removed models _.each(removedPKs, function(pk) { - ReferencedModel.publishRemove(pk, reverseAssociation.alias, id, {noReverse:true}); + ReferencedModel.publishRemove(pk, reverseAssociation.alias, id, req, {noReverse:true}); }); // Alert any added models _.each(addedPKs, function(pk) { - ReferencedModel.publishAdd(pk, reverseAssociation.alias, id, {noReverse:true}); + ReferencedModel.publishAdd(pk, reverseAssociation.alias, id, req, {noReverse:true}); }); } @@ -990,7 +990,7 @@ module.exports = function(sails) { if (reverseAssociation.type == 'model') { var pubData = {}; pubData[reverseAssociation.alias] = null; - ReferencedModel.publishUpdate(referencedModelId, pubData, {noReverse:true}); + ReferencedModel.publishUpdate(referencedModelId, pubData, req, {noReverse:true}); } // If it's a to-many, publish a "removed" alert else { @@ -1259,7 +1259,7 @@ module.exports = function(sails) { // TODO -- support nested creates. For now, we can't tell if an object value here represents // a NEW object or an existing one, so we'll ignore it. if (reverseAssociation.type == 'collection' && !_.isObject(val)) { - ReferencedModel.publishAdd(val, reverseAssociation.alias, id, {noReverse:true}); + ReferencedModel.publishAdd(val, reverseAssociation.alias, id, req, {noReverse:true}); } // Otherwise do a publishUpdate // TODO -- support nested creates. For now, we can't tell if an object value here represents @@ -1292,7 +1292,7 @@ module.exports = function(sails) { // TODO -- support nested creates. For now, we can't tell if an object value here represents // a NEW object or an existing one, so we'll ignore it. if (_.isObject(pk)) return; - ReferencedModel.publishAdd(pk, reverseAssociation.alias, id, {noReverse:true}); + ReferencedModel.publishAdd(pk, reverseAssociation.alias, id, req, {noReverse:true}); }); } From de8c68eb4421af341b437aad2f5491a026d6a8a8 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Thu, 20 Nov 2014 20:19:32 -0500 Subject: [PATCH 014/235] Fix funky spaces/tabs mixing --- lib/hooks/sockets/lib/loadSocketIO.js | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/lib/hooks/sockets/lib/loadSocketIO.js b/lib/hooks/sockets/lib/loadSocketIO.js index d3efe418f8..afadf6b6e3 100644 --- a/lib/hooks/sockets/lib/loadSocketIO.js +++ b/lib/hooks/sockets/lib/loadSocketIO.js @@ -4,14 +4,14 @@ module.exports = function (sails) { * Module dependencies. */ - var util = require( 'sails-util'), - SocketServer = require('socket.io'), - RedisStore = require('socket.io/lib/stores/redis'), - Redis = require('socket.io/node_modules/redis'), - Socket = { - authorization : require('./authorization')(sails), - connection : require('./connection')(sails) - }; + var util = require( 'sails-util'), + SocketServer = require('socket.io'), + RedisStore = require('socket.io/lib/stores/redis'), + Redis = require('socket.io/node_modules/redis'), + Socket = { + authorization : require('./authorization')(sails), + connection : require('./connection')(sails) + }; /** @@ -27,7 +27,7 @@ module.exports = function (sails) { var socketConfig = sails.config.sockets; // Socket.io server (WebSockets+polyfill to support Flash sockets, AJAX long polling, etc.) - var io = sails.io = sails.ws = + var io = sails.io = sails.ws = SocketServer.listen(sails.hooks.http.server, { logger: { info: function (){} @@ -157,7 +157,7 @@ module.exports = function (sails) { // If Redis connection ends, catch the error and retry // until it comes back - + client.on('ready', function() { sails.log.debug('RedisClient::Events[ready]: [OK] Redis "' + id + '" is up. Connections: ', client.connections); }); @@ -168,9 +168,9 @@ module.exports = function (sails) { client.on('error', function (err) { sails.log.error('RedisClient::Events[error]: "' + id + '" , ' + err); - if (/ECONNREFUSED/g.test(err)) { - sails.log.error('Waiting for "' + id + '" redis client to come back online. Connections:', client.connections); - } + if (/ECONNREFUSED/g.test(err)) { + sails.log.error('Waiting for "' + id + '" redis client to come back online. Connections:', client.connections); + } }); return client; From c09e3deeb9465119fac997df6bd91e57f87517dc Mon Sep 17 00:00:00 2001 From: Konstantin Zolotarev Date: Fri, 21 Nov 2014 16:13:33 +0600 Subject: [PATCH 015/235] Update getBaseurl.js --- lib/app/getBaseurl.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/app/getBaseurl.js b/lib/app/getBaseurl.js index d32843dccc..e7819be742 100644 --- a/lib/app/getBaseurl.js +++ b/lib/app/getBaseurl.js @@ -11,9 +11,6 @@ module.exports = function getBaseurl() { if (!~host.indexOf('http')) { host = (usingSSL || port == 443 ? 'https' : 'http') + '://' + host; } - var localAppURL = - host + - (port == 80 || port == 443 ? '' : ':' + port); - + var localAppURL = host + (port == 80 || port == 443 ? '' : ':' + port); return localAppURL; }; From 8c9432b3eb997150eb2c03eeeee3e9adfef35d17 Mon Sep 17 00:00:00 2001 From: Luis Lobo Borobia Date: Fri, 21 Nov 2014 17:15:30 -0600 Subject: [PATCH 016/235] Update README.md changed misspelled word --- lib/app/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/app/README.md b/lib/app/README.md index 48301cf829..23bf01b40a 100644 --- a/lib/app/README.md +++ b/lib/app/README.md @@ -12,7 +12,7 @@ ## Purpose -The `app` directory conatins logic concerned with the lifecycle of the Sails core itself. This includes: +The `app` directory contains logic concerned with the lifecycle of the Sails core itself. This includes: + Loading and initializing hooks + Loading the router From dd977ba76c3463d6f1372cd937cc61693f1bb8d0 Mon Sep 17 00:00:00 2001 From: sleewoo Date: Sat, 22 Nov 2014 20:23:00 +0200 Subject: [PATCH 017/235] Ractive renderHTML is deprecated starting with Ractive 0.3.8 > `ractive.toHTML()` replaces `ractive.renderHTML()` --- lib/hooks/views/consolidate.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/hooks/views/consolidate.js b/lib/hooks/views/consolidate.js index 34c7e06f6f..995defb056 100644 --- a/lib/hooks/views/consolidate.js +++ b/lib/hooks/views/consolidate.js @@ -778,7 +778,7 @@ module.exports = function(sailsAppPath) { } try { - fn(null, new engine(options).renderHTML()); + fn(null, new engine(options).toHTML()); } catch (err) { fn(err); } From 0b3282def93209d9e105bb17b21916f8c520f42d Mon Sep 17 00:00:00 2001 From: Agent-h Date: Wed, 26 Nov 2014 09:57:23 +0100 Subject: [PATCH 018/235] add "created" response method --- lib/hooks/responses/defaults/created.js | 48 +++++++++++++++++++++++++ lib/hooks/responses/index.js | 1 + 2 files changed, 49 insertions(+) create mode 100644 lib/hooks/responses/defaults/created.js diff --git a/lib/hooks/responses/defaults/created.js b/lib/hooks/responses/defaults/created.js new file mode 100644 index 0000000000..6d63c81cf5 --- /dev/null +++ b/lib/hooks/responses/defaults/created.js @@ -0,0 +1,48 @@ +/** + * 201 (CREATED) Response + * + * Usage: + * return res.created(); + * return res.created(data); + * return res.created(data, 'auth/login'); + * + * @param {Object} data + * @param {String|Object} options + * - pass string to render specified view + */ + +module.exports = function sendOK (data, options) { + + // Get access to `req`, `res`, & `sails` + var req = this.req; + var res = this.res; + var sails = req._sails; + + sails.log.silly('res.created() :: Sending 201 ("CREATED") response'); + + // Set status code + res.status(201); + + // If appropriate, serve data as JSON(P) + if (req.wantsJSON) { + return res.jsonx(data); + } + + // If second argument is a string, we take that to mean it refers to a view. + // If it was omitted, use an empty object (`{}`) + options = (typeof options === 'string') ? { view: options } : options || {}; + + // If a view was provided in options, serve it. + // Otherwise try to guess an appropriate view, or if that doesn't + // work, just send JSON. + if (options.view) { + return res.view(options.view, { data: data }); + } + + // If no second argument provided, try to serve the implied view, + // but fall back to sending JSON(P) if no view can be inferred. + else return res.guessView({ data: data }, function couldNotGuessView () { + return res.jsonx(data); + }); + +}; diff --git a/lib/hooks/responses/index.js b/lib/hooks/responses/index.js index 68275dbd2e..527bb42247 100644 --- a/lib/hooks/responses/index.js +++ b/lib/hooks/responses/index.js @@ -89,6 +89,7 @@ module.exports = function(sails) { // Ensure that required custom responses exist. _.defaults(responseDefs, { ok: require('./defaults/ok'), + created: require('./defaults/created'), negotiate: require('./defaults/negotiate'), notFound: require('./defaults/notFound'), serverError: require('./defaults/serverError'), From 83f60ff19ef750b3543f2a8483219d746b89b6ac Mon Sep 17 00:00:00 2001 From: Agent-h Date: Wed, 26 Nov 2014 09:58:00 +0100 Subject: [PATCH 019/235] blueprint create returns 201 status code. fixes #2299 --- lib/hooks/blueprints/actions/create.js | 4 +--- test/integration/hook.cors_csrf.test.js | 8 ++++---- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/lib/hooks/blueprints/actions/create.js b/lib/hooks/blueprints/actions/create.js index 80a03cbdbe..ef0f97922d 100644 --- a/lib/hooks/blueprints/actions/create.js +++ b/lib/hooks/blueprints/actions/create.js @@ -44,8 +44,6 @@ module.exports = function createRecord (req, res) { } // Send JSONP-friendly response if it's supported - // (HTTP 201: Created) - res.status(201); - res.ok(newInstance); + res.created(newInstance); }); }; diff --git a/test/integration/hook.cors_csrf.test.js b/test/integration/hook.cors_csrf.test.js index 54f5cb4d53..7099dccf01 100644 --- a/test/integration/hook.cors_csrf.test.js +++ b/test/integration/hook.cors_csrf.test.js @@ -724,7 +724,7 @@ describe('CORS and CSRF ::', function() { }); - it("a POST request with a valid CSRF token should result in a 200 response", function (done) { + it("a POST request with a valid CSRF token should result in a 201 response", function (done) { httpHelper.testRoute("get", 'csrftoken', function (err, response) { if (err) return done(new Error(err)); @@ -742,7 +742,7 @@ describe('CORS and CSRF ::', function() { if (err) return done(new Error(err)); - assert.equal(response.statusCode, 200); + assert.equal(response.statusCode, 201); done(); }); @@ -777,10 +777,10 @@ describe('CORS and CSRF ::', function() { fs.writeFileSync(path.resolve('../', appName, 'config/csrf.js'), "module.exports.csrf = {protectionEnabled: true, routesDisabled: '/user'};"); }); - it("a POST request on /user without a CSRF token should result in a 200 response", function (done) { + it("a POST request on /user without a CSRF token should result in a 201 response", function (done) { httpHelper.testRoute("post", 'user', function (err, response) { if (err) return done(new Error(err)); - assert.equal(response.statusCode, 200); + assert.equal(response.statusCode, 201); done(); }); From 51ba87799dbf3e9e0c39596fc3fd0a318ec6e076 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Saint-Roch?= Date: Wed, 26 Nov 2014 13:46:07 +0100 Subject: [PATCH 020/235] Update CHANGELOG.md with latest commits --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f4383002c4..4f993f3be4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ ### master +* [ENHANCEMENT] Support partials and layout with Handlebars for the `backend` generator +* [BUGFIX] Blueprint creation returns 201 status code instead of 200 +* [BUGFIX] `ractive.toHTML()` replaces `ractive.renderHTML()` for Ractive template engine +* [BUGFIX] Fix arguments for publishAdd, publishRemove and publishUpdate +* [ENHANCEMENT] Enable views hook for all methods +* [BUGFIX] Resolve depreciation warnings +* [BUGFIX] Fix dependency for npm 2.0.0 +* [BUGFIX] Fix Grunt launching when it's a peer dep * [ENHANCEMENT] Upgrade express and skipper because of security vulnerabilities * [BUGFIX] Fix Sails crashes if Redis goes down [#2277](https://github.com/balderdashy/sails/pull/2277) * [BUGFIX] Fix crash when using sessionless requests over WebSockets [#2107](https://github.com/balderdashy/sails/pull/2107) From dbb816640e2ab08130633318f41d6418b10ec89c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Saint-Roch?= Date: Wed, 26 Nov 2014 15:38:18 +0100 Subject: [PATCH 021/235] Update warnings for layout since we support handlebars --- lib/hooks/views/configure.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/hooks/views/configure.js b/lib/hooks/views/configure.js index d9dfef172e..c30bbda35e 100644 --- a/lib/hooks/views/configure.js +++ b/lib/hooks/views/configure.js @@ -11,7 +11,7 @@ var _ = require('lodash') /** * Marshal relevant parts of sails global configuration, * issue deprecation notices, etc. - * + * * @param {Sails} sails */ @@ -91,9 +91,8 @@ module.exports = function configure ( sails ) { sails.config.views.layout = 'layout.' + sails.config.views.engine.ext; } - if ( sails.config.views.engine.ext !== 'ejs' && - sails.config.views.layout ) { - sails.log.warn('Sails\' built-in layout support only works with the `ejs` view engine.'); + if ( !sails.config.views.layout ) { + sails.log.warn('Sails\' built-in layout support only works with the `ejs` and `handlebars` view engines.'); sails.log.warn('You\'re using `'+ sails.config.views.engine.ext +'`.'); sails.log.warn('Ignoring `sails.config.views.layout`...'); } From 82ac2a8e9f139f0c81ddb57dfb9b461f3e897256 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Saint-Roch?= Date: Thu, 27 Nov 2014 10:55:37 +0100 Subject: [PATCH 022/235] Better warnings for layout configuration --- lib/hooks/views/configure.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/hooks/views/configure.js b/lib/hooks/views/configure.js index c30bbda35e..54130f2f07 100644 --- a/lib/hooks/views/configure.js +++ b/lib/hooks/views/configure.js @@ -91,7 +91,7 @@ module.exports = function configure ( sails ) { sails.config.views.layout = 'layout.' + sails.config.views.engine.ext; } - if ( !sails.config.views.layout ) { + if ( sails.config.views.engine.ext !== 'ejs' && sails.config.views.engine.ext !== 'handlebars' && sails.config.views.layout ) { sails.log.warn('Sails\' built-in layout support only works with the `ejs` and `handlebars` view engines.'); sails.log.warn('You\'re using `'+ sails.config.views.engine.ext +'`.'); sails.log.warn('Ignoring `sails.config.views.layout`...'); From 4a3b013efeee95746a2fb27ce9a5780a360cfe7b Mon Sep 17 00:00:00 2001 From: Scott Gress Date: Wed, 3 Dec 2014 11:51:10 -0600 Subject: [PATCH 023/235] Load environment-specific configuration folders --- lib/hooks/moduleloader/index.js | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/lib/hooks/moduleloader/index.js b/lib/hooks/moduleloader/index.js index 53fa88dc42..f4058caa8d 100644 --- a/lib/hooks/moduleloader/index.js +++ b/lib/hooks/moduleloader/index.js @@ -179,8 +179,22 @@ module.exports = function(sails) { }, cb); }, + // Load environment-specific config folder, e.g. config/env/development/* + 'config/env/**': ['config/local', function loadEnvConfigFolder (cb, async_data) { + // If there's an environment already set in sails.config, then it came from the environment + // or the command line, so that takes precedence. Otherwise, check the config/local.js file + // for an environment setting. Lastly, default to development. + var env = sails.config.environment || async_data['config/local'].environment || 'development'; + buildDictionary.aggregate({ + dirname : (sails.config.paths.config || sails.config.appPath + '/config') + '/env/' + env, + filter : /(.+)\.(js|json|coffee|litcoffee)$/, + optional : true, + identity : false + }, cb); + }], - 'config/env/*' : ['config/local', function loadLocalOverrideFile (cb, async_data) { + // Load environment-specific config file, e.g. config/env/development.js + 'config/env/*' : ['config/local', function loadEnvConfigFile (cb, async_data) { // If there's an environment already set in sails.config, then it came from the environment // or the command line, so that takes precedence. Otherwise, check the config/local.js file // for an environment setting. Lastly, default to development. @@ -201,6 +215,7 @@ module.exports = function(sails) { // taking precedence over everything var config = sails.util.merge( async_data['config/*'], + async_data['config/env/**'], async_data['config/env/*'], async_data['config/local'] ); From 0c14e3de6b90308602d91ea5483e06036306c4c5 Mon Sep 17 00:00:00 2001 From: Mike McNeil Date: Wed, 3 Dec 2014 11:53:32 -0600 Subject: [PATCH 024/235] Update MODULES.md --- MODULES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MODULES.md b/MODULES.md index 6cad2eb014..07ac122d52 100644 --- a/MODULES.md +++ b/MODULES.md @@ -16,7 +16,7 @@ Below, you'll find an overview of the modules maintained by the core team and co | [**waterline**](http://github.com/balderdashy/waterline) | [![Build Status](https://travis-ci.org/balderdashy/waterline.png?branch=master)](https://travis-ci.org/balderdashy/waterline) | [![NPM version](https://badge.fury.io/js/waterline.png)](http://badge.fury.io/js/waterline) | | [**anchor**](http://github.com/balderdashy/anchor) | [![Build Status](https://travis-ci.org/balderdashy/anchor.png?branch=master)](https://travis-ci.org/balderdashy/anchor) | [![NPM version](https://badge.fury.io/js/anchor.png)](http://badge.fury.io/js/anchor) | | [**waterline-criteria**](http://github.com/balderdashy/waterline-criteria) | [![Build Status](https://travis-ci.org/balderdashy/waterline-criteria.png?branch=master)](https://travis-ci.org/balderdashy/waterline-criteria) | [![NPM version](https://badge.fury.io/js/waterline-criteria.png)](http://badge.fury.io/js/waterline-criteria) | -| [**waterline-errors**](http://github.com/balderdashy/waterline-errors) | NEEDS_TRAVIS_SETUP | [![NPM version](https://badge.fury.io/js/waterline-errors.png)](http://badge.fury.io/js/waterline-errors) | +| [**waterline-errors**](http://github.com/vanetix/waterline-errors) | NEEDS_TRAVIS_SETUP | [![NPM version](https://badge.fury.io/js/waterline-errors.png)](http://badge.fury.io/js/waterline-errors) | | [**waterline-schema**](http://github.com/balderdashy/waterline-schema) | NEEDS_TRAVIS_SETUP | [![NPM version](https://badge.fury.io/js/waterline-schema.png)](http://badge.fury.io/js/waterline-schema) | | [**sails-generate**](http://github.com/balderdashy/sails-generate) | [![Build Status](https://travis-ci.org/balderdashy/sails-generate.png?branch=master)](https://travis-ci.org/balderdashy/sails-generate) | [![NPM version](https://badge.fury.io/js/sails-generate.png)](http://badge.fury.io/js/sails-generate) | | [**sails-build-dictionary**](http://github.com/balderdashy/sails-build-dictionary) | N/A | [![NPM version](https://badge.fury.io/js/sails-build-dictionary.png)](http://badge.fury.io/js/sails-build-dictionary) | From 1b113edb35818c2f5196256f630057f046b3fb54 Mon Sep 17 00:00:00 2001 From: sleewoo Date: Thu, 4 Dec 2014 18:28:05 +0200 Subject: [PATCH 025/235] Layout support for Ractive --- lib/hooks/views/consolidate.js | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/lib/hooks/views/consolidate.js b/lib/hooks/views/consolidate.js index 995defb056..69d9535041 100644 --- a/lib/hooks/views/consolidate.js +++ b/lib/hooks/views/consolidate.js @@ -769,14 +769,34 @@ module.exports = function(sailsAppPath) { */ fns.ractive.render = function(str, options, fn){ + var layout = sails.config.views.layout; + if (Object.keys(options).indexOf('layout') > -1) + layout = options.layout; + + if (!layout) + return ractiveRender('{{> body }}', str, options, fn); + + var file = layout + '.' + sails.config.views.engine.ext; + var path = sails.config.paths.views + '/' + file; + // only use cache on production + var cache = sails.config.environment === 'production'; + read(path, {cache: cache}, function (err, layout) { + if (err) + return fn(err); + ractiveRender(layout, str, options, fn); + }); + }; + + ractiveRender = function(layout, body, options, fn) { var engine = requires.ractive || (requires.ractive = require(sailsAppPath + '/ractive')); - - options.template = str; - if (options.data === null || options.data === undefined) - { + + options.template = layout; + if (options.data === null || options.data === undefined) { options.data = options; } - + options.partials || (options.partials = {}); + options.partials.body = body; + try { fn(null, new engine(options).toHTML()); } catch (err) { From f0efb02d431510767bca551925e72f915c27ff58 Mon Sep 17 00:00:00 2001 From: sleewoo Date: Thu, 4 Dec 2014 18:29:05 +0200 Subject: [PATCH 026/235] make ractiveRender local --- lib/hooks/views/consolidate.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/hooks/views/consolidate.js b/lib/hooks/views/consolidate.js index 69d9535041..7bf6866ebf 100644 --- a/lib/hooks/views/consolidate.js +++ b/lib/hooks/views/consolidate.js @@ -787,7 +787,7 @@ module.exports = function(sailsAppPath) { }); }; - ractiveRender = function(layout, body, options, fn) { + var ractiveRender = function(layout, body, options, fn) { var engine = requires.ractive || (requires.ractive = require(sailsAppPath + '/ractive')); options.template = layout; From ed6284e2a48a0826d2e378b2b3d2f6d600642395 Mon Sep 17 00:00:00 2001 From: Lord Daniel Zautner Date: Mon, 8 Dec 2014 17:34:31 +0200 Subject: [PATCH 027/235] Remove unexplained commented lines This two lines are making this file even messier than it already is. Commented out inlined try blocks should never make it to the master branch. --- lib/app/lift.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/app/lift.js b/lib/app/lift.js index 68a8301cba..833638f408 100644 --- a/lib/app/lift.js +++ b/lib/app/lift.js @@ -17,10 +17,6 @@ var _ = require('lodash'); module.exports = function lift(configOverride, cb) { var sails = this; - // try {console.timeEnd('require_core');}catch(e){} - - // try {console.time('core_lift');}catch(e){} - // Callback is optional cb = cb || function(err) { if (err) return sails.log.error(err); From 51ff21bde578cc3d7cad81672552a721aedeeac8 Mon Sep 17 00:00:00 2001 From: Mike McNeil Date: Tue, 9 Dec 2014 13:27:24 -0600 Subject: [PATCH 028/235] Error-proof beforeShutdown. --- lib/app/lower.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/app/lower.js b/lib/app/lower.js index 18a912808f..6d0bd5b699 100644 --- a/lib/app/lower.js +++ b/lib/app/lower.js @@ -27,9 +27,9 @@ module.exports = function lower(cb) { }; sails._exiting = true; - var beforeShutdown = sails.config.beforeShutdown || function(cb) { - return cb(); - }; + var beforeShutdown = (sails.config && sails.config.beforeShutdown) || function(cb) { + return cb(); + }; // Wait until beforeShutdown logic runs beforeShutdown(function(err) { @@ -59,7 +59,7 @@ module.exports = function lower(cb) { async.series([ function shutdownSockets(cb) { - if (!sails.hooks.sockets) { + if (!sails.hooks || !sails.hooks.sockets) { return cb(); } @@ -108,7 +108,7 @@ module.exports = function lower(cb) { } // If `sails.config.process.removeAllListeners` is set, do that. - if (sails.config.process && sails.config.process.removeAllListeners) { + if (sails.config && sails.config.process && sails.config.process.removeAllListeners) { process.removeAllListeners(); // TODO: From 510004e44b03f609f086c180e2028039117d7358 Mon Sep 17 00:00:00 2001 From: Scott Gress Date: Tue, 9 Dec 2014 18:00:13 -0600 Subject: [PATCH 029/235] Set the configurable config key for a user-installed hook --- lib/app/private/loadHooks.js | 5 +++++ lib/hooks/moduleloader/index.js | 8 +++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/lib/app/private/loadHooks.js b/lib/app/private/loadHooks.js index 5b456f7146..7bb8139a80 100644 --- a/lib/app/private/loadHooks.js +++ b/lib/app/private/loadHooks.js @@ -54,6 +54,11 @@ module.exports = function(sails) { // Mix in an `identity` property to hook definition def.identity = id.toLowerCase(); + // If a config key was defined for this hook when it was loaded, + // (probably because a user is overridding the default config key) + // set it on the hook definition + def.configKey = hookPrototype.configKey; + // New up an actual Hook instance hooks[id] = new Hook(def); }); diff --git a/lib/hooks/moduleloader/index.js b/lib/hooks/moduleloader/index.js index f4058caa8d..e60fb0be0f 100644 --- a/lib/hooks/moduleloader/index.js +++ b/lib/hooks/moduleloader/index.js @@ -408,8 +408,14 @@ module.exports = function(sails) { cb(new Error("Tried to load hook `" + hookName + "`, but a hook with that name already exists.")); } + // Load the hook code + var hook = require(path.resolve(sails.config.appPath, "node_modules", identity)); + + // Set its config key (defaults to the hook name) + hook.configKey = (sails.config.hooks && sails.config.hooks[identity] && sails.config.hooks[identity].configKey) || hookName; + // Add this to the list of hooks to load - memo[hookName] = require(path.resolve(sails.config.appPath, "node_modules", identity)); + memo[hookName] = hook; } return memo; }, {})); From 35d7242f4f1d24b49558b6b852b6912cb6c2592c Mon Sep 17 00:00:00 2001 From: Robert Djurasaj Date: Mon, 8 Dec 2014 14:05:38 -0700 Subject: [PATCH 030/235] Sail boat cannot go without seagulls :smiley: --- lib/hooks/logger/ship.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/hooks/logger/ship.js b/lib/hooks/logger/ship.js index 1c47e0a111..a933a5a5b4 100644 --- a/lib/hooks/logger/ship.js +++ b/lib/hooks/logger/ship.js @@ -14,8 +14,9 @@ module.exports = function _drawShip(message, log) { return function() { log(''); + log(' .-..-.'); log(''); - log(' ' + 'Sails ' + ' ' + '<' + '|'); + log(' ' + 'Sails ' + ' ' + '<' + '|' + ' .-..-.'); log(' ' + message + ' |\\'); log(' /|.\\'); log(' / || \\'); From 39642558a442c38c4ac8781187b17006c3bee6ba Mon Sep 17 00:00:00 2001 From: Mike van Rossum Date: Wed, 10 Dec 2014 10:59:02 +0100 Subject: [PATCH 031/235] catch undefined child pk in blueprints/remove --- lib/hooks/blueprints/actions/remove.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/hooks/blueprints/actions/remove.js b/lib/hooks/blueprints/actions/remove.js index bbc7117521..02ae1e588d 100644 --- a/lib/hooks/blueprints/actions/remove.js +++ b/lib/hooks/blueprints/actions/remove.js @@ -37,6 +37,10 @@ module.exports = function remove(req, res) { // from the aliased collection var childPk = actionUtil.parsePk(req); + if(_.isUndefined(childPk)) { + return res.serverError('Missing required child PK.'); + } + Model .findOne(parentPk).exec(function found(err, parentRecord) { if (err) return res.serverError(err); From cffc61bc2581b390a029dba176d8d3e86a284e21 Mon Sep 17 00:00:00 2001 From: Mike McNeil Date: Wed, 10 Dec 2014 09:35:11 -0600 Subject: [PATCH 032/235] remove old comment --- lib/app/private/loadHooks.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/app/private/loadHooks.js b/lib/app/private/loadHooks.js index 7bb8139a80..35f9619862 100644 --- a/lib/app/private/loadHooks.js +++ b/lib/app/private/loadHooks.js @@ -24,8 +24,6 @@ module.exports = function(sails) { _.each(hooks, function(hookPrototype, id) { // Allow disabling of hooks by setting them to "false" - // Useful for testing, but may cause instability in production! - // I sure hope you know what you're doing :) if (hookPrototype === false || hooks[id.split('.')[0]] === false) { delete hooks[id]; return; From b507d99ea076132846cf7fe182ee84b4b5cdad7f Mon Sep 17 00:00:00 2001 From: Mike McNeil Date: Wed, 10 Dec 2014 09:39:00 -0600 Subject: [PATCH 033/235] normalize requires in sockets hook to match conventions --- lib/hooks/sockets/lib/authorization.js | 18 +++++++++--------- lib/hooks/sockets/lib/connection.js | 4 ++-- lib/hooks/sockets/lib/loadSocketIO.js | 16 ++++++++-------- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/lib/hooks/sockets/lib/authorization.js b/lib/hooks/sockets/lib/authorization.js index d77bc3ff06..eb36ab73fa 100644 --- a/lib/hooks/sockets/lib/authorization.js +++ b/lib/hooks/sockets/lib/authorization.js @@ -5,15 +5,15 @@ module.exports = function(sails) { * Module dependencies. */ - var util = require('util'), - cookie = require('express/node_modules/cookie'), - getSDKMetadata = require('./getSDKMetadata'), - parseSignedCookie = require('cookie-parser').signedCookie, - ConnectSession = require('express/node_modules/connect').middleware.session.Session; + var util = require('util'); + var parseSignedCookie = require('cookie-parser').signedCookie; + var cookie = require('express/node_modules/cookie'); + var ConnectSession = require('express/node_modules/connect').middleware.session.Session; + var getSDKMetadata = require('./getSDKMetadata'); - /** + /** * Fired after a socket is connected */ @@ -44,14 +44,14 @@ module.exports = function(sails) { // Parse and decrypt cookie and save it in the handshake if (!handshake.headers.cookie) { - return socketConnectionError(accept, + return socketConnectionError(accept, 'Cannot load session for an incoming socket.io connection... ' + '\n'+ 'No cookie was sent!\n'+ TROUBLESHOOTING, 'Cannot load session. No cookie transmitted.' ); } - + // Decrypt cookie into session id using session secret // Maintain sessionID in socket handshake so that the session // can be queried before processing each incoming message from this @@ -94,7 +94,7 @@ module.exports = function(sails) { // TO_TEST: // do we need to set handshake.sessionID with the id of the new session? - + // TO_TEST: // do we need to revoke/replace the cookie as well? // how can we do that w/ socket.io? diff --git a/lib/hooks/sockets/lib/connection.js b/lib/hooks/sockets/lib/connection.js index 5b3823f66b..34498087f3 100644 --- a/lib/hooks/sockets/lib/connection.js +++ b/lib/hooks/sockets/lib/connection.js @@ -10,8 +10,8 @@ module.exports = function(sails) { * Module dependencies. */ - var interpret = require('./interpreter/interpret')(sails), - _ = require('lodash'); + var _ = require('lodash'); + var interpret = require('./interpreter/interpret')(sails); diff --git a/lib/hooks/sockets/lib/loadSocketIO.js b/lib/hooks/sockets/lib/loadSocketIO.js index afadf6b6e3..bf40d70d5f 100644 --- a/lib/hooks/sockets/lib/loadSocketIO.js +++ b/lib/hooks/sockets/lib/loadSocketIO.js @@ -4,14 +4,14 @@ module.exports = function (sails) { * Module dependencies. */ - var util = require( 'sails-util'), - SocketServer = require('socket.io'), - RedisStore = require('socket.io/lib/stores/redis'), - Redis = require('socket.io/node_modules/redis'), - Socket = { - authorization : require('./authorization')(sails), - connection : require('./connection')(sails) - }; + var util = require( 'sails-util'); + var SocketServer = require('socket.io'); + var RedisStore = require('socket.io/lib/stores/redis'); + var Redis = require('socket.io/node_modules/redis'); + var Socket = { + authorization: require('./authorization')(sails), + connection: require('./connection')(sails) + }; /** From 68eef3a1b11dba9fe7f80035c07aefb574820bd3 Mon Sep 17 00:00:00 2001 From: Mike McNeil Date: Wed, 10 Dec 2014 09:42:45 -0600 Subject: [PATCH 034/235] make defaults deep local --- lib/hooks/sockets/lib/interpreter/interpret.js | 10 +++++----- lib/router/bind.js | 5 +++-- lib/router/bindDefaultHandlers.js | 1 + lib/router/index.js | 2 +- lib/router/req.js | 4 ++-- 5 files changed, 12 insertions(+), 10 deletions(-) diff --git a/lib/hooks/sockets/lib/interpreter/interpret.js b/lib/hooks/sockets/lib/interpreter/interpret.js index 3412cde07a..5333e08271 100644 --- a/lib/hooks/sockets/lib/interpreter/interpret.js +++ b/lib/hooks/sockets/lib/interpreter/interpret.js @@ -12,11 +12,11 @@ module.exports = function (sails) { * Module dependencies. */ - var util = require('sails-util'), - ResStream = require('./ResStream'), - getVerb = require('./getVerb'), - saveSessionAndThen = require('./saveSessionAndThen'), - getSDKMetadata = require('../getSDKMetadata'); + var util = require('sails-util'); + var ResStream = require('./ResStream'); + var getVerb = require('./getVerb'); + var saveSessionAndThen = require('./saveSessionAndThen'); + var getSDKMetadata = require('../getSDKMetadata'); /** diff --git a/lib/router/bind.js b/lib/router/bind.js index 87012b9ed0..66624c1d2b 100644 --- a/lib/router/bind.js +++ b/lib/router/bind.js @@ -1,8 +1,9 @@ /** * Module dependencies. */ -var util = require('sails-util'), - _ = require('lodash'); + +var _ = require('lodash'); +var util = require('sails-util'); diff --git a/lib/router/bindDefaultHandlers.js b/lib/router/bindDefaultHandlers.js index d77c39bb3b..efb4aa8bea 100644 --- a/lib/router/bindDefaultHandlers.js +++ b/lib/router/bindDefaultHandlers.js @@ -1,6 +1,7 @@ /** * Module dependencies */ + var util = require('util'); diff --git a/lib/router/index.js b/lib/router/index.js index c86740400a..6aaeb8b532 100644 --- a/lib/router/index.js +++ b/lib/router/index.js @@ -2,9 +2,9 @@ * Module dependencies. */ +var util = require('util'); var Writable = require('stream').Writable; var QS = require('querystring'); -var util = require('util'); var _ = require('lodash'); var express = require('express'); diff --git a/lib/router/req.js b/lib/router/req.js index 2823b384bb..08ad7d912f 100644 --- a/lib/router/req.js +++ b/lib/router/req.js @@ -2,7 +2,7 @@ * Module dependencies */ var _ = require('lodash'); -_.defaultsDeep = require('merge-defaults'); +var defaultsDeep = require('merge-defaults'); var MockReq = require('mock-req'); @@ -54,7 +54,7 @@ module.exports = function buildRequest (_req) { req.session = (_req && _req.session) || new FakeSession(); // Provide defaults for other request state and methods - req = _.defaultsDeep(req, { + req = defaultsDeep(req, { params: [], query: (_req && _req.query) || {}, body: (_req && _req.body) || {}, From b6b70ca3787bd923953b3f88f23483379d098fcf Mon Sep 17 00:00:00 2001 From: Mike McNeil Date: Wed, 10 Dec 2014 10:44:52 -0600 Subject: [PATCH 035/235] Trivial: format requires --- lib/hooks/http/initialize.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/hooks/http/initialize.js b/lib/hooks/http/initialize.js index 0573237ab3..9595c35584 100644 --- a/lib/hooks/http/initialize.js +++ b/lib/hooks/http/initialize.js @@ -2,8 +2,8 @@ * Module dependencies. */ -var _ = require('lodash'), - async = require('async'); +var _ = require('lodash'); +var async = require('async'); From 87eede73d1637a33e1de278ed8d5e7b8066dd3b3 Mon Sep 17 00:00:00 2001 From: Mike McNeil Date: Wed, 10 Dec 2014 11:18:36 -0600 Subject: [PATCH 036/235] format prep for sio1 --- lib/hooks/sockets/lib/index.js | 32 ++++++++++++++------------------ 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/lib/hooks/sockets/lib/index.js b/lib/hooks/sockets/lib/index.js index e6aa8cc51e..5750673e72 100644 --- a/lib/hooks/sockets/lib/index.js +++ b/lib/hooks/sockets/lib/index.js @@ -52,11 +52,11 @@ module.exports = function(sails) { // Default behavior is a noop // Code to run when a new socket connects - onConnect: function (session, socket) {}, + onConnect: function(session, socket) {}, // Default behavior is a noop // Code to run when a socket disconnects - onDisconnect: function (session, socket) {}, + onDisconnect: function(session, socket) {}, // Setup adapter to use for socket.io MQ (pubsub) store // (`undefined` indicates default memory store) @@ -144,7 +144,7 @@ module.exports = function(sails) { // Should be set to true when you want the location handshake to match the protocol of the origin. // This fixes issues with terminating the SSL in front of Node // and forcing location to think it's wss instead of ws. - 'match origin protocol' : false, + 'match origin protocol': false, // Global authorization for Socket.IO access, // this is called when the initial handshake is performed with the server. @@ -191,19 +191,16 @@ module.exports = function(sails) { }, - configure: function () { + configure: function() { // onConnect must be valid function if (sails.config.sockets.onConnect && typeof sails.config.sockets.onConnect !== 'function') { throw new Error('Invalid `sails.config.sockets.onConnect`! Must be a function.'); } // If one piece of the ssl config is specified, ensure the other required piece is there - if ( sails.config.ssl && ( + if (sails.config.ssl && ( sails.config.ssl.cert && !sails.config.ssl.key - ) || ( - !sails.config.ssl.cert && sails.config.ssl.key - ) - ) { + ) || (!sails.config.ssl.cert && sails.config.ssl.key)) { throw new Error('Invalid SSL config object! Must include cert and key!'); } }, @@ -221,14 +218,13 @@ module.exports = function(sails) { // If http hook is enabled, wait until the http server is configured // before linking the socket server to it - sails.after('hook:http:loaded', function () { - sails.after('hook:session:loaded', function () { + sails.after('hook:http:loaded', function() { + sails.after('hook:session:loaded', function() { loadSocketIO(cb); }); }); - } - else { + } else { // TODO: implement standalone socket server usage var notImplementedError = @@ -253,7 +249,7 @@ module.exports = function(sails) { before: { - 'all /*': function addOriginHeader (req, res, next) { + 'all /*': function addOriginHeader(req, res, next) { if (req.isSocket) { req.headers = req.headers || {}; req.headers.origin = req.socket.handshake && req.socket.handshake.headers && req.socket.handshake.headers.origin; @@ -261,13 +257,13 @@ module.exports = function(sails) { return next(); }, - 'get /firehose': function firehose (req, res) { + 'get /firehose': function firehose(req, res) { if (!req.isSocket) { sails.log.error("Cannot subscribe to firehose over HTTP! Firehose is for socket messages only."); return; } if (process.env.NODE_ENV !== 'development') { - sails.log.warn('Warning: A client socket was just subscribed to the firehose, but the environment is set to `'+process.env.NODE_ENV+'`.'+ + sails.log.warn('Warning: A client socket was just subscribed to the firehose, but the environment is set to `' + process.env.NODE_ENV + '`.' + ' Firehose messages are only delivered in the development environment.'); } sails.log.silly("A client socket was just subscribed to the firehose."); @@ -278,11 +274,11 @@ module.exports = function(sails) { after: { - 'get /__getcookie': function sendCookie (req, res, next) { + 'get /__getcookie': function sendCookie(req, res, next) { // Allow this endpoint to be disabled by setting: // sails.config.sockets.grant3rdPartyCookie = false; - if ( !sails.config.sockets.grant3rdPartyCookie ) { + if (!sails.config.sockets.grant3rdPartyCookie) { return next(); } From 65f66a5b09cd6d75a5775c0e0c05c029b444f3f5 Mon Sep 17 00:00:00 2001 From: Mike McNeil Date: Wed, 10 Dec 2014 17:04:38 -0600 Subject: [PATCH 037/235] Fix typo --- lib/EVENTS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/EVENTS.md b/lib/EVENTS.md index 8fa71b0d73..ce40848202 100644 --- a/lib/EVENTS.md +++ b/lib/EVENTS.md @@ -106,7 +106,7 @@ Called when a route is unbound. ### Runtime -> NOTE: these event should only be relied on by attached servers without their own routers, or when a hook +> NOTE: these events should only be relied on by attached servers without their own routers, or when a hook > implementation prefers to use the built-in Sails router. > > The optimal behavior for the http hook implemented on Express, for instance, is to listen to `router:bind` From 813f66f8e5391258b9c29a98199a015e9a681a02 Mon Sep 17 00:00:00 2001 From: Mike McNeil Date: Wed, 10 Dec 2014 18:00:47 -0600 Subject: [PATCH 038/235] Some new silly-level logs in core virtual router code --- lib/router/index.js | 18 ++++++++++++++---- lib/router/req.js | 15 +++++++++++++++ 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/lib/router/index.js b/lib/router/index.js index 6aaeb8b532..2f3506027f 100644 --- a/lib/router/index.js +++ b/lib/router/index.js @@ -3,6 +3,7 @@ */ var util = require('util'); +var Readable = require('stream').Readable; var Writable = require('stream').Writable; var QS = require('querystring'); var _ = require('lodash'); @@ -139,10 +140,9 @@ Router.prototype.route = function(req, res) { var sails = this.sails; var _privateRouter = this._privateRouter; - // Use base req and res definitions to ensure the specified // objects are at least ducktype-able as standard node HTTP - // req and req objects. + // req and req streams. // // Make sure request and response objects have reasonable defaults // (will use the supplied definitions if possible) @@ -162,11 +162,13 @@ Router.prototype.route = function(req, res) { // Run some basic middleware + sails.log.silly('Handling virtual request :: Running virtual querystring parser...'); qsParser(req,res, function (err) { if (err) { return res.send(400, err && err.stack); } + sails.log.silly('Handling virtual request :: Running virtual body parser...'); bodyParser(req,res, function (err) { if (err) { return res.send(400, err && err.stack); @@ -181,11 +183,13 @@ Router.prototype.route = function(req, res) { // Use the default server error handler if (err) { + sails.log.silly('Handling virtual request :: Running final "error" handler...'); sails.emit('router:request:500', err, req, res); return; } // Or the default not found handler + sails.log.silly('Handling virtual request :: Running final "not found" handler...'); sails.emit('router:request:404', req, res); return; }); @@ -321,12 +325,16 @@ function qsParser(req,res,next) { // Extremely simple body parser (`req.body`) function bodyParser (req, res, next) { var bodyBuffer=''; - var parsedBody={}; if (req.method === 'GET' || req.method === 'HEAD' || req.method === 'DELETE'){ - req.body = _.merge(req.body, parsedBody); + req.body = _.extend({}, req.body); return next(); } + // Ensure that `req` is a readable stream at this point + if ( ! req instanceof Readable ) { + return next(new Error('Sails Internal Error: `req` should be a Readable stream by the time `route()` is called')); + } + req.on('readable', function() { var chunk; while (null !== (chunk = req.read())) { @@ -334,6 +342,8 @@ function bodyParser (req, res, next) { } }); req.on('end', function() { + + var parsedBody; try { parsedBody = JSON.parse(bodyBuffer); } catch (e) {} diff --git a/lib/router/req.js b/lib/router/req.js index 08ad7d912f..494f8142b6 100644 --- a/lib/router/req.js +++ b/lib/router/req.js @@ -22,6 +22,9 @@ module.exports = function buildRequest (_req) { _req = _req||{}; var req; + + // If `_req` appears to be a stream (duck-typing), then don't try + // and turn it into a mock stream again. if (typeof _req === 'object' && _req.read) { req = _req; } @@ -37,11 +40,23 @@ module.exports = function buildRequest (_req) { }); } + // Create a mock request stream. req = new MockReq({ method: _req && _req.method || 'GET', headers: _req && _req.headers || {}, url: _req && _req.url }); + + // // If not, pump client request body to the IncomingMessage stream (req) + // // Req stream ends automatically if this is a GET or HEAD or DELETE request + // // (since there is no request body in that case) + // if (req.method !== 'GET' && req.method !== 'HEAD' && req.method !== 'DELETE') { + // // Only write the body if there IS a body. + // if (req.body) { + // req.write(req.body); + // } + // req.end(); + // } } function FakeSession() { From 15058737279129e49710bb90b135a8e210c6e336 Mon Sep 17 00:00:00 2001 From: Mike McNeil Date: Wed, 10 Dec 2014 18:12:08 -0600 Subject: [PATCH 039/235] Core interpreter pumps req.body into virtual IncomingMessage stream --- lib/app/request.js | 12 ---------- .../sockets/lib/interpreter/interpret.js | 11 --------- lib/router/req.js | 23 ++++++++++--------- 3 files changed, 12 insertions(+), 34 deletions(-) diff --git a/lib/app/request.js b/lib/app/request.js index 1c43068d54..19c0c40aca 100644 --- a/lib/app/request.js +++ b/lib/app/request.js @@ -146,18 +146,6 @@ module.exports = function request( /* address, body, cb */ ) { // To kick things off, pass `req` and `res` to the Sails router sails.router.route(req, res); - // Write client request body to the simulated `req` (IncomingMessage) - // Req stream ends automatically if this is a GET or HEAD request - // - no need to do it again - if (opts.method !== 'GET' && opts.method !== 'HEAD' && opts.method !== 'DELETE') { - // Only write the body if there IS a body. - if (opts.body) { - req.write(opts.body); - } - req.end(); - } - - // Return clientRes stream return clientRes; } diff --git a/lib/hooks/sockets/lib/interpreter/interpret.js b/lib/hooks/sockets/lib/interpreter/interpret.js index 5333e08271..290c0b53f3 100644 --- a/lib/hooks/sockets/lib/interpreter/interpret.js +++ b/lib/hooks/sockets/lib/interpreter/interpret.js @@ -561,17 +561,6 @@ module.exports = function (sails) { res: res }); - // Pump client request body to the IncomingMessage stream (req) - // Req stream ends automatically if this is a GET or HEAD or DELETE request - // (since there is no request body in that case) - if (req.method !== 'GET' && req.method !== 'HEAD' && req.method !== 'DELETE') { - // Only write the body if there IS a body. - if (req.body) { - req.write(req.body); - } - req.end(); - } - }); diff --git a/lib/router/req.js b/lib/router/req.js index 494f8142b6..481b493993 100644 --- a/lib/router/req.js +++ b/lib/router/req.js @@ -40,23 +40,24 @@ module.exports = function buildRequest (_req) { }); } - // Create a mock request stream. + // Create a mock IncomingMessage stream. req = new MockReq({ method: _req && _req.method || 'GET', headers: _req && _req.headers || {}, url: _req && _req.url }); - // // If not, pump client request body to the IncomingMessage stream (req) - // // Req stream ends automatically if this is a GET or HEAD or DELETE request - // // (since there is no request body in that case) - // if (req.method !== 'GET' && req.method !== 'HEAD' && req.method !== 'DELETE') { - // // Only write the body if there IS a body. - // if (req.body) { - // req.write(req.body); - // } - // req.end(); - // } + // Now pump client request body to the mock IncomingMessage stream (req) + // Req stream ends automatically if this is a GET or HEAD or DELETE request + // (since there is no request body in that case) so no need to do it again. + if (req.method !== 'GET' && req.method !== 'HEAD' && req.method !== 'DELETE') { + + // Only write the body if there IS a body. + if (req.body) { + req.write(req.body); + } + req.end(); + } } function FakeSession() { From 2011596c101d7f84dedd2912885b690c56419cce Mon Sep 17 00:00:00 2001 From: Mike McNeil Date: Wed, 10 Dec 2014 18:21:18 -0600 Subject: [PATCH 040/235] Update comments on router.route() --- lib/router/index.js | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/lib/router/index.js b/lib/router/index.js index 2f3506027f..a4f83dad30 100644 --- a/lib/router/index.js +++ b/lib/router/index.js @@ -114,19 +114,20 @@ Router.prototype.load = function(cb) { /** - * `sails.router.route(req, res, next)` + * `sails.router.route(partialReq, partialRes)` * - * Routes the specified request using the built-in router. + * Interpret the specified (usually partial) request and response objects into + * streams with all of the expected methods, then routes the fully-formed request + * using the built-in private router. Useful for creating virtual request/response + * streams from non-HTTP sources, like Socket.io or unit tests. * - * NOTE: this should only be used if the request handler does not have its own router. - * (this approach also dramatically simplifies unit testing!) + * This method is not always helpful-- it is not called for HTTP requests, for instance, + * since the true HTTP req/res streams already exist. In that case, at lift-time, Sails + * calls `router:bind`, which loads Sails' routes as normal middleware/routes in the http hook. + * stack will run as usual. * - * The optimal behavior for Express, for instance, is to listen to `router:bind` - * and use the built-in router at lift-time, whereas Socket.io needs to use the - * `router:request` event to simulate a connect-style router since it - * can't bind dynamic routes ahead of time. - * - * By default, params and IO methods like res.send() are noops that should be overridden. + * On the other hand, Socket.io needs to use this method (i.e. the `router:request` event) + * to simulate a connect-style router since it can't bind dynamic routes ahead of time. * * Keep in mind that, if `route` is not used, the implementing server is responsible * for routing to Sails' default `next(foo)` handler. From e7ba5213ffd0762c2bf6ce6128fcddd9d0f005f2 Mon Sep 17 00:00:00 2001 From: Mike McNeil Date: Wed, 10 Dec 2014 18:40:14 -0600 Subject: [PATCH 041/235] Added some safety measures to vrouter, as well as a default charset of utf8. --- lib/router/res.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/router/res.js b/lib/router/res.js index fa95d56f5e..8c9d0393c4 100644 --- a/lib/router/res.js +++ b/lib/router/res.js @@ -22,6 +22,8 @@ module.exports = function buildResponse (req, _res) { var res; + // If `_res` appears to be a stream (duck-typing), then don't try + // and turn it into a mock stream again. if (typeof _res === 'object' && _res.end) { res = _res; } @@ -41,8 +43,11 @@ module.exports = function buildResponse (req, _res) { res.send = res.send || function send_shim () { var args = normalizeResArgs(arguments); + // Ensure charset is set + res.charset = res.charset || 'utf-8'; + if (!res.end || !res.write) { - return res._cb(); + return res._cb && res._cb(); } else { res.statusCode = args.statusCode || res.statusCode || 200; From 1897a17737113b97d86f875de61332a097c7dba3 Mon Sep 17 00:00:00 2001 From: Mike McNeil Date: Wed, 10 Dec 2014 18:45:40 -0600 Subject: [PATCH 042/235] Added comment explaining res.header code in 'res.js' --- lib/router/res.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/router/res.js b/lib/router/res.js index 8c9d0393c4..d4bf4b61c9 100644 --- a/lib/router/res.js +++ b/lib/router/res.js @@ -30,6 +30,8 @@ module.exports = function buildResponse (req, _res) { else { res = new MockRes(); } + + // Ensure res.headers and res.locals exist. res = _.merge(res, {locals: {}, headers: {}}, _res); From 2051f55e25b731f91f4f712aef4b8234bc750e82 Mon Sep 17 00:00:00 2001 From: Mike McNeil Date: Wed, 10 Dec 2014 19:00:55 -0600 Subject: [PATCH 043/235] Prevent responding more than once. --- lib/router/res.js | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/lib/router/res.js b/lib/router/res.js index d4bf4b61c9..a935753792 100644 --- a/lib/router/res.js +++ b/lib/router/res.js @@ -45,6 +45,19 @@ module.exports = function buildResponse (req, _res) { res.send = res.send || function send_shim () { var args = normalizeResArgs(arguments); + // Don't allow users to respond/redirect more than once per request + try { + onlyAllowOneResponse(res); + } + catch (e) { + if (req._sails && req._sails.log && req._sails.log.error) { + req._sails.log.error(e); + return; + } + console.error(e); + return; + } + // Ensure charset is set res.charset = res.charset || 'utf-8'; @@ -52,6 +65,9 @@ module.exports = function buildResponse (req, _res) { return res._cb && res._cb(); } else { + + // Ensure statusCode is set + // (override `this.statusCode` if `statusCode` argument specified) res.statusCode = args.statusCode || res.statusCode || 200; if (args.other) { @@ -135,3 +151,18 @@ function normalizeResArgs( args ) { other: args[0] }; } + + +/** + * NOTE: ALL RESPONSES (INCLUDING REDIRECTS) ARE PREVENTED ONCE THE RESPONSE HAS BEEN SENT!! + * Even though this is not strictly required with sockets, since res.redirect() + * is an HTTP-oriented method from Express, it's important to maintain consistency. + * + * @api private + */ +function onlyAllowOneResponse (res) { + if (res._virtualResponseStarted) { + throw new Error('Cannot write to response more than once'); + } + res._virtualResponseStarted = true; +} From fa4d4ecb86dbb783814c3112293fe9dce8642fe2 Mon Sep 17 00:00:00 2001 From: Mike McNeil Date: Wed, 10 Dec 2014 20:30:08 -0600 Subject: [PATCH 044/235] Allow for a '_clientRes' stream to be suppied to router.route(). Provides a cleaner API for usage of the core router by many different hooks, and also enables much simpler unit testing. --- lib/app/request.js | 49 +++++++++++---------- lib/router/index.js | 9 ++-- lib/router/req.js | 8 +++- lib/router/res.js | 103 +++++++++++++++++++++++++++++++++++--------- 4 files changed, 119 insertions(+), 50 deletions(-) diff --git a/lib/app/request.js b/lib/app/request.js index 19c0c40aca..db6f56b254 100644 --- a/lib/app/request.js +++ b/lib/app/request.js @@ -5,8 +5,6 @@ var _ = require('lodash'); var util = require('util'); var QS = require('querystring'); -var buildReq = require('../router/req'); -var buildRes = require('../router/res'); var Transform = require('stream').Transform; @@ -82,32 +80,32 @@ module.exports = function request( /* address, body, cb */ ) { } - // TODO: return Deferred - return _requestHelper({ - method: method, - url: url, - body: body - }, cb); + // // TODO: return Deferred + // return _requestHelper({ + // method: method, + // url: url, + // body: body + // }, cb); - function _requestHelper(opts, cb) { + // function _requestHelper(opts, cb) { - opts.method = opts.method && opts.method.toUpperCase() || 'GET'; - opts.headers = opts.headers || {}; + // opts.method = opts.method && opts.method.toUpperCase() || 'GET'; + // opts.headers = opts.headers || {}; // Build HTTP Server Request - var req = buildReq(opts, {}); + // var req = buildReq(opts, {}); // Build HTTP Server Response - var res = buildRes(req, {}); + // var res = buildRes(opts, {}); // Build HTTP Client Response stream var clientRes = new MockClientResponse(); clientRes.on('finish', function() { - // Status code and headers - clientRes.headers = res.headers; - clientRes.statusCode = res.statusCode; + // console.log('* * * * * * * * * * '); + // console.log('clientRes finished!'); + // console.log('* * * * * * * * * * '); // Only dump the buffer if a callback was supplied if (cb) { @@ -134,21 +132,26 @@ module.exports = function request( /* address, body, cb */ ) { } }); clientRes.on('error', function(err) { + // console.log('* * * * * * * * * * '); + // console.log('clientRes errored!!!!!!!!!'); + // console.log('* * * * * * * * * * '); err = err || new Error('Error on response stream'); if (cb) return cb(err); else return clientRes.emit('error', err); }); - // Set up all things pushed to `res` on the server - // to be piped down the client response stream. - res.pipe(clientRes); - - // To kick things off, pass `req` and `res` to the Sails router - sails.router.route(req, res); + // To kick things off, pass `opts` (as req) and `res` to the Sails router + sails.router.route({ + method: method, + url: url, + body: body + }, { + _clientRes: clientRes + }); // Return clientRes stream return clientRes; - } + // } }; diff --git a/lib/router/index.js b/lib/router/index.js index a4f83dad30..e2544c0694 100644 --- a/lib/router/index.js +++ b/lib/router/index.js @@ -141,6 +141,9 @@ Router.prototype.route = function(req, res) { var sails = this.sails; var _privateRouter = this._privateRouter; + // Provide access to `sails` object + req._sails = req._sails || sails; + // Use base req and res definitions to ensure the specified // objects are at least ducktype-able as standard node HTTP // req and req streams. @@ -150,18 +153,12 @@ Router.prototype.route = function(req, res) { req = buildReq(req, res); res = buildRes(req, res); - // Provide access to `sails` object - req._sails = req._sails || sails; // Deprecation error: res._cb = function noRouteCbSpecified(err) { throw new Error('As of v0.10, `_cb()` shim is no longer supported in the Sails router.'); }; - // Track request start time - req._startTime = new Date(); - - // Run some basic middleware sails.log.silly('Handling virtual request :: Running virtual querystring parser...'); qsParser(req,res, function (err) { diff --git a/lib/router/req.js b/lib/router/req.js index 481b493993..29f5be7501 100644 --- a/lib/router/req.js +++ b/lib/router/req.js @@ -42,7 +42,7 @@ module.exports = function buildRequest (_req) { // Create a mock IncomingMessage stream. req = new MockReq({ - method: _req && _req.method || 'GET', + method: _req && (_.isString(_req.method) ? _req.method.toUpperCase() : 'GET'), headers: _req && _req.headers || {}, url: _req && _req.url }); @@ -60,6 +60,12 @@ module.exports = function buildRequest (_req) { } } + // Track request start time + req._startTime = new Date(); + + // TODO: Load the session + // TODO: add all the other methods in core + function FakeSession() { // TODO: mimic the session store impl in sockets hook // (all of this can drastically simplify that hook and consolidate diff --git a/lib/router/res.js b/lib/router/res.js index a935753792..66edfeddef 100644 --- a/lib/router/res.js +++ b/lib/router/res.js @@ -2,10 +2,11 @@ * Module dependencies */ var util = require('util'); +var Transform = require('stream').Transform; +var path = require('path'); var _ = require('lodash'); var MockRes = require('mock-res'); var fsx = require('fs-extra'); -var path = require('path'); /** @@ -31,16 +32,42 @@ module.exports = function buildResponse (req, _res) { res = new MockRes(); } + + // // Now that we're sure `res` is a Transform stream, we'll handle the two different + // // approaches which a user of the virtual request interpreter might have taken: + + // // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // // (1) Providing a Writable stream (`_clientRes`) + // // + // // If a `_clientRes` response Transform stream was provided, pipe `res` directly to it. + // if (res._clientRes) { + // res.pipe(res._clientRes); + // } + // // + // // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + + // // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // // (2) Providing a callback function (`_clientCallback`) + // // + // // If a `_clientCallback` function was provided, also pipe `res` into a + // // fake clientRes stream where the response `body` will be buffered. + // if (_res._clientCallback) { + + // // res.pipe(clientRes); + // // TODO: + // } + // // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + // Ensure res.headers and res.locals exist. res = _.merge(res, {locals: {}, headers: {}}, _res); - // res.status() res.status = res.status || function status_shim (statusCode) { res.statusCode = statusCode; }; - // res.send() res.send = res.send || function send_shim () { var args = normalizeResArgs(arguments); @@ -61,24 +88,54 @@ module.exports = function buildResponse (req, _res) { // Ensure charset is set res.charset = res.charset || 'utf-8'; - if (!res.end || !res.write) { - return res._cb && res._cb(); + // Ensure statusCode is set + // (override `this.statusCode` if `statusCode` argument specified) + res.statusCode = args.statusCode || res.statusCode || 200; + + + // Now that we're sure `res` is a Transform stream, we'll handle the two different + // approaches which a user of the virtual request interpreter might have taken: + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // (1) Providing a Writable stream (`_clientRes`) + // + // If a `_clientRes` response Transform stream was provided, pipe `res` directly to it. + if (res._clientRes) { + + // Set status code and headers on the `_clientRes` stream so they are accessible + // to the provider of that stream. + res._clientRes.headers = res.headers; + res._clientRes.statusCode = res.statusCode; + + res.pipe(res._clientRes); } - else { - - // Ensure statusCode is set - // (override `this.statusCode` if `statusCode` argument specified) - res.statusCode = args.statusCode || res.statusCode || 200; - - if (args.other) { - var toWrite = args.other; - if (typeof toWrite === 'object') { - toWrite = util.inspect(toWrite); - } - res.write(toWrite); + // + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // (2) Providing a callback function (`_clientCallback`) + // + // If a `_clientCallback` function was provided, also pipe `res` into a + // fake clientRes stream where the response `body` will be buffered. + if (res._clientCallback) { + + // res.pipe(clientRes); + // TODO: + } + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + // Write body to `res` stream if it exists + if (args.other) { + var toWrite = args.other; + if (typeof toWrite === 'object') { + toWrite = util.inspect(toWrite); } - res.end(); + res.write(toWrite); } + + // End the `res` stream. + res.end(); }; // res.json() @@ -104,7 +161,13 @@ module.exports = function buildResponse (req, _res) { cb = arguments[1]; locals = {}; } - if (!req._sails.renderView) return res.send('Cannot call res.render() - `views` hook is not enabled', 500); + + if (!req._sails) { + return res.send('Cannot call res.render() - `req._sails` was not attached', 500); + } + if (!req._sails.renderView) { + return res.send('Cannot call res.render() - `req._sails.renderView` was not attached (perhaps `views` hook is not enabled?)', 500); + } // TODO: // Instead of this shim, turn `sails.renderView` into something like @@ -112,7 +175,6 @@ module.exports = function buildResponse (req, _res) { return res.send(501,'Not implemented in core yet'); }; - return res; @@ -166,3 +228,4 @@ function onlyAllowOneResponse (res) { } res._virtualResponseStarted = true; } + From f824f23de5f45ee160dd8cf2cd6db3d0a751ce2a Mon Sep 17 00:00:00 2001 From: Mike McNeil Date: Thu, 11 Dec 2014 00:28:06 -0600 Subject: [PATCH 045/235] Pipe res to res._clientRes before res.send() is called to allow for streaming usage. --- lib/app/request.js | 124 +++++++++++++----------------- lib/router/bindDefaultHandlers.js | 8 ++ lib/router/res.js | 97 ++++++++++------------- 3 files changed, 100 insertions(+), 129 deletions(-) diff --git a/lib/app/request.js b/lib/app/request.js index db6f56b254..c13e58a167 100644 --- a/lib/app/request.js +++ b/lib/app/request.js @@ -80,78 +80,60 @@ module.exports = function request( /* address, body, cb */ ) { } - // // TODO: return Deferred - // return _requestHelper({ - // method: method, - // url: url, - // body: body - // }, cb); - - // function _requestHelper(opts, cb) { - - // opts.method = opts.method && opts.method.toUpperCase() || 'GET'; - // opts.headers = opts.headers || {}; - - // Build HTTP Server Request - // var req = buildReq(opts, {}); - - // Build HTTP Server Response - // var res = buildRes(opts, {}); - - - // Build HTTP Client Response stream - var clientRes = new MockClientResponse(); - clientRes.on('finish', function() { - - // console.log('* * * * * * * * * * '); - // console.log('clientRes finished!'); - // console.log('* * * * * * * * * * '); - - // Only dump the buffer if a callback was supplied - if (cb) { - clientRes.body = Buffer.concat(clientRes._readableState.buffer).toString(); - try { - clientRes.body = JSON.parse(clientRes.body); - } catch (e) {} - - // Don't include body if it is empty - if (!clientRes.body) delete clientRes.body; - - // If status code is indicative of an error, send the - // response body or status code as the first error argument. - if (clientRes.statusCode < 200 || clientRes.statusCode >= 400) { - var error = new Error(); - if (clientRes.body) {error.body = clientRes.body;} - error.status = clientRes.statusCode; - error.message = util.inspect(error.body || error.status); - return cb(error); - } - else { - return cb(null, clientRes, clientRes.body); - } + + // Build HTTP Client Response stream + var clientRes = new MockClientResponse(); + clientRes.on('finish', function() { + + // console.log('* * * * * * * * * * '); + // console.log('clientRes finished!'); + // console.log('* * * * * * * * * * '); + + // Only dump the buffer if a callback was supplied + if (cb) { + clientRes.body = Buffer.concat(clientRes._readableState.buffer).toString(); + try { + clientRes.body = JSON.parse(clientRes.body); + } catch (e) {} + + // Don't include body if it is empty + if (!clientRes.body) delete clientRes.body; + + // If status code is indicative of an error, send the + // response body or status code as the first error argument. + if (clientRes.statusCode < 200 || clientRes.statusCode >= 400) { + var error = new Error(); + if (clientRes.body) {error.body = clientRes.body;} + error.status = clientRes.statusCode; + error.message = util.inspect(error.body || error.status); + return cb(error); + } + else { + return cb(null, clientRes, clientRes.body); } - }); - clientRes.on('error', function(err) { - // console.log('* * * * * * * * * * '); - // console.log('clientRes errored!!!!!!!!!'); - // console.log('* * * * * * * * * * '); - err = err || new Error('Error on response stream'); - if (cb) return cb(err); - else return clientRes.emit('error', err); - }); - - // To kick things off, pass `opts` (as req) and `res` to the Sails router - sails.router.route({ - method: method, - url: url, - body: body - }, { - _clientRes: clientRes - }); - - // Return clientRes stream - return clientRes; - // } + } + }); + clientRes.on('error', function(err) { + // console.log('* * * * * * * * * * '); + // console.log('clientRes errored!!!!!!!!!'); + // console.log('* * * * * * * * * * '); + err = err || new Error('Error on response stream'); + if (cb) return cb(err); + else return clientRes.emit('error', err); + }); + + // To kick things off, pass `opts` (as req) and `res` to the Sails router + sails.router.route({ + method: method, + url: url, + body: body + }, { + _clientRes: clientRes + }); + + // Return clientRes stream + return clientRes; + }; diff --git a/lib/router/bindDefaultHandlers.js b/lib/router/bindDefaultHandlers.js index efb4aa8bea..2d8f925556 100644 --- a/lib/router/bindDefaultHandlers.js +++ b/lib/router/bindDefaultHandlers.js @@ -28,9 +28,15 @@ module.exports = function(sails) { */ 500: function(err, req, res) { + // console.log('* * * FIRED FINAL HANDLER (500) * * *'); + // console.log('args:',arguments); + // console.log('* * * * * *'); + // console.log(); + try { // Use error handler if it exists if (typeof res.negotiate === 'function') { + // console.log('res.negotiate()'); return res.negotiate(err); } } catch (e) {} @@ -41,6 +47,7 @@ module.exports = function(sails) { // Log a message and try to use `res.send` to respond. sails.log.error('Server Error:'); sails.log.error(err); + // console.log('catch-all.. log an error and send a 500'); if (process.env.NODE_ENV === 'production') return res.send(500); return res.send(500, err); } @@ -51,6 +58,7 @@ module.exports = function(sails) { // Emit an `abort` message on the request object. If an attached server is managing this request, // it should monitor for `abort` events and manage its private resources (e.g. TCP sockets) accordingly. catch (errorSendingResponse) { + // console.log('serious error occurred'); sails.log.error('But no response could be sent because another error occurred:'); sails.log.error(errorSendingResponse); } diff --git a/lib/router/res.js b/lib/router/res.js index 66edfeddef..658249a22e 100644 --- a/lib/router/res.js +++ b/lib/router/res.js @@ -33,35 +33,37 @@ module.exports = function buildResponse (req, _res) { } - // // Now that we're sure `res` is a Transform stream, we'll handle the two different - // // approaches which a user of the virtual request interpreter might have taken: - - // // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - // // (1) Providing a Writable stream (`_clientRes`) - // // - // // If a `_clientRes` response Transform stream was provided, pipe `res` directly to it. - // if (res._clientRes) { - // res.pipe(res._clientRes); - // } - // // - // // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - + // Ensure res.headers and res.locals exist. + // res = _.merge(res, {locals: {}, headers: {}}, _res); + res = _.extend(res, {locals: {}, headers: {}}); + res = _.extend(res, _res); - // // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - // // (2) Providing a callback function (`_clientCallback`) - // // - // // If a `_clientCallback` function was provided, also pipe `res` into a - // // fake clientRes stream where the response `body` will be buffered. - // if (_res._clientCallback) { + // Now that we're sure `res` is a Transform stream, we'll handle the two different + // approaches which a user of the virtual request interpreter might have taken: - // // res.pipe(clientRes); - // // TODO: - // } - // // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // (1) Providing a callback function (`_clientCallback`) + // + // If a `_clientCallback` function was provided, also pipe `res` into a + // fake clientRes stream where the response `body` will be buffered. + if (res._clientCallback) { + // TODO: + // Create a new stream as `res._clientRes` so we can use it below + // The stream should trigger the callback when it finishes or errors. - // Ensure res.headers and res.locals exist. - res = _.merge(res, {locals: {}, headers: {}}, _res); + } + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // (2) Providing a Writable stream (`_clientRes`) + // + // If a `_clientRes` response Transform stream was provided, pipe `res` directly to it. + if (res._clientRes) { + res.pipe(res._clientRes); + } + // + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // res.status() res.status = res.status || function status_shim (statusCode) { @@ -88,43 +90,13 @@ module.exports = function buildResponse (req, _res) { // Ensure charset is set res.charset = res.charset || 'utf-8'; + // Ensure headers are set + res.headers = res.headers || {}; + // Ensure statusCode is set // (override `this.statusCode` if `statusCode` argument specified) res.statusCode = args.statusCode || res.statusCode || 200; - - // Now that we're sure `res` is a Transform stream, we'll handle the two different - // approaches which a user of the virtual request interpreter might have taken: - - // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - // (1) Providing a Writable stream (`_clientRes`) - // - // If a `_clientRes` response Transform stream was provided, pipe `res` directly to it. - if (res._clientRes) { - - // Set status code and headers on the `_clientRes` stream so they are accessible - // to the provider of that stream. - res._clientRes.headers = res.headers; - res._clientRes.statusCode = res.statusCode; - - res.pipe(res._clientRes); - } - // - // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - - - // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - // (2) Providing a callback function (`_clientCallback`) - // - // If a `_clientCallback` function was provided, also pipe `res` into a - // fake clientRes stream where the response `body` will be buffered. - if (res._clientCallback) { - - // res.pipe(clientRes); - // TODO: - } - // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - // Write body to `res` stream if it exists if (args.other) { var toWrite = args.other; @@ -134,6 +106,15 @@ module.exports = function buildResponse (req, _res) { res.write(toWrite); } + + // Set status code and headers on the `_clientRes` stream so they are accessible + // to the provider of that stream. + // (this has to happen in `send()` because the code/headers might have changed) + if (res._clientRes) { + res._clientRes.headers = res.headers; + res._clientRes.statusCode = res.statusCode; + } + // End the `res` stream. res.end(); }; From fe6386d5a14bbed72990a211359a0942b4255140 Mon Sep 17 00:00:00 2001 From: Mike McNeil Date: Thu, 11 Dec 2014 00:37:37 -0600 Subject: [PATCH 046/235] Remove logs --- lib/app/request.js | 7 ------- 1 file changed, 7 deletions(-) diff --git a/lib/app/request.js b/lib/app/request.js index c13e58a167..e920a6eda7 100644 --- a/lib/app/request.js +++ b/lib/app/request.js @@ -85,10 +85,6 @@ module.exports = function request( /* address, body, cb */ ) { var clientRes = new MockClientResponse(); clientRes.on('finish', function() { - // console.log('* * * * * * * * * * '); - // console.log('clientRes finished!'); - // console.log('* * * * * * * * * * '); - // Only dump the buffer if a callback was supplied if (cb) { clientRes.body = Buffer.concat(clientRes._readableState.buffer).toString(); @@ -114,9 +110,6 @@ module.exports = function request( /* address, body, cb */ ) { } }); clientRes.on('error', function(err) { - // console.log('* * * * * * * * * * '); - // console.log('clientRes errored!!!!!!!!!'); - // console.log('* * * * * * * * * * '); err = err || new Error('Error on response stream'); if (cb) return cb(err); else return clientRes.emit('error', err); From d697eda69eeb354c67c21e6bb8c95d2f0c01f44c Mon Sep 17 00:00:00 2001 From: Mike McNeil Date: Thu, 11 Dec 2014 09:48:07 -0600 Subject: [PATCH 047/235] Support _clientCallback in core for easier integration w/ web-protocol-related hooks --- lib/router/index.js | 1 + lib/router/res.js | 36 ++++++++++++++++++++++++++++++++++-- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/lib/router/index.js b/lib/router/index.js index e2544c0694..4321d3efe6 100644 --- a/lib/router/index.js +++ b/lib/router/index.js @@ -159,6 +159,7 @@ Router.prototype.route = function(req, res) { throw new Error('As of v0.10, `_cb()` shim is no longer supported in the Sails router.'); }; + // Run some basic middleware sails.log.silly('Handling virtual request :: Running virtual querystring parser...'); qsParser(req,res, function (err) { diff --git a/lib/router/res.js b/lib/router/res.js index 658249a22e..a6d0886b5e 100644 --- a/lib/router/res.js +++ b/lib/router/res.js @@ -48,9 +48,30 @@ module.exports = function buildResponse (req, _res) { // fake clientRes stream where the response `body` will be buffered. if (res._clientCallback) { - // TODO: - // Create a new stream as `res._clientRes` so we can use it below + // Build `res._clientRes` if one was not provided + if (!res._clientRes) { + res._clientRes = new MockClientResponse(); + } + // The stream should trigger the callback when it finishes or errors. + res._clientRes.on('finish', function() { + + res._clientRes.body = Buffer.concat(res._clientRes._readableState.buffer).toString(); + try { + res._clientRes.body = JSON.parse(res._clientRes.body); + } catch (e) {} + + // Don't include body if it is empty + if (!res._clientRes.body) delete res._clientRes.body; + + return res._clientCallback(res._clientRes); + }); + res._clientRes.on('error', function(err) { + err = err || new Error('Error on response stream'); + res._clientRes.statusCode = 500; + res._clientRes.body = err; + return res._clientCallback(res._clientRes); + }); } // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * @@ -210,3 +231,14 @@ function onlyAllowOneResponse (res) { res._virtualResponseStarted = true; } + +// The constructor for clientRes stream +// (just a normal transform stream) +function MockClientResponse() { + Transform.call(this); +} +util.inherits(MockClientResponse, Transform); +MockClientResponse.prototype._transform = function(chunk, encoding, next) { + this.push(chunk); + next(); +}; From 36d01a94a14973419128d73be94abb1bf4699296 Mon Sep 17 00:00:00 2001 From: The Gitter Badger Date: Thu, 11 Dec 2014 17:08:13 +0000 Subject: [PATCH 048/235] Added Gitter badge --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index d0642319ec..42dc3bed87 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,8 @@ ### [Website](http://sailsjs.org/)   [Getting Started](http://sailsjs.org/#!getStarted)   [Docs (v0.9)](http://sailsjs.org/#!documentation)   [Submit Issue](https://github.com/balderdashy/sails/search?q=&type=Issues) +[![Gitter](https://badges.gitter.im/Join Chat.svg)](https://gitter.im/balderdashy/sails?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) + Sails.js is a web framework that makes it easy to build custom, enterprise-grade Node.js apps. It is designed to resemble the MVC architecture from frameworks like Ruby on Rails, but with support for the more modern, data-oriented style of web app development. It's especially good for building realtime features like chat. From dfaba709ebfb5fcd7669dc5b16eee643b6a29f94 Mon Sep 17 00:00:00 2001 From: Cody Stoltman Date: Thu, 11 Dec 2014 12:43:05 -0600 Subject: [PATCH 049/235] don't pass status code in to res methods, use .status to remove deprecation messages --- lib/hooks/responses/index.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/hooks/responses/index.js b/lib/hooks/responses/index.js index 527bb42247..35223bfea9 100644 --- a/lib/hooks/responses/index.js +++ b/lib/hooks/responses/index.js @@ -136,9 +136,9 @@ module.exports = function(sails) { // } if (process.env.NODE_ENV === 'production') { - return res.send(statusCode || 500, err); + return res.status(statusCode || 500).send(err); } - return res.send(statusCode || 500, err); + return res.status(statusCode || 500).send(err); }; } }, @@ -206,7 +206,7 @@ function _mixin_jsonx(req, res) { // Send conventional status message if no data was provided // (see http://expressjs.com/api.html#res.send) if (_.isUndefined(data)) { - return res.send(res.statusCode); + return res.status(res.statusCode).send(); } else if (typeof data !== 'object') { // (note that this guard includes arrays) From 7522dd1f6a8b59f28ef6b780354765a6621a0ea6 Mon Sep 17 00:00:00 2001 From: Mike McNeil Date: Fri, 12 Dec 2014 10:08:29 -0600 Subject: [PATCH 050/235] Remove sockets hook and use external dep. --- lib/app/configuration/default-hooks.js | 28 + lib/app/configuration/defaultHooks.js | 28 - lib/app/configuration/index.js | 14 +- lib/hooks/sockets/README.md | 18 - lib/hooks/sockets/index.js | 1 - lib/hooks/sockets/lib/authorization.js | 137 ---- lib/hooks/sockets/lib/connection.js | 112 --- lib/hooks/sockets/lib/getSDKMetadata.js | 27 - lib/hooks/sockets/lib/index.js | 293 -------- .../sockets/lib/interpreter/ResStream.js | 31 - lib/hooks/sockets/lib/interpreter/getVerb.js | 45 -- .../sockets/lib/interpreter/interpret.js | 690 ------------------ .../lib/interpreter/saveSessionAndThen.js | 33 - lib/hooks/sockets/lib/loadSocketIO.js | 179 ----- 14 files changed, 39 insertions(+), 1597 deletions(-) create mode 100644 lib/app/configuration/default-hooks.js delete mode 100644 lib/app/configuration/defaultHooks.js delete mode 100644 lib/hooks/sockets/README.md delete mode 100644 lib/hooks/sockets/index.js delete mode 100644 lib/hooks/sockets/lib/authorization.js delete mode 100644 lib/hooks/sockets/lib/connection.js delete mode 100644 lib/hooks/sockets/lib/getSDKMetadata.js delete mode 100644 lib/hooks/sockets/lib/index.js delete mode 100644 lib/hooks/sockets/lib/interpreter/ResStream.js delete mode 100644 lib/hooks/sockets/lib/interpreter/getVerb.js delete mode 100644 lib/hooks/sockets/lib/interpreter/interpret.js delete mode 100644 lib/hooks/sockets/lib/interpreter/saveSessionAndThen.js delete mode 100644 lib/hooks/sockets/lib/loadSocketIO.js diff --git a/lib/app/configuration/default-hooks.js b/lib/app/configuration/default-hooks.js new file mode 100644 index 0000000000..acf8e5fb8c --- /dev/null +++ b/lib/app/configuration/default-hooks.js @@ -0,0 +1,28 @@ +/** + * Default hooks + * (order matters) + * TODO: make order _not_ matter (it pretty much doesn't already b/c events- but for a few core hooks it still does) + */ + +module.exports = { + 'moduleloader': true, + 'logger': true, + 'request': true, + 'orm': true, + 'views': true, + 'blueprints': true, + 'responses': true, + 'controllers': true, + 'sockets': require('sails-hook-sockets'), + 'pubsub': true, + 'policies': true, + 'services': true, + 'csrf': true, + 'cors': true, + 'i18n': true, + 'userconfig': true, + 'session': true, + 'grunt': true, + 'http': true, + 'userhooks': true +}; diff --git a/lib/app/configuration/defaultHooks.js b/lib/app/configuration/defaultHooks.js deleted file mode 100644 index e297a470d0..0000000000 --- a/lib/app/configuration/defaultHooks.js +++ /dev/null @@ -1,28 +0,0 @@ -/** - * Default hooks - * (order matters) - * TODO: make order _not_ matter - */ - -module.exports = [ - 'moduleloader', - 'logger', - 'request', - 'orm', - 'views', - 'blueprints', - 'responses', - 'controllers', - 'sockets', - 'pubsub', - 'policies', - 'services', - 'csrf', - 'cors', - 'i18n', - 'userconfig', - 'session', - 'grunt', - 'http', - 'userhooks' -]; diff --git a/lib/app/configuration/index.js b/lib/app/configuration/index.js index dcb98b2b67..eae4cbc99b 100644 --- a/lib/app/configuration/index.js +++ b/lib/app/configuration/index.js @@ -5,7 +5,7 @@ var _ = require('lodash'); var path = require('path'); var fs = require('fs'); -var DEFAULT_HOOKS = require('./defaultHooks'); +var DEFAULT_HOOKS = require('./default-hooks'); module.exports = function(sails) { @@ -50,8 +50,16 @@ module.exports = function(sails) { // Default hooks // TODO: remove hooks from config to avoid confusion // (because you can't configure hooks in `userconfig`-- only in `overrides`) - hooks: _.reduce(DEFAULT_HOOKS, function (memo, hookName) { - memo[hookName] = require('../../hooks/'+hookName); + hooks: _.reduce(DEFAULT_HOOKS, function (memo, hookName, hookDef) { + + // if `true`, the hook is bundled in the Sails core module + // (look in the `lib/hooks` folder in core) + if (hookDef === true) { + memo[hookName] = require('../../hooks/'+hookName); + } + else { + memo[hookName] = hookDef; + } return memo; }, {}) || {}, diff --git a/lib/hooks/sockets/README.md b/lib/hooks/sockets/README.md deleted file mode 100644 index b7a442df13..0000000000 --- a/lib/hooks/sockets/README.md +++ /dev/null @@ -1,18 +0,0 @@ -# sockets (Core Hook) - -## Status - -> ##### Stability: [2](http://nodejs.org/api/documentation.html#documentation_stability_index) - Unstable - - -## Purpose - -This hook's responsibilities are: - -> TODO - - -## FAQ - -> If you have a question that isn't covered here, please feel free to send a PR adding it to this section. -> If it's a good question, we'll merge it, even if you don't have the answer :) diff --git a/lib/hooks/sockets/index.js b/lib/hooks/sockets/index.js deleted file mode 100644 index bb0a047c4f..0000000000 --- a/lib/hooks/sockets/index.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require('./lib'); diff --git a/lib/hooks/sockets/lib/authorization.js b/lib/hooks/sockets/lib/authorization.js deleted file mode 100644 index eb36ab73fa..0000000000 --- a/lib/hooks/sockets/lib/authorization.js +++ /dev/null @@ -1,137 +0,0 @@ -module.exports = function(sails) { - - - /** - * Module dependencies. - */ - - var util = require('util'); - var parseSignedCookie = require('cookie-parser').signedCookie; - var cookie = require('express/node_modules/cookie'); - var ConnectSession = require('express/node_modules/connect').middleware.session.Session; - var getSDKMetadata = require('./getSDKMetadata'); - - - - /** - * Fired after a socket is connected - */ - - return function socketAttemptingToConnect (handshake, accept) { - - // If a cookie override was provided in the query string, use it. - // (e.g. ?cookie=sails.sid=a4g8dsgajsdgadsgasd) - if (handshake.query.cookie) { - handshake.headers.cookie = handshake.query.cookie; - } - - var sdk = getSDKMetadata(handshake); - sails.log.verbose(util.format('%s client (v%s) is trying to connect a socket...', sdk.platform, sdk.version)); - - var TROUBLESHOOTING = - 'Perhaps you have an old browser tab open? In that case, you can ignore this warning.'+'\n'+ - 'Otherwise, are you are trying to access your Sails.js ' + '\n'+ - 'server from a socket.io javascript client hosted on a 3rd party domain? ' + '\n'+ - ' *-> You can override the cookie for a user entirely by setting ?cookie=... in the querystring of '+ '\n'+ - 'your socket.io connection url on the client.'+ '\n'+ - ' *-> You can send a JSONP request first from the javascript client on the other domain '+ '\n'+ - 'to the Sails.js server to get the cookie first, then connect the socket.'+ '\n'+ - ' *-> For complete customizability, to override the built-in session assignment logic in Sails '+ '\n'+ - 'for socket.io requests, you can override socket.io\'s `authorization` logic with your own function '+ '\n'+ - 'in `config/sockets.js`. ' + '\n'+ - 'Or disable authorization for incoming socket connection requests entirely by setting `authorization: false`.'+'\n'; - - - // Parse and decrypt cookie and save it in the handshake - if (!handshake.headers.cookie) { - return socketConnectionError(accept, - 'Cannot load session for an incoming socket.io connection... ' + '\n'+ - 'No cookie was sent!\n'+ - TROUBLESHOOTING, - 'Cannot load session. No cookie transmitted.' - ); - } - - // Decrypt cookie into session id using session secret - // Maintain sessionID in socket handshake so that the session - // can be queried before processing each incoming message from this - // socket in the future. - try { - handshake.cookie = cookie.parse(handshake.headers.cookie); - handshake.sessionID = parseSignedCookie(handshake.cookie[sails.config.session.key], sails.config.session.secret); - } - catch (e) { - return socketConnectionError(accept, - 'Unable to parse the cookie that was transmitted for an incoming socket.io connect request:\n' + - util.inspect(e) + '\n' + TROUBLESHOOTING, - 'Cannot load session. Cookie could not be parsed.' - ); - } - - - // Look up this socket's session id in the Connect session store - // and see if we already have a record of 'em. - sails.session.get(handshake.sessionID, function(err, session) { - - // An error occurred, so refuse the connection - if(err) { - return socketConnectionError(accept, - 'Error loading session during socket connection! \n' + err, - 'Error loading session.'); - } - - // Cookie is present (there is a session id), but it doesn't - // correspond to a known session in the session store. - // So generate a new, blank session. - else if (!session) { - handshake.session = new ConnectSession(handshake, { - cookie: { - // Prevent access from client-side javascript - httpOnly: true - } - }); - sails.log.verbose("Generated new session for socket....", handshake); - - // TO_TEST: - // do we need to set handshake.sessionID with the id of the new session? - - // TO_TEST: - // do we need to revoke/replace the cookie as well? - // how can we do that w/ socket.io? - // can we access the response headers in the http UPGRADE response? - // or should we just clear the cookie from the handshake and call it good? - // e.g - // var date = new Date(); - // date.setTime(date.getTime()+(days*24*60*60*1000)); - // var expires = "; expires="+date.toGMTString(); - // handshake.headers.cookie = name+"="+value+expires+"; path=/"; - - accept(null, true); - } - - // Parsed cookie matches a known session- onward! - else { - - // Create a session object, passing our just-acquired session handshake - handshake.session = new ConnectSession(handshake, session); - sails.log.verbose("Connected socket to existing session...."); - accept(null, true); - } - }); - }; - - - /** - * Fired when an internal server error occurs while authorizing the socket - */ - - function socketConnectionError (accept, devMsg, prodMsg) { - var msg; - if (sails.config.environment === 'development') { - msg = devMsg; - } - else msg = prodMsg; - return accept(msg, false); - } - -}; diff --git a/lib/hooks/sockets/lib/connection.js b/lib/hooks/sockets/lib/connection.js deleted file mode 100644 index 34498087f3..0000000000 --- a/lib/hooks/sockets/lib/connection.js +++ /dev/null @@ -1,112 +0,0 @@ -/** - * Module dependencies - */ - - -module.exports = function(sails) { - - - /** - * Module dependencies. - */ - - var _ = require('lodash'); - var interpret = require('./interpreter/interpret')(sails); - - - - /** - * Fired after a socket is connected - */ - - return function onConnect (socket) { - sails.log.verbose('A socket.io client ('+ socket.id + ') connected successfully!'); - - // Listen for incoming socket requests on the `message` endpoint - // (legacy support for 0.8.x sails.io clients) - // mapRoute('message'); - - // Verb events - // (supports sails.io clients 0.9 and up) - mapRoute('get'); - mapRoute('post'); - mapRoute('put'); - mapRoute('delete'); - mapRoute('patch'); - mapRoute('options'); - mapRoute('head'); - - - // Configurable custom onConnect logic runs here: - //////////////////////////////////////////////////////////////////////// - - // (default: do nothing) - if (sails.config.sockets.onConnect) { - sails.session.fromSocket(socket, function sessionReady (err, session) { - - // If an error occurred loading the session, log what happened - if (err) { - sails.log.error(err); - return; - } - - // But continue on to run event handler either way - sails.config.sockets.onConnect(session, socket); - - }); - } - - // Configurable custom onConnect logic here - // (default: do nothing) - if (sails.config.sockets.onDisconnect) { - socket.on('disconnect', function onSocketDisconnect () { - sails.session.fromSocket(socket, function sessionReady (err, session) { - - // If an error occurred loading the session, log what happened - if (err) { - sails.log.error(err); - return; - } - - // But continue on to run event handler either way - sails.config.sockets.onDisconnect(session, socket); - }); - }); - } - - - - /** - * Map a socket message to the router using the request interpreter. - * - * @param {String} messageName [name of our socket endpoint] - */ - function mapRoute (messageName) { - - socket.on(messageName, function (incomingSailsIOSocketMsg, callback) { - sails.log.verbose('Routing message over socket: ', incomingSailsIOSocketMsg); - - // ??? - callback = callback || function noCallbackWarning(body, status) { - sails.log.error('No callback specified!'); - }; - - // Translate socket.io message to an Express-looking request - interpret(incomingSailsIOSocketMsg, callback, socket, messageName, function requestBuilt (err, requestCtx) { - - // If interpretation fails, log the error and do nothing - if (err) return sails.log.error(err); - - // Otherwise, emit the now-normalized request to the Sails router - // as a normal runtime request. - sails.emit('router:request', requestCtx.req, requestCtx.res); - }); - - }); - } - - - - }; - -}; diff --git a/lib/hooks/sockets/lib/getSDKMetadata.js b/lib/hooks/sockets/lib/getSDKMetadata.js deleted file mode 100644 index 32621c1871..0000000000 --- a/lib/hooks/sockets/lib/getSDKMetadata.js +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Accept the socket handshake, and use the querystring to infer the - * SDK platform, version, and programming language. - * - * @param {Object} handshake - * @return {Object} - */ -module.exports = function (handshake) { - var SDK_META_PARAMS = { - version: '__sails_io_sdk_version', - platform: '__sails_io_sdk_platform', - language: '__sails_io_sdk_language' - }; - - // If metadata about the SDK version of the connecting client was not - // provided in the version string, assume version = 0.9 - // (prior to 0.10, there wasn't any SDK versioning) - if (!handshake.query[SDK_META_PARAMS.version]) { - handshake.query[SDK_META_PARAMS.version] = '0.9.0'; - } - - return { - version: handshake.query[SDK_META_PARAMS.version], - platform: handshake.query[SDK_META_PARAMS.platform], - language: handshake.query[SDK_META_PARAMS.language] - }; -}; diff --git a/lib/hooks/sockets/lib/index.js b/lib/hooks/sockets/lib/index.js deleted file mode 100644 index 5750673e72..0000000000 --- a/lib/hooks/sockets/lib/index.js +++ /dev/null @@ -1,293 +0,0 @@ -module.exports = function(sails) { - - - /** - * Module dependencies. - */ - - var loadSocketIO = require('./loadSocketIO')(sails); - - - - /** - * Expose `sockets` hook definition - */ - - return { - - defaults: { - - // Self-awareness: the host the server *thinks it is* - // host: 'localhost', - - // Port to run this app on - port: 1337, - - // Users' SSL cert settings end up here - ssl: {}, - - // Socket.io configuration - sockets: { - - // Whether to run code which supports legacy usage for connected - // sockets running the v0.9 version of the socket client SDK (i.e. sails.io.js). - // Disabled in newly generated projects, but enabled as an implicit default. - 'backwardsCompatibilityFor0.9SocketClients': true, - - // Whether to expose a 'get /__getcookie' route with CORS support - // that sets a cookie (this is used by the sails.io.js socket client - // to get access to a 3rd party cookie and to enable sessions). - // - // Warning: Currently in this scenario, CORS settings apply to interpreted - // requests sent via a socket.io connetion that used this cookie to connect, - // even for non-browser clients! (e.g. iOS apps, toasters, node.js unit tests) - grant3rdPartyCookie: true, - - // Whether to send various aspects of an emulated HTTP response - // down in the JWR originated from each socket request. - // (doesn't affect direct socket.io usage-- only if you're using - // the Sails SDK and therefore the request interpreter) - sendResponseHeaders: true, - sendStatusCode: true, - - // Default behavior is a noop - // Code to run when a new socket connects - onConnect: function(session, socket) {}, - - // Default behavior is a noop - // Code to run when a socket disconnects - onDisconnect: function(session, socket) {}, - - // Setup adapter to use for socket.io MQ (pubsub) store - // (`undefined` indicates default memory store) - // NOTE: Default memory store will not work for clustered deployments with multiple instances. - adapter: undefined, - - // A array of allowed transport methods which the clients will try to use. - // The flashsocket transport is disabled by default - // You can enable flashsockets by adding 'flashsocket' to this list: - transports: [ - 'websocket', - 'htmlfile', - 'xhr-polling', - 'jsonp-polling' - ], - - // Match string representing the origins that are allowed to connect to the Socket.IO server - origins: '*:*', - - // Should we use heartbeats to check the health of Socket.IO connections? - heartbeats: true, - - // When client closes connection, the # of seconds to wait before attempting a reconnect. - // This value is sent to the client after a successful handshake. - 'close timeout': 60, - - // The # of seconds between heartbeats sent from the client to the server - // This value is sent to the client after a successful handshake. - 'heartbeat timeout': 60, - - // The max # of seconds to wait for an expcted heartbeat before declaring the pipe broken - // This number should be less than the `heartbeat timeout` - 'heartbeat interval': 25, - - // The maximum duration of one HTTP poll- - // if it exceeds this limit it will be closed. - 'polling duration': 20, - - // Enable the flash policy server if the flashsocket transport is enabled - 'flash policy server': false, - - // By default the Socket.IO client will check port 10843 on your server - // to see if flashsocket connections are allowed. - // The Adobe Flash Player normally uses 843 as default port, - // but Socket.io defaults to a non root port (10843) by default - // - // If you are using a hosting provider that doesn't allow you to start servers - // other than on port 80 or the provided port, and you still want to support flashsockets - // you can set the `flash policy port` to -1 - 'flash policy port': 10843, - - // Used by the HTTP transports. The Socket.IO server buffers HTTP request bodies up to this limit. - // This limit is not applied to websocket or flashsockets. - 'destroy buffer size': '10E7', - - // Do we need to destroy non-socket.io upgrade requests? - 'destroy upgrade': true, - - // Does Socket.IO need to serve the static resources like socket.io.js and WebSocketMain.swf etc. - 'browser client': true, - - // Cache the Socket.IO file generation in the memory of the process - // to speed up the serving of the static files. - 'browser client cache': true, - - // Does Socket.IO need to send a minified build of the static client script? - 'browser client minification': false, - - // Does Socket.IO need to send an ETag header for the static requests? - 'browser client etag': false, - - // Adds a Cache-Control: private, x-gzip-ok="", max-age=31536000 header to static requests, - // but only if the file is requested with a version number like /socket.io/socket.io.v0.9.9.js. - 'browser client expires': 315360000, - - // Does Socket.IO need to GZIP the static files? - // This process is only done once and the computed output is stored in memory. - // So we don't have to spawn a gzip process for each request. - 'browser client gzip': false, - - // A function that should serve all static handling, including socket.io.js et al. - 'browser client handler': false, - - // Meant to be used when running socket.io behind a proxy. - // Should be set to true when you want the location handshake to match the protocol of the origin. - // This fixes issues with terminating the SSL in front of Node - // and forcing location to think it's wss instead of ws. - 'match origin protocol': false, - - // Global authorization for Socket.IO access, - // this is called when the initial handshake is performed with the server. - // - // By default, Sails verifies that a valid cookie was sent with the upgrade request - // However, in the case of cross-domain requests, no cookies are sent for some transports, - // so sockets will fail to connect. You might also just want to allow anyone to connect w/o a cookie! - // - // To bypass this cookie check, you can set `authorization: false`, - // which will silently create an anonymous cookie+session for the user - // - // `authorization: true` indicates that Sails should use the built-in logic - // - // You can also use your own custom logic with: - // `authorization: function (data, accept) { ... }` - authorization: false, - - // Direct access to the socket.io MQ store config - // The 'adapter' property is the preferred method - // (`undefined` indicates that Sails should defer to the 'adapter' config) - store: undefined, - - // A logger instance that is used to output log information. - // (`undefined` indicates deferment to the main Sails log config) - logger: undefined, - - // The amount of detail that the server should output to the logger. - // (`undefined` indicates deferment to the main Sails log config) - 'log level': undefined, - - // Whether to color the log type when output to the logger. - // (`undefined` indicates deferment to the main Sails log config) - 'log colors': undefined, - - // A Static instance that is used to serve the socket.io client and its dependencies. - // (`undefined` indicates use default) - 'static': undefined, - - // The entry point where Socket.IO starts looking for incoming connections. - // This should be the same between the client and the server. - resource: '/socket.io' - - } - }, - - - configure: function() { - // onConnect must be valid function - if (sails.config.sockets.onConnect && typeof sails.config.sockets.onConnect !== 'function') { - throw new Error('Invalid `sails.config.sockets.onConnect`! Must be a function.'); - } - - // If one piece of the ssl config is specified, ensure the other required piece is there - if (sails.config.ssl && ( - sails.config.ssl.cert && !sails.config.ssl.key - ) || (!sails.config.ssl.cert && sails.config.ssl.key)) { - throw new Error('Invalid SSL config object! Must include cert and key!'); - } - }, - - - /** - * Initialize is fired first thing when the hook is loaded - * - * @api public - */ - - initialize: function(cb) { - - if (sails.config.hooks.http) { - - // If http hook is enabled, wait until the http server is configured - // before linking the socket server to it - sails.after('hook:http:loaded', function() { - sails.after('hook:session:loaded', function() { - loadSocketIO(cb); - }); - }); - - } else { - - // TODO: implement standalone socket server usage - var notImplementedError = - 'Socket server cannot be started without HTTP server because the feature ' + - 'has not been implemented yet!\n' + - 'For now, please reenable the `http` hook.'; - sails.log.error(notImplementedError); - throw new Error(notImplementedError); - - // If not, configure the socket server immediately - // loadSocketIO(cb); - - // // TODO: Start independent socket server as soon as sails is ready - // sails.on('ready', function () { - // // TODO - // }); - } - - }, - - routes: { - - before: { - - 'all /*': function addOriginHeader(req, res, next) { - if (req.isSocket) { - req.headers = req.headers || {}; - req.headers.origin = req.socket.handshake && req.socket.handshake.headers && req.socket.handshake.headers.origin; - } - return next(); - }, - - 'get /firehose': function firehose(req, res) { - if (!req.isSocket) { - sails.log.error("Cannot subscribe to firehose over HTTP! Firehose is for socket messages only."); - return; - } - if (process.env.NODE_ENV !== 'development') { - sails.log.warn('Warning: A client socket was just subscribed to the firehose, but the environment is set to `' + process.env.NODE_ENV + '`.' + - ' Firehose messages are only delivered in the development environment.'); - } - sails.log.silly("A client socket was just subscribed to the firehose."); - sails.sockets.subscribeToFirehose(req.socket); - res.send(200); - } - }, - - after: { - - 'get /__getcookie': function sendCookie(req, res, next) { - - // Allow this endpoint to be disabled by setting: - // sails.config.sockets.grant3rdPartyCookie = false; - if (!sails.config.sockets.grant3rdPartyCookie) { - return next(); - } - - res.send('_sailsIoJSConnect();'); - } - - } - - } - }; - -}; diff --git a/lib/hooks/sockets/lib/interpreter/ResStream.js b/lib/hooks/sockets/lib/interpreter/ResStream.js deleted file mode 100644 index 0e96e7bed7..0000000000 --- a/lib/hooks/sockets/lib/interpreter/ResStream.js +++ /dev/null @@ -1,31 +0,0 @@ - -/** - * Not fully supported yet- - * see https://github.com/nkzawa/socket.io-stream - */ - -module.exports = ResStream; - -// Set readable and writable in constructor. -// Inherit from base stream class. -function ResStream () { - this.writable = true; -} -require('util').inherits(ResStream, require('stream')); - -// Extract args to `write` and emit as `data` event. -// Optional callback -ResStream.prototype.write = function(str) { - // Fire 'data' event on socket - this.socket.emit('data', str); -}; - -// If err set, emit `error`, otherwise emit `end` event. -// Optional callback -ResStream.prototype.end = function(err) { - if (err) { - this.emit('error', err); - this.socket.emit('error', err); - } - else this.socket.emit('end'); -}; \ No newline at end of file diff --git a/lib/hooks/sockets/lib/interpreter/getVerb.js b/lib/hooks/sockets/lib/interpreter/getVerb.js deleted file mode 100644 index 9d16512b33..0000000000 --- a/lib/hooks/sockets/lib/interpreter/getVerb.js +++ /dev/null @@ -1,45 +0,0 @@ -/** - * Module dependenies - */ - -var _ = require('lodash'); - - -/** - * Get simulated HTTP method (aka "verb") for a given request object. - * (NOTE: the returned verb will be in all caps, like "GET" or "POST") - * - * @param {???} incomingSailsIORequest - * @param {String} messageName - * @return {String} the HTTP verb for this request, e.g. "HEAD" or "PATCH" or "DELETE" - */ -module.exports = function getVerb (incomingSailsIORequest, messageName) { - - // If messageName is not `message` (i.e. legacy 0.8), it must correspond - // to an HTTP method (this is how socket.io endpoints are set up >= 0.9.x) - if ( _.isString(messageName) && messageName.toLowerCase() !== 'message' ) { - return messageName.toUpperCase(); - } - - // try and parse the socket io data if it looks like JSON - var body; - if ( _.isString(socketIOData) ) { - try { - body = JSON.parse(socketIOData); - } catch(e) {} - } - - // Only try to use the socket io data if it's usable - if ( _.isObject(body) ) { - - if (_.isString(body.verb)) { - return body.verb.toUpperCase(); - } - - if (_.isString(body.method)) { - return body.method.toUpperCase(); - } - } - - return 'GET'; -}; \ No newline at end of file diff --git a/lib/hooks/sockets/lib/interpreter/interpret.js b/lib/hooks/sockets/lib/interpreter/interpret.js deleted file mode 100644 index 290c0b53f3..0000000000 --- a/lib/hooks/sockets/lib/interpreter/interpret.js +++ /dev/null @@ -1,690 +0,0 @@ -/** - * Module dependencies - */ -var buildReq = require('../../../../router/req'); -var buildRes = require('../../../../router/res'); - - -module.exports = function (sails) { - - - /** - * Module dependencies. - */ - - var util = require('sails-util'); - var ResStream = require('./ResStream'); - var getVerb = require('./getVerb'); - var saveSessionAndThen = require('./saveSessionAndThen'); - var getSDKMetadata = require('../getSDKMetadata'); - - - /** - * Module errors - */ - var Err = { - invalidRequestCallback: function (invalidFn) { - return new Error( - 'Invalid callback specified in socket request :: ' + - util.inspect(invalidFn) - ); - }, - invalidRedirect: function (location) { - return new Error( - '\n' + - 'res.redirect() :: [socket.io] ::' + - '\n' + - 'Cannot redirect socket to invalid location :: ' + - util.inspect(location) - ); - } - }; - - - /** - * Interpret an incoming socket.io "request" - * Emulates Express semantics by mocking request (`req`) and response (`res`) - * - * @param {[type]} socketReq [incoming sails.io-formatted socket msg] - * @param {[type]} socketIOCallback [an ack callback useful for sending a response back to the client] - * @param {[type]} socket [the socket that originated this request] - * @param {[type]} messageName [the name of the message] - * @param {Function} cb [called when request interpretation is complete] - */ - - return function interpretSocketReq (socketReq, socketIOCallback, socket, messageName, cb) { - - var msg; - - // If invalid callback function specified, freak out - if (socketIOCallback && !util.isFunction(socketIOCallback)) { - msg = 'Invalid socket request Could not be parse :: '+socketReq; - return sails.log.error(msg); - } - - // Parse request as JSON (or just use the object if we have one) - if (! util.isObject(socketReq) && util.isString(socketReq) ) { - try { - socketReq = JSON.parse(socketReq); - } catch (e) { - msg = 'Invalid socket request! The following JSON could not be parsed :: '+socketReq; - return emitError(msg); - } - } - - // If no URL specified, error out - if (!socketReq.url) { - msg = 'No url provided in request: '+socketReq; - return emitError(msg); - } - - if (!util.isString(socketReq.url)) { - msg = 'Invalid url provided in request: ' + socketReq.url; - return emitError(msg); - } - - - // Grab the metadata for the SDK - var sdk = getSDKMetadata(socket.handshake); - - // Attached data becomes simulated HTTP body (`req.body`) - // Allow `params` or `data` to be specified for backwards/sideways-compatibility. - var bodyParams = util.extend({}, socketReq.params || {}, socketReq.data || {}); - - // Get forwarded ip:port from x-forwarded-for header if IIS - var forwarded = socket.handshake.headers['x-forwarded-for']; - forwarded = forwarded && forwarded.split(':') || []; - - // Build request object - var req = { - - // TODO: grab actual transports from socket.io - transport: 'socket.io', - - method : getVerb(socketReq, messageName), - - protocol: 'ws', - - ip : forwarded[0] || socket.handshake.address && socket.handshake.address.address , - - port : forwarded[1] || socket.handshake.address && socket.handshake.address.port , - - url : socketReq.url, - - socket : socket, - - isSocket: true, - - // Request params (`req.params`) are automatically parsed from URL path by the private router - // query : queryParams || {}, - body : bodyParams || {}, - - // Lookup parameter - param: function(paramName) { - - var key, params = {}; - for (key in (req.params || {}) ) { - params[key] = req.params[key]; - } - for (key in (req.query || {}) ) { - params[key] = req.query[key]; - } - for (key in (req.body || {}) ) { - params[key] = req.body[key]; - } - - // Grab the value of the parameter from the appropriate place - // and return it - return params[paramName]; - }, - - // Allow optional headers - headers: util.defaults({ - host: sails.config.host - }, socketReq.headers || {}), - - }; - - - /** - * req.header( headerName, [defaultValue] ) - * - * Backwards compat. for Express 2.x - * http://expressjs.com/2x/guide.html#req.header() - * - * Looks up value of INCOMING request header called `headerName` - * - * @api deprecated - */ - req.header = function getHeader(headerName, defaultValue) { - var headerValue = req.headers[headerName]; - return (typeof headerValue === 'undefined') ? defaultValue : headerValue; - }; - - - - /** - * socket.join() - * https://github.com/LearnBoost/socket.io/wiki/Rooms - * - * Join the specified room (listen for messages/broadcasts to it) - * Associates the current socket - * - * @api public - * @alias req.listen() - * @alias req.subscribe() - */ - req.join = function (room) { - - // TODO: add support for optional callback (for use w/ redis) - return this.socket.join(roomName); - }; - req.subscribe = req.join; - req.listen = req.join; - - - - - - // Build response object as stream - var res = util.extend(new ResStream(), { - - /** - * http://nodejs.org/api/http.html#http_response_statuscode - * Equivalent to Node's status code for HTTP. - * - * @api private - */ - statusCode: null, - - /** - * http://expressjs.com/api.html#res.charset - * Assign the charset. - * - * @defaultsTo 'utf-8' - * @api public - */ - charset: 'utf-8' - - }); - - - /** - * Set status code - * - * http://expressjs.com/api.html#res.status - * - * @chainable - * @api public - */ - res.status = function setStatusCode (code) { - res.statusCode = code; - return res; - }; - - - /** - * Send a response if a callback was specified - * if no callback was specified, emit event over socket - * - * http://expressjs.com/api.html#res.send - * - * @api public - */ - res.send = saveSessionAndThen(req, sails, - function sendSimpleResponse ( /* [statusCode|body],[statusCode|body] */ ) { - var args = normalizeResArgs(arguments), - statusCode = args.statusCode, - body = args.other; - - // Don't allow users to respond/redirect more than once per request - onlyAllowOneResponse(res); - - // Ensure statusCode is set - // (override `this.statusCode` if `statusCode` argument specified) - this.statusCode = statusCode || this.statusCode || 200; - - // Ensure charset is set - this.charset = this.charset || 'utf-8'; - - - - // Modern behavior - // (builds a complete simulation of an HTTP response.) - if ( sdk.version === '0.10.0' ) { - - var responseCtx = { - body: body - }; - - // Allow headers and status code to be disabled to allow for squeezing - // out a little more performance when relevant (and reducing bandwidth usage). - // To achieve this, set `sails.config.sockets.sendResponseHeaders=false` and/or - // `sails.config.sockets.sendStatusCode=false`. - if ( typeof sails.config.sockets === 'object' ) { - if (sails.config.sockets.sendResponseHeaders) { - responseCtx.headers = res.headers; - } - if (sails.config.sockets.sendStatusCode) { - responseCtx.statusCode = res.statusCode; - } - } - - // Send down response. - socketIOCallback(responseCtx); - return res; - } - - // Backwards compat. for the 0.9.0 version of the sails.io browser SDK - // (triggers callback with ONLY the response body) - else { - socketIOCallback(body); - return res; - } - - }); - - - - /** - * Redirect to a different url - * - * @api public - */ - res.redirect = - saveSessionAndThen(req, sails, - function doRedirect ( /* [location|statusCode], [location|statusCode] */ ) { - var args = normalizeResArgs(arguments), - statusCode = args.statusCode, - location = args.other; - - // Don't allow users to respond/redirect more than once per request - onlyAllowOneResponse(res); - - // Ensure statusCode is set - res.statusCode = statusCode || res.statusCode || 302; - - // Prevent redirects to public URLs - var PUBLIC_URL = /^[^\/].+/; - if ( location.match( PUBLIC_URL ) ) { - return emitError( Err.invalidRedirect(location) ); - } - - // Set URL for redirect - req.url = location; - - // Simulate another request at the new url - sails.emit('router:request', req, res); - }); - - - - /** - * Send json response - * - * @api public - */ - res.json = function sendJSON ( /* [statusCode|obj],[statusCode|obj] */ ) { - var args = normalizeResArgs(arguments), - statusCode = args.statusCode, - obj = args.other; - - // TODO: use configured json replacer - // TODO: use configured json spaces - - var body = obj; - - // Modern behavior - // (don't stringify JSON- let socket.io take care of it) - if ( sdk.version === '0.10.0' ) { - return this.send(statusCode, body); - } - - // Backwards compat. for the 0.9.0 version of the sails.io browser SDK - // (safe-stringifies JSON) - else { - body = sails.util.stringify(obj); - if (!body) { - return sendError('res.json() :: Error stringifying JSON :: ' + obj, 500); - } - } - - - // send response - return this.send(statusCode, body); - }; - - - - /** - * There isn't really an equivalent for JSONP over sockets - * so we can just transparently defer to `res.json()` - * - * @api public - */ - res.jsonp = function sendJSONP ( /* [statusCode|obj],[statusCode|obj] */ ) { - return this.json.apply(this, arguments); - }; - - - /** - * res.header( headerName [,value] ) - * - * Backwards compat. for Express 2.x - * http://expressjs.com/2x/guide.html#res.header() - * - * Gets or sets value of OUTGOING response header. - * - * @api deprecated - */ - res.header = function getHeader(headerName, value) { - - // Sets `headerName` to `value` - if (value) { - return res.set(headerName, value); - } - - // `res.header(headerName)` - // Returns value of `headerName` - return res.get(headerName); - }; - - - /** - * res.set( headerName, value ) - * - * @param {[type]} headerName [description] - * @param {[type]} value [description] - */ - res.set = function (headerName, value) { - res.headers = res.headers || {}; - res.headers[headerName] = value; - return value; - }; - - /** - * res.get( headerName ) - * - * @param {[type]} headerName [description] - * @return {[type]} [description] - */ - res.get = function (headerName) { - return res.headers && res.headers[headerName]; - }; - - - /** - * http://expressjs.com/api.html#res.render - * http://expressjs.com/api.html#res.locals - * - * TODO: Built-in support for rendering view templates (use `consolidate`) - * TODO: Built-in support for locals - * TODO: Built-in support for partials - * TODO: Built-in support for layouts equivalent to the built-in ejs-locals support for HTTP requests - * - * @chainable - * @api unsupported - * @todo - */ - res.render = function renderViewOverSockets (view, options, fn) { - sendError( - 'You are trying to render a view (' + view + '), ' + - 'but Sails doesn\'t support rendering views over Socket.io... yet!\n' + - 'You might consider serving your HTML view normally, then fetching data with sockets ' + - 'in your client-side JavaScript.\n' + - 'If you didn\'t intend to serve a view here, you might look into content-negotiation\n' + - 'to handle AJAX/socket requests explictly, instead of `res.redirect()`/`res.view()`.' - ); - return res; - }; - - - /** - * Scoped local variables accesible from views - * see also: http://expressjs.com/api.html#res.locals - */ - res.locals = (new function Locals (){ - this.partial = function renderPartial () { - return sendError('View partials not implemented over socket.io.'); - }; - }()); - - /** - * Get or set the value of a local variable in the view - * - * Backwards compat. for Express 2.x - * http://expressjs.com/2x/guide.html#res.local() - * - * @chainable - * @api deprecated - */ - res.local = function setLocal (localName, value) { - // `res.local(localName)` - // Sets `localName` to `value` - if (value) { - res.locals[localName] = value; - return value; - } - - // `res.local(localName)` - // Returns value of `localName` - return res.locals[localName]; - }; - - - /** - * http://expressjs.com/api.html#res.format - * - * Performs content-negotiation on the request Accept header field when present. - * This method uses req.accepted, an array of acceptable types ordered by their quality values, - * otherwise the first callback is invoked. When no match is performed the server responds with - * 406 "Not Acceptable", or invokes the default callback. - * - * The Content-Type is set for you when a callback is selected, however you may alter this within - * the callback using res.set() or res.type() etc. - * - * @chainable - * @api unsupported - */ - res.format = todo('format'); - - - /** - * http://expressjs.com/api.html#res.download - * http://expressjs.com/api.html#res.attachment - * http://expressjs.com/api.html#res.sendfile - * - * Serving files is not part of the short-term roadmap for the socket interpreter. - * - * @chainable - * @api unsupported - */ - res.download = todo('download'); - res.sendfile = todo('sendfile'); - res.attachment = todo('attachment'); - - - // TODO: Implement support for other `res.*` methods from Express - res.contentType = todo('contentType'); - res.type = todo('type'); - res.links = todo('links'); - // res.header = todo('header'); - res.clearCookie = todo('clearCookie'); - res.signedCookie = todo('signedCookie'); - res.cookie = todo('cookie'); - - - - - /** - * Access to underlying socket - * - * @api public - */ - res.socket = socket; - - - /** - * Publish some data to a room - * - * @param {String} room - * @param {Object} data - * - * @api public - */ - res.broadcast = function broadcastMessage (room, data) { - req.socket.broadcast.to(room).json.send(data); - return res; - }; - - - // Populate req.session using shared session store - sails.session.fromSocket(req.socket, function sessionReady (err, session) { - if (err) return cb(err); - - // Provide access to session data as req.session - req.session = session; - - // Now streamify the things - req = buildReq(req,res); - res = buildRes(req,res); - - // Pipe response back to the socket.io callback - // TODO - - // Set request/response timeout - // TODO - - // Send newly constructed req and res objects back to router - cb(null, { - req: req, - res: res - }); - - }); - - - - - // Respond with a message indicating that the feature is not compatible with sockets - function notSupportedError() { - return sendError('Trying to invoke unsupported response method (`res.foo`) in response to a request from socket.io!'); - } - - // Return function which responds with a message indicating that the method - // is not yet implemented - function todo (method) { - return function () { - return sendError( - 'res.' + method + '() is not yet supported over socket.io. '+ - 'If you need this functionality, please don\'t hesitate to get involved!' - ); - }; - } - - // Respond with an error message - function sendError(errmsg, statusCode) { - sails.log.warn(errmsg); - res.json( statusCode || 500, { - error: errmsg - }); - } - - /** - * Send a low-level error back over the socket - * (useful in cases where basic interpreter plumbing is not working) - * - * Request callback function will NOT be triggered! - * Instead, an error message will be emitted. - */ - function emitError (error) { - - // TODO: implement best practice for socket.io error reporting - - // TODO: something like this..? - // e.g. - // socket.emit('sails__500', 'error'); - - // ******************************************************** - // WARNING - // - // This is a breaking change!! - // Do not implement until next minor release (0.10.0) - // - // Will require documentation of clear steps in changelog - // and some changes in bundled client-side SDK, i.e. - // assets/js/sails.io.js - // -AND- - // assets/linker/js/sails.io.js - // ******************************************************** - - //////////////////////////////////// - // But for now: - //////////////////////////////////// - - // Log error - sails.log.error(error); - - // If callback is invalid or non-existent: - if ( !util.isFunction(socketIOCallback) ) { - // do nothing... - return; - } - - // Otherwise just send the error directly to the callback... - socketIOCallback(error); - } - - - /** - * NOTE: ALL RESPONSES (INCLUDING REDIRECTS) ARE PREVENTED ONCE THE RESPONSE HAS BEEN SENT!! - * Even though this is not strictly required with sockets, since res.redirect() - * is an HTTP-oriented method from Express, it's important to maintain consistency. - * - * @api private - */ - function onlyAllowOneResponse () { - // TODO - return; - } - - /** - * As long as one of them is a number (i.e. a status code), - * allows a 2-nary method to be called with flip-flopped arguments: - * method( [statusCode|other], [statusCode|other] ) - * - * This avoids confusing errors & provides Express 2.x backwards compat. - * - * E.g. usage in res.send(): - * var args = normalizeResArgs.apply(this, arguments), - * body = args.other, - * statusCode = args.statusCode; - * - * @api private - */ - function normalizeResArgs( args ) { - - // Traditional usage: - // `method( other [,statusCode] )` - var isTraditionalUsage = - 'number' !== typeof args[0] && - ( !args[1] || 'number' === typeof args[1] ); - - if ( isTraditionalUsage ) { - return { - statusCode: args[1], - other: args[0] - }; - } - - // Explicit usage, i.e. Express 3: - // `method( statusCode [,other] )` - return { - statusCode: args[0], - other: args[1] - }; - } - }; - - -}; diff --git a/lib/hooks/sockets/lib/interpreter/saveSessionAndThen.js b/lib/hooks/sockets/lib/interpreter/saveSessionAndThen.js deleted file mode 100644 index 3d15657ae8..0000000000 --- a/lib/hooks/sockets/lib/interpreter/saveSessionAndThen.js +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Used to save session automatically when: - * + res.send() or res.json() is called - * + res.redirect() is called - * + TODO: res receives an 'end' event from a stream piped into it - * - * @param {Request} req - * @param {Sails} sails - * @param {Function} cb - * @return {Function} which persists session data, then triggers callback. - */ - -module.exports = function saveSessionAndThen(req, sails, cb) { - - return function saveSession () { - var ctx = this, - args = Array.prototype.slice.call(arguments); - var next = function (err) { - if (err) { - sails.log.error('Session could not be persisted:',err); - } - cb.apply(ctx,args); - }; - - // If we have a session on the request, save it - // Otherwise, process the callback on the next tick - if (req.session) { - req.session.save(next); - } else { - process.nextTick(next); - } - }; -}; diff --git a/lib/hooks/sockets/lib/loadSocketIO.js b/lib/hooks/sockets/lib/loadSocketIO.js deleted file mode 100644 index bf40d70d5f..0000000000 --- a/lib/hooks/sockets/lib/loadSocketIO.js +++ /dev/null @@ -1,179 +0,0 @@ -module.exports = function (sails) { - - /** - * Module dependencies. - */ - - var util = require( 'sails-util'); - var SocketServer = require('socket.io'); - var RedisStore = require('socket.io/lib/stores/redis'); - var Redis = require('socket.io/node_modules/redis'); - var Socket = { - authorization: require('./authorization')(sails), - connection: require('./connection')(sails) - }; - - - /** - * loadSocketIO() - * @param {Function} cb - * - * Prepare the nascent ws:// server (but don't listen for connections yet) - */ - - return function loadSocketIO (cb) { - sails.log.verbose('Configuring socket (ws://) server...'); - - var socketConfig = sails.config.sockets; - - // Socket.io server (WebSockets+polyfill to support Flash sockets, AJAX long polling, etc.) - var io = sails.io = sails.ws = - SocketServer.listen(sails.hooks.http.server, { - logger: { - info: function (){} - } - }); - - // If logger option not set, use the default Sails logger config - if (!socketConfig.logger) { - var logLevels = { - 'silent': 0, - 'error': 1, - 'warn': 2, - 'debug': 4, // Socket.io flips these around (and it considers debug more verbose than `info`) - 'info': 3, // Socket.io flips these around - 'verbose': 4 // Socket.io has no concept of `verbose` - }; - io.set('log level', logLevels[sails.config.log.level] || logLevels['info']); - io.set('logger', { - error: sails.log.error, - warn: sails.log.warn, - info: sails.log.verbose, - debug: sails.log.verbose // socket.io considers `debug` the most verbose config, so we'll use verbose to represent it - }); - } - - - - // Process the Config File - util.each(socketConfig, function(value, propertyName) { - - // Handle `Memory` adapter value - if (propertyName === 'adapter' && value === 'memory') return; - - // Setup custom socket.io MQ (pubsub) store(s) - if (propertyName === 'adapter' && value === 'redis') { - var host = socketConfig.host || '127.0.0.1'; - var port = socketConfig.port || 6379; - - var pub = createRedisConnection(port, host, 'pub'); - var sub = createRedisConnection(port, host, 'sub'); - var client = createRedisConnection(port, host, 'client'); - - var storeConfig = { - redisPub: pub, - redisSub: sub, - redisClient: client - }; - - // Add a pointer to the redis, required with Auth - if(socketConfig.pass) { - storeConfig.redis = Redis; - } - - io.set('store', new RedisStore(storeConfig)); - return; - } - - // Configure logic to be run before allowing sockets to connect - if (propertyName === 'authorization') { - - // Custom logic - if (util.isFunction(value)) { - io.set('authorization', value); - return; - } - - // `authorization: true` means go ahead and use the default behavior - if (value === true) { - io.set('authorization', Socket.authorization); - return; - } - - // Otherwise skip the authorization step - io.set('authorization', false); - - return; - } - - // If value is explicitly undefined, do nothing - if (util.isUndefined(value)) return; - - // In the general case, pass the configuration straight down to socket.io - io.set(propertyName, value); - - }); - - - // For later: - // io.configure('development', function() {}); - // io.configure('production', function() {}); - - - // Link Socket.io requests to a controller/action - // When a socket.io client connects, listen for the actions in the routing table - // Authorization has already passed at this point! - io.sockets.on('connection', Socket.connection); - - cb && cb(); - }; - - - /** - * Creates a new Redis Connection if specified. - * - * Can be used to connect to remote server with authentication if - * `pass` is declared in the socketConfig file. - */ - - function createRedisConnection(port, host, id) { - - var socketConfig = sails.config.sockets; - - // Create a new client using the port, host and other options - var client = Redis.createClient(port, host, socketConfig); - - // If a password is needed use client.auth to set it - if(socketConfig.pass) { - client.auth(socketConfig.pass, function(err) { - if (err) throw err; - }); - } - - // If a db is set select it on the client - if (socketConfig.db) { - client.select(socketConfig.db); - } - - // If Redis connection ends, catch the error and retry - // until it comes back - - client.on('ready', function() { - sails.log.debug('RedisClient::Events[ready]: [OK] Redis "' + id + '" is up. Connections: ', client.connections); - }); - - client.on('end', function() { - sails.log.debug('RedisClient::Events[end]: "' + id + '" , Connected:', client.connected); - }); - - client.on('error', function (err) { - sails.log.error('RedisClient::Events[error]: "' + id + '" , ' + err); - if (/ECONNREFUSED/g.test(err)) { - sails.log.error('Waiting for "' + id + '" redis client to come back online. Connections:', client.connections); - } - }); - - return client; - } - -}; From ae4467a69c2494cd272dfad1e0aa459944a513ca Mon Sep 17 00:00:00 2001 From: Mike McNeil Date: Fri, 12 Dec 2014 10:09:08 -0600 Subject: [PATCH 051/235] bump to v0.11.0 for tests --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index f64f428a15..3b990f37f8 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "sails", - "author": "Mike McNeil ", - "version": "0.10.5", + "author": "Mike McNeil <@mikermcneil>", + "version": "0.11.0", "description": "API-driven framework for building realtime apps, using MVC conventions (based on Express and Socket.io)", "homepage": "http://sailsjs.org", "keywords": [ From 5b1a45b9d4ddc57693be59b9b6690b2960e59217 Mon Sep 17 00:00:00 2001 From: Mike McNeil Date: Fri, 12 Dec 2014 10:17:50 -0600 Subject: [PATCH 052/235] Remove dependency of pubsub hook on the private getSDKMetadata() method on the sockets hook --- lib/app/configuration/index.js | 2 +- lib/hooks/pubsub/index.js | 19 +++++++++++-------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/lib/app/configuration/index.js b/lib/app/configuration/index.js index eae4cbc99b..fd65921392 100644 --- a/lib/app/configuration/index.js +++ b/lib/app/configuration/index.js @@ -50,7 +50,7 @@ module.exports = function(sails) { // Default hooks // TODO: remove hooks from config to avoid confusion // (because you can't configure hooks in `userconfig`-- only in `overrides`) - hooks: _.reduce(DEFAULT_HOOKS, function (memo, hookName, hookDef) { + hooks: _.reduce(DEFAULT_HOOKS, function (memo, hookDef, hookName) { // if `true`, the hook is bundled in the Sails core module // (look in the `lib/hooks` folder in core) diff --git a/lib/hooks/pubsub/index.js b/lib/hooks/pubsub/index.js index 95e1292850..6b3110689c 100644 --- a/lib/hooks/pubsub/index.js +++ b/lib/hooks/pubsub/index.js @@ -1,15 +1,18 @@ /** * Module dependencies. */ -var util = require('util') - , _ = require('lodash') - , getSDKMetadata = require('../sockets/lib/getSDKMetadata') - , STRINGFILE = require('sails-stringfile'); + +var util = require('util'); +var _ = require('lodash'); +var STRINGFILE = require('sails-stringfile'); + + /** * Module errors */ + var Err = { dependency: function (dependent, dependency) { return new Error( '\n' + @@ -651,7 +654,7 @@ module.exports = function(sails) { // If the subscribing socket is using the legacy (v0.9.x) socket SDK (sails.io.js), // always subscribe the client to the `legacy_v0.9` context. if (sails.config.sockets['backwardsCompatibilityFor0.9SocketClients'] && socket.handshake) { - var sdk = getSDKMetadata(socket.handshake); + var sdk = app.getSDKMetadata(socket.handshake); var isLegacySocketClient = sdk.version === '0.9.0'; if (isLegacySocketClient) { contexts.push('legacy_v0.9'); @@ -711,7 +714,7 @@ module.exports = function(sails) { } if (sails.config.sockets['backwardsCompatibilityFor0.9SocketClients'] && socket.handshake) { - var sdk = getSDKMetadata(socket.handshake); + var sdk = app.getSDKMetadata(socket.handshake); var isLegacySocketClient = sdk.version === '0.9.0'; if (isLegacySocketClient) { contexts.push('legacy_v0.9'); @@ -1383,7 +1386,7 @@ module.exports = function(sails) { sails.log.silly("Subscribed socket ", sails.sockets.id(socket), "to", this._classRoom()); if (sails.config.sockets['backwardsCompatibilityFor0.9SocketClients'] && socket.handshake) { - var sdk = getSDKMetadata(socket.handshake); + var sdk = app.getSDKMetadata(socket.handshake); var isLegacySocketClient = sdk.version === '0.9.0'; if (isLegacySocketClient) { sails.sockets.join(socket, this._classRoom()+':legacy_v0.9'); @@ -1409,7 +1412,7 @@ module.exports = function(sails) { sails.log.silly("Unubscribed socket ", sails.sockets.id(socket), "from", this._classRoom()); if (sails.config.sockets['backwardsCompatibilityFor0.9SocketClients'] && socket.handshake) { - var sdk = getSDKMetadata(socket.handshake); + var sdk = app.getSDKMetadata(socket.handshake); var isLegacySocketClient = sdk.version === '0.9.0'; if (isLegacySocketClient) { sails.sockets.leave(socket, this._classRoom()+':legacy_v0.9'); From 4ee2ec794f9e90d5d63960fda120c5ee7e3af59b Mon Sep 17 00:00:00 2001 From: Mike McNeil Date: Fri, 12 Dec 2014 11:57:19 -0600 Subject: [PATCH 053/235] Remove direct sio dep. --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index 3b990f37f8..312aabfe44 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,6 @@ "reportback": "~0.1.4", "sails-util": "~0.10.3", "colors": "~0.6.2", - "socket.io": "~0.9.14", "connect-flash": "~0.1.1", "pluralize": "~0.0.5", "node-uuid": "~1.4.0", From fae5c3daeed915862bc95c443851606ccff055cf Mon Sep 17 00:00:00 2001 From: Scott Gress Date: Fri, 12 Dec 2014 12:07:03 -0600 Subject: [PATCH 054/235] Added basic hooks API tests --- test/fixtures/customHooks.js | 85 +++++++++++++++++++++-- test/unit/app.initializeHooks.test.js | 97 +++++++++++++++++++++++++++ 2 files changed, 177 insertions(+), 5 deletions(-) diff --git a/test/fixtures/customHooks.js b/test/fixtures/customHooks.js index 9b21d9d93b..2eef38df18 100644 --- a/test/fixtures/customHooks.js +++ b/test/fixtures/customHooks.js @@ -1,15 +1,15 @@ /** * Stub custom hooks for use in tests. - * + * * @type {Object} */ module.exports = { // Extremely simple hook that doesn't do anything. - NOOP: function (sails) { + NOOP: function (sails) { return { identity: 'noop' }; }, - + // Depends on 'noop' hook NOOP2: function (sails) { return { @@ -21,5 +21,80 @@ module.exports = { // Deliberately rotten hook- it throws. SPOILED_HOOK: function (sails) { throw new Error('smells nasty'); - } -}; \ No newline at end of file + }, + + // Hook to test `defaults` object + DEFAULTS_OBJ: function(sails) { + return { + identity: 'defaults_obj', + defaults: { + foo: 'bar', + inky: { + dinky: 'doo', + pinky: 'dont' + } + } + }; + }, + + // Hook to test `defaults` function + DEFAULTS_FN: function(sails) { + return { + identity: 'defaults_fn', + defaults: function() { + return { + foo: 'bar', + inky: { + dinky: 'doo', + pinky: 'dont' + } + }; + } + }; + }, + + // Hook to test `initialize` function + INIT_FN: function(sails) { + return { + identity: 'init_fn', + initialize: function(cb) { + sails.config.hookInitLikeABoss = true; + return cb(); + } + }; + }, + + // Hook to test `configure` function + CONFIG_FN: function(sails) { + return { + identity: 'config_fn', + configure: function() { + // Test that loaded config is available by copying a value + sails.config.hookConfigLikeABoss = sails.config.testConfig; + } + }; + }, + + // Hook to test `routes` function + ROUTES: function(sails) { + return { + identity: 'routes', + routes: { + before: { + "GET /foo": function(req, res, next) { + sails.config.foo = "a"; + next(); + } + }, + after: { + "GET /foo": function(req, res, next) { + sails.config.foo = sails.config.foo + "c"; + res.send(sails.config.foo); + } + } + } + }; + }, + + +}; diff --git a/test/unit/app.initializeHooks.test.js b/test/unit/app.initializeHooks.test.js index 82f37a3166..865dc449fa 100644 --- a/test/unit/app.initializeHooks.test.js +++ b/test/unit/app.initializeHooks.test.js @@ -10,6 +10,7 @@ var customHooks = require('../fixtures/customHooks'); var $Sails = require('../helpers/sails'); +var supertest = require('supertest'); // TIP: @@ -92,6 +93,102 @@ describe('app.initializeHooks()', function() { }); + describe('configured with a custom hook with a `defaults` object', function() { + var sails = $Sails.load({ + hooks: { + defaults_obj: customHooks.DEFAULTS_OBJ + }, + inky: { + pinky: 'boo' + } + }); + + it('should add a `foo` key to sails config', function() { + assert(sails.config.foo == 'bar'); + }); + it('should add an `inky.dinky` key to sails config', function() { + assert(sails.config.inky.dinky == 'doo'); + }); + it('should keep the existing `inky.pinky` key to sails config', function() { + assert(sails.config.inky.pinky == 'boo'); + }); + + }); + + describe('configured with a custom hook with a `defaults` function', function() { + var sails = $Sails.load({ + hooks: { + defaults_fn: customHooks.DEFAULTS_FN + }, + inky: { + pinky: 'boo' + } + }); + + it('should add a `foo` key to sails config', function() { + assert(sails.config.foo == 'bar'); + }); + it('should add an `inky.dinky` key to sails config', function() { + assert(sails.config.inky.dinky == 'doo'); + }); + it('should keep the existing `inky.pinky` key to sails config', function() { + assert(sails.config.inky.pinky == 'boo'); + }); + + }); + + describe('configured with a custom hook with a `configure` function', function() { + var sails = $Sails.load({ + hooks: { + config_fn: customHooks.CONFIG_FN + }, + testConfig: 'oh yeah!' + }); + + it('should add a `hookConfigLikeABoss` key to sails config', function() { + assert(sails.config.hookConfigLikeABoss == 'oh yeah!'); + }); + + }); + + describe('configured with a custom hook with an `initialize` function', function() { + var sails = $Sails.load({ + hooks: { + init_fn: customHooks.INIT_FN + } + }); + + it('should add a `hookInitLikeABoss` key to sails config', function() { + assert(sails.config.hookInitLikeABoss === true); + }); + + }); + + + describe('configured with a custom hook with a `routes` object', function() { + var sails = $Sails.load({ + hooks: { + routes: customHooks.ROUTES + }, + routes: { + "GET /foo": function(req, res, next) {sails.config.foo += "b"; return next();} + } + }); + + it('should add two `/foo` routes to the sails config', function() { + var boundRoutes = sails.router._privateRouter.routes['get']; + assert(_.where(boundRoutes, {path: "/foo", method: "get"}).length === 3); + }); + + it('should bind the routes in the correct order', function(done) { + supertest(sails.router._privateRouter) + .get('/foo') + .expect(200, 'abc') + .end(done); + }); + + }); + // describe('configured with a circular hook dependency', function () { From 6feb2ef90ada08900d8e3db783400fe2104505fe Mon Sep 17 00:00:00 2001 From: Mike McNeil Date: Fri, 12 Dec 2014 12:07:22 -0600 Subject: [PATCH 055/235] Bit of cleanup for core hooks setup process --- lib/app/configuration/default-hooks.js | 2 +- lib/app/configuration/index.js | 34 ++++++++++++++++++++------ 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/lib/app/configuration/default-hooks.js b/lib/app/configuration/default-hooks.js index acf8e5fb8c..3d611d9990 100644 --- a/lib/app/configuration/default-hooks.js +++ b/lib/app/configuration/default-hooks.js @@ -13,7 +13,7 @@ module.exports = { 'blueprints': true, 'responses': true, 'controllers': true, - 'sockets': require('sails-hook-sockets'), + 'sockets': 'sails-hook-sockets', 'pubsub': true, 'policies': true, 'services': true, diff --git a/lib/app/configuration/index.js b/lib/app/configuration/index.js index fd65921392..be71df90e6 100644 --- a/lib/app/configuration/index.js +++ b/lib/app/configuration/index.js @@ -47,18 +47,36 @@ module.exports = function(sails) { environment: defaultEnv, - // Default hooks - // TODO: remove hooks from config to avoid confusion + //////////////////////////////////////////////////////////////////////////////// + // TODO: + // to avoid confusion, eventually remove `hooks` from config in favor of something + // more flexible, e.g. the `app` object itself. // (because you can't configure hooks in `userconfig`-- only in `overrides`) - hooks: _.reduce(DEFAULT_HOOKS, function (memo, hookDef, hookName) { + //////////////////////////////////////////////////////////////////////////////// - // if `true`, the hook is bundled in the Sails core module - // (look in the `lib/hooks` folder in core) - if (hookDef === true) { - memo[hookName] = require('../../hooks/'+hookName); + // Core (default) hooks + hooks: _.reduce(DEFAULT_HOOKS, function (memo, hookBundled, hookIdentity) { + + // if `true`, then the core hook is bundled in the `lib/hooks/` directory + // as `lib/hooks/HOOK_IDENTITY`. + if (hookBundled === true) { + memo[hookIdentity] = require('../../hooks/'+hookIdentity); + } + // if it's a string, then the core hook is an NPM dependency of sails, + // so require it (which grabs it from `node_modules/`) + else if (_.isString(hookBundled)) { + var hook; + try { + hook = require(hookBundled); + } + catch (e) { + throw new Error('Sails internal error: Could not require(\''+hookBundled+'\')'); + } + memo[hookIdentity] = hook; } + // otherwise freak out else { - memo[hookName] = hookDef; + throw new Error('Sails internal error: "'+hookIdentity+'", a core hook, is invalid!'); } return memo; }, {}) || {}, From 65a10dccda716870de9ae6ab6d14f29d930a3cb7 Mon Sep 17 00:00:00 2001 From: Mike McNeil Date: Fri, 12 Dec 2014 12:09:08 -0600 Subject: [PATCH 056/235] Expand comment in default-hooks --- lib/app/configuration/default-hooks.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/app/configuration/default-hooks.js b/lib/app/configuration/default-hooks.js index 3d611d9990..962d857d1c 100644 --- a/lib/app/configuration/default-hooks.js +++ b/lib/app/configuration/default-hooks.js @@ -1,7 +1,12 @@ /** * Default hooks - * (order matters) - * TODO: make order _not_ matter (it pretty much doesn't already b/c events- but for a few core hooks it still does) + * + * (order still matters for now for some of these) + * + * TODO: + * make order _not_ matter + * (it pretty much doesn't already b/c of our use of events... + * ...but for a few core hooks, e.g. `moduleloader`, it still does) */ module.exports = { From dba53b1838ee64f6c38e3d5a61eefae3009ad777 Mon Sep 17 00:00:00 2001 From: Scott Gress Date: Fri, 12 Dec 2014 12:14:48 -0600 Subject: [PATCH 057/235] Added advanced routing test for hooks --- test/fixtures/customHooks.js | 35 +++++++++++++++++++++++++++ test/unit/app.initializeHooks.test.js | 23 ++++++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/test/fixtures/customHooks.js b/test/fixtures/customHooks.js index 2eef38df18..7fffe1c42d 100644 --- a/test/fixtures/customHooks.js +++ b/test/fixtures/customHooks.js @@ -96,5 +96,40 @@ module.exports = { }; }, + // Hook to test `routes` function + ADVANCED_ROUTES: function(sails) { + return { + identity: 'advanced_routes', + initialize: function(cb) { + sails.on('router:before', function() { + sails.router.bind('GET /foo', function(req, res, next) { + sails.config.foo = sails.config.foo + "b"; + next(); + }); + }); + sails.on('router:after', function() { + sails.router.bind('GET /foo', function(req, res, next) { + sails.config.foo = sails.config.foo + "e"; + res.send(sails.config.foo); + }); + }); + cb(); + }, + routes: { + before: { + "GET /foo": function(req, res, next) { + sails.config.foo = "a"; + next(); + } + }, + after: { + "GET /foo": function(req, res, next) { + sails.config.foo = sails.config.foo + "d"; + next(); + } + } + } + }; + }, }; diff --git a/test/unit/app.initializeHooks.test.js b/test/unit/app.initializeHooks.test.js index 865dc449fa..bd0dafe48e 100644 --- a/test/unit/app.initializeHooks.test.js +++ b/test/unit/app.initializeHooks.test.js @@ -189,6 +189,29 @@ describe('app.initializeHooks()', function() { }); + describe('configured with a custom hook with advanced routing', function() { + var sails = $Sails.load({ + hooks: { + advanced_routes: customHooks.ADVANCED_ROUTES + }, + routes: { + "GET /foo": function(req, res, next) {sails.config.foo += "c"; return next();} + } + }); + + it('should add four `/foo` routes to the sails config', function() { + var boundRoutes = sails.router._privateRouter.routes['get']; + assert(_.where(boundRoutes, {path: "/foo", method: "get"}).length === 5); + }); + + it('should bind the routes in the correct order', function(done) { + supertest(sails.router._privateRouter) + .get('/foo') + .expect(200, 'abcde') + .end(done); + }); + + }); // describe('configured with a circular hook dependency', function () { From 1c15b31263f0cdc7d9e36240749f5db676e2dd77 Mon Sep 17 00:00:00 2001 From: Mike McNeil Date: Fri, 12 Dec 2014 14:12:36 -0600 Subject: [PATCH 058/235] Fix initializeHooks test to reflect new core hook built-in setup. --- test/fixtures/constants.js | 2 +- test/unit/app.initializeHooks.test.js | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/test/fixtures/constants.js b/test/fixtures/constants.js index 4ef4a7a08b..2f4c573151 100644 --- a/test/fixtures/constants.js +++ b/test/fixtures/constants.js @@ -5,5 +5,5 @@ * @type {Object} */ module.exports = { - EXPECTED_DEFAULT_HOOKS: require('../../lib/app/configuration/defaultHooks') + EXPECTED_DEFAULT_HOOKS: require('../../lib/app/configuration/default-hooks') }; diff --git a/test/unit/app.initializeHooks.test.js b/test/unit/app.initializeHooks.test.js index 82f37a3166..c4b2e156e2 100644 --- a/test/unit/app.initializeHooks.test.js +++ b/test/unit/app.initializeHooks.test.js @@ -38,8 +38,8 @@ describe('app.initializeHooks()', function() { }); it('should expose at least the expected core hooks', function() { - var intersection = _.intersection(Object.keys(sails.hooks), constants.EXPECTED_DEFAULT_HOOKS); - assert.deepEqual(intersection, constants.EXPECTED_DEFAULT_HOOKS, 'Missing expected default hooks'); + var intersection = _.intersection(_.keys(sails.hooks), _.keys(constants.EXPECTED_DEFAULT_HOOKS)); + assert.deepEqual(intersection, _.keys(constants.EXPECTED_DEFAULT_HOOKS), 'Missing expected default hooks'); }); }); @@ -57,8 +57,8 @@ describe('app.initializeHooks()', function() { .property('noop'); }); it('should also expose the expected core hooks', function() { - var intersection = _.intersection(Object.keys(sails.hooks), constants.EXPECTED_DEFAULT_HOOKS); - assert.deepEqual(intersection, constants.EXPECTED_DEFAULT_HOOKS, 'Missing expected default hooks'); + var intersection = _.intersection(Object.keys(sails.hooks), _.keys(constants.EXPECTED_DEFAULT_HOOKS)); + assert.deepEqual(intersection, _.keys(constants.EXPECTED_DEFAULT_HOOKS), 'Missing expected default hooks'); }); }); From 559f2614dccae76946cb939babfd6d2a33ecd761 Mon Sep 17 00:00:00 2001 From: Scott Gress Date: Fri, 12 Dec 2014 14:15:21 -0600 Subject: [PATCH 059/235] Easier installable hook default configuration using special __configKey__ key. --- lib/hooks/index.js | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/lib/hooks/index.js b/lib/hooks/index.js index 035a54d36c..9f4a826949 100644 --- a/lib/hooks/index.js +++ b/lib/hooks/index.js @@ -64,10 +64,17 @@ module.exports = function(sails) { * @api private */ function applyDefaults() { - defaultsDeep(sails.config, - _.isFunction(self.defaults) ? - self.defaults(sails.config) : - self.defaults); + // Get the hook defaults + var defaults = (_.isFunction(self.defaults) ? + self.defaults(sails.config) : + self.defaults) || {}; + // Replace the special __configKey__ key with the actual config key + if (self.defaults.__configKey__ && self.configKey) { + self.defaults[self.configKey] = self.defaults.__configKey__; + delete self.defaults.__configKey__; + } + + defaultsDeep(sails.config, defaults); } From 3c985cf324496c3a3c28314f7a2bf333b05a645d Mon Sep 17 00:00:00 2001 From: Mike McNeil Date: Fri, 12 Dec 2014 14:51:04 -0600 Subject: [PATCH 060/235] More flexible support for hooks that would like to skip JSON stringification/parsing (e.g. sockets). --- lib/router/res.js | 79 +++++++++++++++++++++++++++++++---------------- 1 file changed, 53 insertions(+), 26 deletions(-) diff --git a/lib/router/res.js b/lib/router/res.js index a6d0886b5e..c675735f4e 100644 --- a/lib/router/res.js +++ b/lib/router/res.js @@ -48,22 +48,13 @@ module.exports = function buildResponse (req, _res) { // fake clientRes stream where the response `body` will be buffered. if (res._clientCallback) { - // Build `res._clientRes` if one was not provided + // If `res._clientRes` WAS NOT provided, then create one if (!res._clientRes) { res._clientRes = new MockClientResponse(); } // The stream should trigger the callback when it finishes or errors. res._clientRes.on('finish', function() { - - res._clientRes.body = Buffer.concat(res._clientRes._readableState.buffer).toString(); - try { - res._clientRes.body = JSON.parse(res._clientRes.body); - } catch (e) {} - - // Don't include body if it is empty - if (!res._clientRes.body) delete res._clientRes.body; - return res._clientCallback(res._clientRes); }); res._clientRes.on('error', function(err) { @@ -118,19 +109,66 @@ module.exports = function buildResponse (req, _res) { // (override `this.statusCode` if `statusCode` argument specified) res.statusCode = args.statusCode || res.statusCode || 200; - // Write body to `res` stream if it exists + // if a `_clientCallback` was specified, we'll skip the streaming stuff for res.send(). + if (res._clientCallback) { + + // Manually plug in res.body. + res.body = args.other; + res._clientRes.body = res.body; + // (but don't include body if it is empty) + if (!res.body) delete res.body; + if (!res._clientRes.body) delete res._clientRes.body; + + // Set status code and headers on the `_clientRes` stream so they are accessible + // to the provider of that stream. + // (this has to happen in `send()` because the code/headers might have just changed) + if (res._clientRes) { + res._clientRes.headers = res.headers; + res._clientRes.statusCode = res.statusCode; + } + + // End the `res` stream (which will in turn end the `res._clientRes` stream) + res.end(); + return; + } + + // Otherwise, the hook using the interpreter must have provided us with a `res._clientRes` stream, + // so we'll need to serialize everything to work w/ that stream. + + // Write body to `res` stream if (args.other) { + var toWrite = args.other; + if (typeof toWrite === 'object') { - toWrite = util.inspect(toWrite); + try { + toWrite = JSON.stringify(args.other); + + // original method: + // toWrite = util.inspect(toWrite); + } + catch(e) { + var failedStringify = new Error( + 'Failed to stringify specified JSON response body :: ' + util.inspect(args.other) + + '\nError:\n' + util.inspect(e) + ); + console.log('failed to stringify!'); + if (req._sails && req._sails.log && req._sails.log.error) { + req._sails.log.error(failedToStringify); + } + else { + console.error(failedToStringify); + } + toWrite = failedStringify.message; + res.statusCode = 500; + } } res.write(toWrite); } - // Set status code and headers on the `_clientRes` stream so they are accessible // to the provider of that stream. - // (this has to happen in `send()` because the code/headers might have changed) + // (this has to happen in `send()` because the code/headers might have just changed) if (res._clientRes) { res._clientRes.headers = res.headers; res._clientRes.statusCode = res.statusCode; @@ -143,18 +181,7 @@ module.exports = function buildResponse (req, _res) { // res.json() res.json = res.json || function json_shim () { var args = normalizeResArgs(arguments); - - try { - var json = JSON.stringify(args.other); - return res.send(json, args.statusCode || res.statusCode || 200); - } - catch(e) { - var failedStringify = new Error( - 'Failed to stringify specified JSON response body :: ' + util.inspect(args.other) + - '\nError:\n' + util.inspect(e) - ); - return res.send(failedStringify.stack, 500); - } + return res.send(args.other, args.statusCode || res.statusCode || 200); }; // res.render() From 83fb33b2993cc041f1a468cf15041752315ca9b3 Mon Sep 17 00:00:00 2001 From: Scott Gress Date: Fri, 12 Dec 2014 15:24:54 -0600 Subject: [PATCH 061/235] Change installable-hooks config key to "sails.config.installedHooks" to avoid collision with existing sails.config.hooks --- lib/hooks/moduleloader/index.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/hooks/moduleloader/index.js b/lib/hooks/moduleloader/index.js index e60fb0be0f..6e0f069572 100644 --- a/lib/hooks/moduleloader/index.js +++ b/lib/hooks/moduleloader/index.js @@ -395,9 +395,9 @@ module.exports = function(sails) { // Determine the name the hook should be added as var hookName; - // If an identity was specified in sails.config.hooks, use that - if (sails.config.hooks && sails.config.hooks[identity] && sails.config.hooks[identity].identity) { - hookName = sails.config.hooks[identity].identity; + // If an identity was specified in sails.config.installedHooks, use that + if (sails.config.installedHooks && sails.config.installedHooks[identity] && sails.config.installedHooks[identity].identity) { + hookName = sails.config.installedHooks[identity].identity; } // Otherwise use the module name, with initial "sails-hook" stripped off if it exists else { @@ -412,7 +412,7 @@ module.exports = function(sails) { var hook = require(path.resolve(sails.config.appPath, "node_modules", identity)); // Set its config key (defaults to the hook name) - hook.configKey = (sails.config.hooks && sails.config.hooks[identity] && sails.config.hooks[identity].configKey) || hookName; + hook.configKey = (sails.config.installedHooks && sails.config.installedHooks[identity] && sails.config.installedHooks[identity].configKey) || hookName; // Add this to the list of hooks to load memo[hookName] = hook; From afcb98a625f73f9b31a84e3bdd2597cfa4f52fba Mon Sep 17 00:00:00 2001 From: Mike McNeil Date: Fri, 12 Dec 2014 15:25:59 -0600 Subject: [PATCH 062/235] Implemented in-the-mean-time thing for https://github.com/Automattic/socket.io/issues/1908#issuecomment-66836641 --- lib/hooks/pubsub/index.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/lib/hooks/pubsub/index.js b/lib/hooks/pubsub/index.js index 6b3110689c..7bcfd6bb9b 100644 --- a/lib/hooks/pubsub/index.js +++ b/lib/hooks/pubsub/index.js @@ -292,10 +292,18 @@ module.exports = function(sails) { * @return {array} An array of socket ID strings */ sails.sockets.subscribers = function(roomName, returnSockets) { + + // The underlying implementation was changed a bit with the upgrade + // to Socket.io v1.0. For more information, see: + // •-> https://github.com/Automattic/socket.io/issues/1908#issuecomment-66836641 + // and •-> https://github.com/Automattic/socket.io/pull/1630#issuecomment-64389524 + if (returnSockets) { - return sails.io.sockets.clients(roomName); + return sails.io.nsps['/'].adapter.rooms[roomName]; + // return sails.io.sockets.clients(roomName); } else { - return _.pluck(sails.io.sockets.clients(roomName), 'id'); + return _.keys(sails.io.nsps['/'].adapter.rooms[roomName]); + // return _.pluck(sails.io.sockets.clients(roomName), 'id'); } }; From fd9a15f5abb4f1205afa13605af1b9851ffd96ee Mon Sep 17 00:00:00 2001 From: Scott Gress Date: Fri, 12 Dec 2014 15:38:12 -0600 Subject: [PATCH 063/235] Removed unused var --- lib/hooks/cors/index.js | 4 +--- lib/hooks/csrf/index.js | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/lib/hooks/cors/index.js b/lib/hooks/cors/index.js index ff6abd60a4..38b5c7246f 100644 --- a/lib/hooks/cors/index.js +++ b/lib/hooks/cors/index.js @@ -5,9 +5,7 @@ module.exports = function(sails) { */ var _ = require('lodash'), - util = require('sails-util'), - Hook = require('../../index'); - + util = require('sails-util') /** * Expose hook definition diff --git a/lib/hooks/csrf/index.js b/lib/hooks/csrf/index.js index 1fd7633ba6..b0392d4ecc 100644 --- a/lib/hooks/csrf/index.js +++ b/lib/hooks/csrf/index.js @@ -5,9 +5,7 @@ module.exports = function(sails) { */ var _ = require('lodash'), - util = require('sails-util'), - Hook = require('../../index'); - + util = require('sails-util'); /** * Expose hook definition From aa0805fb25d409b472e0995875ea3d82d8b296d0 Mon Sep 17 00:00:00 2001 From: Scott Gress Date: Fri, 12 Dec 2014 15:52:49 -0600 Subject: [PATCH 064/235] Don't keep loading hooks if one of them is invalid. --- lib/hooks/moduleloader/index.js | 70 ++++++++++++++++++--------------- 1 file changed, 39 insertions(+), 31 deletions(-) diff --git a/lib/hooks/moduleloader/index.js b/lib/hooks/moduleloader/index.js index 6e0f069572..5116f9ed28 100644 --- a/lib/hooks/moduleloader/index.js +++ b/lib/hooks/moduleloader/index.js @@ -386,41 +386,49 @@ module.exports = function(sails) { // api/hooks folder are assumed to be okay. var hooks = results.hooksFolder; - _.extend(hooks, _.reduce(results.nodeModulesFolder, function(memo, module, identity) { - - // Hooks loaded from "node_modules" need to have "sails.isHook: true" in order for us - // to know that they are a sails hook - if (module['package.json'] && module['package.json'].sails && module['package.json'].sails.isHook) { - - // Determine the name the hook should be added as - var hookName; + try { - // If an identity was specified in sails.config.installedHooks, use that - if (sails.config.installedHooks && sails.config.installedHooks[identity] && sails.config.installedHooks[identity].identity) { - hookName = sails.config.installedHooks[identity].identity; - } - // Otherwise use the module name, with initial "sails-hook" stripped off if it exists - else { - hookName = identity.match(/^sails-hook-/) ? identity.replace(/^sails-hook-/,'') : identity; + _.extend(hooks, _.reduce(results.nodeModulesFolder, function(memo, module, identity) { + + // Hooks loaded from "node_modules" need to have "sails.isHook: true" in order for us + // to know that they are a sails hook + if (module['package.json'] && module['package.json'].sails && module['package.json'].sails.isHook) { + + // Determine the name the hook should be added as + var hookName; + + // If an identity was specified in sails.config.installedHooks, use that + if (sails.config.installedHooks && sails.config.installedHooks[identity] && sails.config.installedHooks[identity].identity) { + hookName = sails.config.installedHooks[identity].identity; + } + // Otherwise use the module name, with initial "sails-hook" stripped off if it exists + else { + hookName = identity.match(/^sails-hook-/) ? identity.replace(/^sails-hook-/,'') : identity; + } + // If we have a core hook or a hook in api/hooks with this name, throw an error + if (sails.hooks[hookName] || hooks[hookName]) { + var err = new Error("Tried to load hook `" + hookName + "`, but a hook with that name already exists."); + err.code = 'E_INVALID_HOOK_NAME'; + throw err; + } + + // Load the hook code + var hook = require(path.resolve(sails.config.appPath, "node_modules", identity)); + + // Set its config key (defaults to the hook name) + hook.configKey = (sails.config.installedHooks && sails.config.installedHooks[identity] && sails.config.installedHooks[identity].configKey) || hookName; + + // Add this to the list of hooks to load + memo[hookName] = hook; } - // If we have a core hook or a hook in api/hooks with this name, throw an error - if (sails.hooks[hookName] || hooks[hookName]) { - cb(new Error("Tried to load hook `" + hookName + "`, but a hook with that name already exists.")); - } - - // Load the hook code - var hook = require(path.resolve(sails.config.appPath, "node_modules", identity)); + return memo; + }, {})); - // Set its config key (defaults to the hook name) - hook.configKey = (sails.config.installedHooks && sails.config.installedHooks[identity] && sails.config.installedHooks[identity].configKey) || hookName; + cb(null, hooks); - // Add this to the list of hooks to load - memo[hookName] = hook; - } - return memo; - }, {})); - - cb(null, hooks); + } catch (e) { + return cb(e); + } }); }, From 15fee5b92bc5c6251f0b61214869c550664bd33f Mon Sep 17 00:00:00 2001 From: Scott Gress Date: Fri, 12 Dec 2014 15:53:36 -0600 Subject: [PATCH 065/235] Don't start ORM if sails is already exiting due to previous errors. --- lib/hooks/orm/build-orm.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/hooks/orm/build-orm.js b/lib/hooks/orm/build-orm.js index d167ab7381..3c5c5d5f7c 100644 --- a/lib/hooks/orm/build-orm.js +++ b/lib/hooks/orm/build-orm.js @@ -18,6 +18,10 @@ var Waterline = require('waterline'); module.exports = function howto_buildORM(sails) { return function buildORM(modelDefs, cb) { + // If sails is already exiting due to previous errors, bail out. + // This could happen if another hook fails to load. + if (sails._exiting) {return cb("SAILS EXITING");} + // -> Instantiate ORM in memory. // -> Iterate through each model definition: // -> Create a proper Waterline Collection for each model From 7c7ff990b25af3fa3d51ec97fe9ad26d9362bc91 Mon Sep 17 00:00:00 2001 From: Scott Gress Date: Fri, 12 Dec 2014 16:20:38 -0600 Subject: [PATCH 066/235] Added tests for installable hooks --- .../fixtures/hooks/installable/shout/index.js | 27 ++ .../hooks/installable/shout/package.json | 7 + test/integration/helpers/appHelper.js | 18 ++ test/integration/hook.3rdparty.test.js | 290 ++++++++++++++++++ 4 files changed, 342 insertions(+) create mode 100644 test/integration/fixtures/hooks/installable/shout/index.js create mode 100644 test/integration/fixtures/hooks/installable/shout/package.json create mode 100644 test/integration/hook.3rdparty.test.js diff --git a/test/integration/fixtures/hooks/installable/shout/index.js b/test/integration/fixtures/hooks/installable/shout/index.js new file mode 100644 index 0000000000..c6b20367fa --- /dev/null +++ b/test/integration/fixtures/hooks/installable/shout/index.js @@ -0,0 +1,27 @@ +module.exports = function(sails) { + + var phrase; + return { + + defaults: { + __configKey__: { + phrase: "make it rain" + } + }, + + initialize: function(cb) { + phrase = sails.config[this.configKey].phrase; + cb(); + }, + + routes: { + before: { + "GET /shout": function(req, res, next) { + res.send(phrase); + } + } + } + + }; + +}; diff --git a/test/integration/fixtures/hooks/installable/shout/package.json b/test/integration/fixtures/hooks/installable/shout/package.json new file mode 100644 index 0000000000..e0365f026b --- /dev/null +++ b/test/integration/fixtures/hooks/installable/shout/package.json @@ -0,0 +1,7 @@ +{ + "name": "sails-hook-shout", + "main": "index.js", + "sails": { + "isHook": true + } +} diff --git a/test/integration/helpers/appHelper.js b/test/integration/helpers/appHelper.js index ca4087b8eb..5833a34e7c 100644 --- a/test/integration/helpers/appHelper.js +++ b/test/integration/helpers/appHelper.js @@ -84,6 +84,24 @@ module.exports.teardown = function(appName) { } }; +module.exports.liftQuiet = function(options, callback) { + + if (typeof options == 'function') { + callback = options; + options = null; + } + + options = options || {}; + _.defaults(options, { + log: { + level: 'silent' + } + }); + + return module.exports.lift(options, callback); + +}; + module.exports.lift = function(options, callback) { delete process.env.NODE_ENV; diff --git a/test/integration/hook.3rdparty.test.js b/test/integration/hook.3rdparty.test.js new file mode 100644 index 0000000000..3d52d7bbb9 --- /dev/null +++ b/test/integration/hook.3rdparty.test.js @@ -0,0 +1,290 @@ +/** + * Test dependencies + */ +var assert = require('assert'); +var httpHelper = require('./helpers/httpHelper.js'); +var appHelper = require('./helpers/appHelper'); +var util = require('util'); +var wrench = require('wrench'); +var path = require('path'); +var fs = require('fs-extra'); + +describe('hooks :: ', function() { + + var sailsprocess; + + describe('installing a 3rd-party hook', function() { + var appName = 'testApp'; + + before(function() { + appHelper.teardown(); + }); + + describe('into node_modules/sails-hook-shout', function(){ + + before(function(done) { + this.timeout(5000); + fs.mkdirs(path.resolve(__dirname, "../..", appName, "node_modules"), function(err) { + if (err) {return done(err);} + wrench.copyDirSyncRecursive(path.resolve(__dirname, 'fixtures/hooks/installable/shout'), path.resolve(__dirname,'../../testApp/node_modules/sails-hook-shout')); + process.chdir(path.resolve(__dirname, "../..", appName)); + done(); + }); + }); + + after(function() { + process.chdir('../'); + appHelper.teardown(); + }); + + describe('with default settings', function() { + + var sails; + + before(function(done) { + appHelper.liftQuiet(function(err, _sails) { + if (err) {return done(err);} + sails = _sails; + return done(); + }); + }); + + after(function(done) { + sails.lower(done); + }); + + it('should install a hook into `sails.hooks.shout`', function() { + + assert(sails.hooks.shout); + + }); + + it('should use merge the default hook config', function() { + + assert(sails.config.shout.phrase == 'make it rain', sails.config.shout.phrase); + + }); + + it('should bind a /shout route that responds with the default phrase', function(done) { + httpHelper.testRoute('GET', "shout", function(err, resp, body) { + assert(body == 'make it rain'); + return done(); + }); + }); + + }); + + describe('with hook-level config options', function() { + + var sails; + + before(function(done) { + appHelper.liftQuiet({shout: {phrase: "yolo"}}, function(err, _sails) { + if (err) {return done(err);} + sails = _sails; + return done(); + }); + }); + + after(function(done) { + sails.lower(done); + }); + + it('should bind a /shout route that responds with the configured phrase', function(done) { + httpHelper.testRoute('GET', "shout", function(err, resp, body) { + assert(body == 'yolo'); + return done(); + }); + }); + + }); + + describe('setting the config key to `shoutHook`', function() { + + var sails; + + before(function(done) { + appHelper.liftQuiet({installedHooks: {'sails-hook-shout': {configKey: 'shoutHook'}}, shoutHook: {phrase: 'holla back!'}}, function(err, _sails) { + if (err) {return done(err);} + sails = _sails; + return done(); + }); + }); + + after(function(done) { + sails.lower(done); + }); + + + it('should bind a /shout route that responds with the configured phrase', function(done) { + httpHelper.testRoute('GET', "shout", function(err, resp, body) { + assert(body == 'holla back!'); + return done(); + }); + }); + + }); + + describe('setting the hook identity to `foobar`', function(){ + + var sails; + + before(function(done) { + appHelper.liftQuiet({installedHooks: {'sails-hook-shout': {identity: 'foobar'}}}, function(err, _sails) { + if (err) {return done(err);} + sails = _sails; + return done(); + }); + }); + + after(function(done) { + sails.lower(done); + }); + + it('should install a hook into `sails.hooks.foobar`', function() { + + assert(sails.hooks.foobar); + + }); + + it('should use merge the default hook config', function() { + + assert(sails.config.foobar.phrase == 'make it rain', sails.config.foobar.phrase); + + }); + + it('should bind a /shout route that responds with the default phrase', function(done) { + httpHelper.testRoute('GET', "shout", function(err, resp, body) { + assert(body == 'make it rain'); + return done(); + }); + }); + + }); + + describe('setting the hook identity to `views` (an existing hook)', function(){ + + it ('should throw an error', function(done) { + appHelper.liftQuiet({installedHooks: {'sails-hook-shout': {identity: 'views'}}}, function(err, _sails) { + assert(err && err.code == 'E_INVALID_HOOK_NAME'); + done(); + }); + }); + + }); + + + }); + + describe('into node_modules/shouty', function(){ + + before(function(done) { + this.timeout(5000); + fs.mkdirs(path.resolve(__dirname, "../..", appName, "node_modules"), function(err) { + if (err) {return done(err);} + wrench.copyDirSyncRecursive(path.resolve(__dirname, 'fixtures/hooks/installable/shout'), path.resolve(__dirname,'../../testApp/node_modules/shouty')); + process.chdir(path.resolve(__dirname, "../..", appName)); + done(); + }); + }); + + after(function() { + process.chdir('../'); + appHelper.teardown(); + }); + + describe('with default settings', function() { + + var sails; + + before(function(done) { + appHelper.liftQuiet(function(err, _sails) { + if (err) {return done(err);} + sails = _sails; + return done(); + }); + }); + + after(function(done) { + sails.lower(done); + }); + + + it('should install a hook into `sails.hooks.shouty`', function() { + assert(sails.hooks.shouty); + + }); + + it('should use merge the default hook config', function() { + + assert(sails.config.shouty.phrase == 'make it rain', sails.config.shouty.phrase); + + }); + + it('should bind a /shout route that responds with the default phrase', function(done) { + httpHelper.testRoute('GET', "shout", function(err, resp, body) { + assert(body == 'make it rain'); + return done(); + }); + }); + + }); + + }); + + describe('into node_modules/sails-hook-views', function(){ + + before(function(done) { + this.timeout(5000); + fs.mkdirs(path.resolve(__dirname, "../..", appName, "node_modules"), function(err) { + if (err) {return done(err);} + wrench.copyDirSyncRecursive(path.resolve(__dirname, 'fixtures/hooks/installable/shout'), path.resolve(__dirname,'../../testApp/node_modules/sails-hook-views')); + process.chdir(path.resolve(__dirname, "../..", appName)); + done(); + }); + }); + + after(function() { + process.chdir('../'); + appHelper.teardown(); + }); + + it ('should throw an error', function(done) { + appHelper.liftQuiet(function(err, _sails) { + assert(err && err.code == 'E_INVALID_HOOK_NAME'); + done(); + }); + }); + + }); + + + describe('into node_modules/views', function(){ + + before(function(done) { + this.timeout(5000); + fs.mkdirs(path.resolve(__dirname, "../..", appName, "node_modules"), function(err) { + if (err) {return done(err);} + wrench.copyDirSyncRecursive(path.resolve(__dirname, 'fixtures/hooks/installable/shout'), path.resolve(__dirname,'../../testApp/node_modules/views')); + process.chdir(path.resolve(__dirname, "../..", appName)); + done(); + }); + }); + + after(function() { + process.chdir('../'); + appHelper.teardown(); + }); + + it ('should throw an error', function(done) { + appHelper.liftQuiet(function(err, _sails) { + assert(err && err.code == 'E_INVALID_HOOK_NAME'); + done(); + }); + }); + + }); + + }); + +}); From a074834495d58bafaf47be9cda719f6d61647bd0 Mon Sep 17 00:00:00 2001 From: Mike McNeil Date: Fri, 12 Dec 2014 16:51:16 -0600 Subject: [PATCH 067/235] Some setup to allow for separation of concerns in session support. --- lib/router/req.js | 20 +++++++++---- lib/router/res.js | 73 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+), 6 deletions(-) diff --git a/lib/router/req.js b/lib/router/req.js index 29f5be7501..c0468c4e27 100644 --- a/lib/router/req.js +++ b/lib/router/req.js @@ -66,12 +66,6 @@ module.exports = function buildRequest (_req) { // TODO: Load the session // TODO: add all the other methods in core - function FakeSession() { - // TODO: mimic the session store impl in sockets hook - // (all of this can drastically simplify that hook and consolidate - // request interpreter logic) - } - // Set session req.session = (_req && _req.session) || new FakeSession(); @@ -106,3 +100,17 @@ module.exports = function buildRequest (_req) { + +// ////////////////////////////////////////////////////////////////////////// +///// ////////////////////////////////////////////////////////////////////////// +/// || //////////////////////////////////////////////////////////////////////// +///// TODO \/ ///////////////////////////////////////////////////////////////////////////// +//////// ///////////////////////////////////////////////////////////////////////////// +////////// ///////////////////////////////////////////////////////////////////////////// + + +function FakeSession() { + // TODO: mimic the session store impl in sockets hook + // (all of this can drastically simplify that hook and consolidate + // request interpreter logic) +} diff --git a/lib/router/res.js b/lib/router/res.js index c675735f4e..eec1d0e510 100644 --- a/lib/router/res.js +++ b/lib/router/res.js @@ -204,6 +204,31 @@ module.exports = function buildResponse (req, _res) { return res.send(501,'Not implemented in core yet'); }; + + /** + * res.set( headerName, value ) + * + * @param {[type]} headerName [description] + * @param {[type]} value [description] + */ + res.set = function (headerName, value) { + res.headers = res.headers || {}; + res.headers[headerName] = value; + return value; + }; + + /** + * res.get( headerName ) + * + * @param {[type]} headerName [description] + * @return {[type]} [description] + */ + res.get = function (headerName) { + return res.headers && res.headers[headerName]; + }; + + + return res; @@ -269,3 +294,51 @@ MockClientResponse.prototype._transform = function(chunk, encoding, next) { this.push(chunk); next(); }; + + + + + + +// ////////////////////////////////////////////////////////////////////////// +///// ////////////////////////////////////////////////////////////////////////// +/// || //////////////////////////////////////////////////////////////////////// +///// TODO \/ ///////////////////////////////////////////////////////////////////////////// +//////// ///////////////////////////////////////////////////////////////////////////// +////////// ///////////////////////////////////////////////////////////////////////////// + + /** + * @param {Sails} app + */ + +function ToSaveSession(app) { + + /** + * saveSession() + * + * Used to save session automatically when: + * + res.send() or res.json() is called + * + res.redirect() is called + * + TODO: res receives an 'end' event from a stream piped into it + * + * @param {Session} session + * @param {Function} cb + */ + return function saveSession(session, cb) { + + // If we have a session on the request, save it + // Otherwise, process the callback on the next tick + (function selfInvokingFn(next){ + if (session) { + session.save(next); + } else { + process.nextTick(next); + } + })(function eitherWay(err) { + if (err) { + app.log.error('Session could not be persisted. Details:', err); + } + cb(); + }); + }; +} From 67c987abaaba60ad7c3431b033625788dc468313 Mon Sep 17 00:00:00 2001 From: Scott Gress Date: Sun, 14 Dec 2014 12:43:12 -0600 Subject: [PATCH 068/235] Make key for configuring installed hook name "name" instead of "identity" --- lib/hooks/moduleloader/index.js | 4 ++-- test/integration/hook.3rdparty.test.js | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/hooks/moduleloader/index.js b/lib/hooks/moduleloader/index.js index 5116f9ed28..f77fb234a1 100644 --- a/lib/hooks/moduleloader/index.js +++ b/lib/hooks/moduleloader/index.js @@ -398,8 +398,8 @@ module.exports = function(sails) { var hookName; // If an identity was specified in sails.config.installedHooks, use that - if (sails.config.installedHooks && sails.config.installedHooks[identity] && sails.config.installedHooks[identity].identity) { - hookName = sails.config.installedHooks[identity].identity; + if (sails.config.installedHooks && sails.config.installedHooks[identity] && sails.config.installedHooks[identity].name) { + hookName = sails.config.installedHooks[identity].name; } // Otherwise use the module name, with initial "sails-hook" stripped off if it exists else { diff --git a/test/integration/hook.3rdparty.test.js b/test/integration/hook.3rdparty.test.js index 3d52d7bbb9..75beb2a1bb 100644 --- a/test/integration/hook.3rdparty.test.js +++ b/test/integration/hook.3rdparty.test.js @@ -125,12 +125,12 @@ describe('hooks :: ', function() { }); - describe('setting the hook identity to `foobar`', function(){ + describe('setting the hook name to `foobar`', function(){ var sails; before(function(done) { - appHelper.liftQuiet({installedHooks: {'sails-hook-shout': {identity: 'foobar'}}}, function(err, _sails) { + appHelper.liftQuiet({installedHooks: {'sails-hook-shout': {name: 'foobar'}}}, function(err, _sails) { if (err) {return done(err);} sails = _sails; return done(); @@ -162,10 +162,10 @@ describe('hooks :: ', function() { }); - describe('setting the hook identity to `views` (an existing hook)', function(){ + describe('setting the hook name to `views` (an existing hook)', function(){ it ('should throw an error', function(done) { - appHelper.liftQuiet({installedHooks: {'sails-hook-shout': {identity: 'views'}}}, function(err, _sails) { + appHelper.liftQuiet({installedHooks: {'sails-hook-shout': {name: 'views'}}}, function(err, _sails) { assert(err && err.code == 'E_INVALID_HOOK_NAME'); done(); }); From 3b77489c731ade387049a6e1bfe35e92289d2949 Mon Sep 17 00:00:00 2001 From: Scott Gress Date: Sun, 14 Dec 2014 13:34:45 -0600 Subject: [PATCH 069/235] Changed the way hooks load "module loader" and "userconfig" always load first (defaults + configuration() + load()) Then "defaults" are applied for all other hooks Then "configure" is run for all other hooks Then "load" is run for all other hooks --- lib/app/private/loadHooks.js | 95 +++++++++++++++++++++++++++++------- lib/hooks/index.js | 82 +++++-------------------------- lib/hooks/userhooks/index.js | 34 ++++++------- 3 files changed, 105 insertions(+), 106 deletions(-) diff --git a/lib/app/private/loadHooks.js b/lib/app/private/loadHooks.js index 35f9619862..51d36139cb 100644 --- a/lib/app/private/loadHooks.js +++ b/lib/app/private/loadHooks.js @@ -5,7 +5,7 @@ var _ = require('lodash'); var async = require('async'); var __hooks = require('../../hooks'); - +var defaultsDeep = require('merge-defaults'); module.exports = function(sails) { @@ -61,26 +61,85 @@ module.exports = function(sails) { hooks[id] = new Hook(def); }); + // Function to apply a hook's "defaults" obj or function + function applyDefaults(hook) { + // Get the hook defaults + var defaults = (_.isFunction(hook.defaults) ? + hook.defaults(sails.config) : + hook.defaults) || {}; + // Replace the special __configKey__ key with the actual config key + if (hook.defaults.__configKey__ && hook.configKey) { + hook.defaults[hook.configKey] = hook.defaults.__configKey__; + delete hook.defaults.__configKey__; + } + + defaultsDeep(sails.config, defaults); + } + + // Load a hook (bind its routes, load any modules and initialize it) + function loadHook(id, cb) { + hooks[id].load(function(err) { + if (err) { + sails.log.error('A hook (`' + id + '`) failed to load!'); + sails.emit('hook:' + id + ':error'); + return cb(err); + } - // Call `load` on each hook - async.auto({ + sails.log.verbose(id, 'hook loaded successfully.'); + sails.emit('hook:' + id + ':loaded'); + + // Defer to next tick to allow other stuff to happen + process.nextTick(cb); + }); + } + + async.series({ + + // First load the moduleloader (if any) + moduleloader: function(cb) { + if (!hooks.moduleloader) { + return cb(); + } + applyDefaults(hooks['moduleloader']); + hooks['moduleloader'].configure(); + loadHook('moduleloader', cb); + }, + + // Next load the user config (if any) + userconfig: function(cb) { + if (!hooks.userconfig) { + return cb(); + } + applyDefaults(hooks['userconfig']); + hooks['userconfig'].configure(); + loadHook('userconfig', cb); + }, + + // Apply the default config for all other hooks + defaults: function(cb) { + async.each(_.without(_.keys(hooks), 'userconfig', 'moduleloader'), function (id, cb) { + var hook = hooks[id]; + applyDefaults(hook); + // Defer to next tick to allow other stuff to happen + process.nextTick(cb); + }, cb); + }, + + // Run configuration method for all other hooks + configure: function(cb) { + async.each(_.without(_.keys(hooks), 'userconfig', 'moduleloader'), function (id, cb) { + var hook = hooks[id]; + hook.configure(); + // Defer to next tick to allow other stuff to happen + process.nextTick(cb); + }, cb); + }, - initialize: function(cb) { - async.each(_.keys(hooks), function initializeHook(id, cb) { + // Load all other hooks + load: function(cb) { + async.each(_.without(_.keys(hooks), 'userconfig', 'moduleloader'), function (id, cb) { sails.log.silly('Loading hook: ' + id); - hooks[id].load(function(err) { - if (err) { - sails.log.error('A hook (`' + id + '`) failed to load!'); - sails.emit('hook:' + id + ':error'); - return cb(err); - } - - sails.log.verbose(id, 'hook loaded successfully.'); - sails.emit('hook:' + id + ':loaded'); - - // Defer to next tick to allow other stuff to happen - process.nextTick(cb); - }); + loadHook(id, cb); }, cb); } }, diff --git a/lib/hooks/index.js b/lib/hooks/index.js index 9f4a826949..21449fbdc6 100644 --- a/lib/hooks/index.js +++ b/lib/hooks/index.js @@ -58,86 +58,21 @@ module.exports = function(sails) { }); }); - /** - * Apply hook default configuration to sails.config - * - * @api private - */ - function applyDefaults() { - // Get the hook defaults - var defaults = (_.isFunction(self.defaults) ? - self.defaults(sails.config) : - self.defaults) || {}; - // Replace the special __configKey__ key with the actual config key - if (self.defaults.__configKey__ && self.configKey) { - self.defaults[self.configKey] = self.defaults.__configKey__; - delete self.defaults.__configKey__; - } - - defaultsDeep(sails.config, defaults); - } - - - // Run configure and loadModules methods + // Run loadModules method if moduleloader is loaded async.auto({ - // If the `userconfig` hook is enabled, always wait for it - // (unless of course, THIS IS `userconfig`) - configuration: function(cb) { - - if (sails.config.hooks.userconfig) { - - // Can't wait for `userconfig` if this IS `userconfig`!! - // Also, `moduleloader` cannot wait for userconfig. - if (self.identity === 'userconfig' || self.identity === 'moduleloader') { - - applyDefaults(); - self.configure(); - return cb(); - } - - sails.after('hook:userconfig:loaded', function() { - applyDefaults(); - self.configure(); - return cb(); - }); - return; - } - - applyDefaults(); - self.configure(); - return cb(); - }, - - // If the `moduleloader` hook is enabled, always wait for it - // (unless of course, THIS IS `moduleloader`) - modules: ['configuration', - function(cb) { - - // Can't wait for moduleloader if this IS moduleloader!! - if (self.identity === 'moduleloader') { - return cb(); - } + modules: function(cb) { if (sails.config.hooks.moduleloader) { - // Load modules - sails.after('hook:moduleloader:loaded', function moduleloaderReady() { - return self.loadModules(cb); - }); - return; - } + return self.loadModules(cb); + } return cb(); } - ] }, function(err) { if (err) return cb(err); - - // Call initialize() method if one provided - if (self.initialize) { - self.initialize(cb); - } else cb(); + self.initialize(cb); }); }; @@ -173,6 +108,13 @@ module.exports = function(sails) { return cb(); }; + /** + * Hooks may override this function + */ + this.initialize = function(cb) { + return cb(); + }; + /////// TODO: most of the following could be replaced by taking advantage of lodash "merge" diff --git a/lib/hooks/userhooks/index.js b/lib/hooks/userhooks/index.js index 9ba8ac4db9..3afcab5945 100644 --- a/lib/hooks/userhooks/index.js +++ b/lib/hooks/userhooks/index.js @@ -24,32 +24,30 @@ module.exports = function(sails) { } // Wait for moduleloader - sails.after('hook:moduleloader:loaded', function () { - sails.log.verbose('Loading user hooks...'); + sails.log.verbose('Loading user hooks...'); - // Load user hook definitions - sails.modules.loadUserHooks(function hookDefinitionsLoaded(err, hooks) { - if (err) return cb(err); - - // Ensure hooks is valid - hooks = util.isObject(hooks) ? hooks : {}; + // Load user hook definitions + sails.modules.loadUserHooks(function hookDefinitionsLoaded(err, hooks) { + if (err) return cb(err); - sails.log.verbose('Located ' + Object.keys(hooks).length + ' user hook(s)...'); + // Ensure hooks is valid + hooks = util.isObject(hooks) ? hooks : {}; - // Initialize new hooks - initializeHooks(hooks, function (err) { - if (err) return cb(err); + sails.log.verbose('Located ' + Object.keys(hooks).length + ' user hook(s)...'); - // Mix hooks into sails.hooks - util.each(hooks, function (hook, hookID) { - sails.hooks[hookID] = hook; - }); + // Initialize new hooks + initializeHooks(hooks, function (err) { + if (err) return cb(err); - sails.log.verbose('Initialized ' + Object.keys(hooks).length + ' user hook(s)...'); - return cb(); + // Mix hooks into sails.hooks + util.each(hooks, function (hook, hookID) { + sails.hooks[hookID] = hook; }); + sails.log.verbose('Initialized ' + Object.keys(hooks).length + ' user hook(s)...'); + return cb(); }); + }); } }; From 8fc5b8a867529a4fa3fe7e76a4f6cb05403e8c4c Mon Sep 17 00:00:00 2001 From: Scott Gress Date: Sun, 14 Dec 2014 15:06:50 -0600 Subject: [PATCH 070/235] Log errors encountered when lowering Sails --- lib/app/lift.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/app/lift.js b/lib/app/lift.js index 833638f408..88332012ac 100644 --- a/lib/app/lift.js +++ b/lib/app/lift.js @@ -33,12 +33,10 @@ module.exports = function lift(configOverride, cb) { ], function sailsReady(err, async_data) { if (err) { return sails.lower(function (errorLoweringSails){ - - cb(err); - if (errorLoweringSails) { sails.log.error('When trying to lower the app as a result of a failed lift, encountered an error:',errorLoweringSails); } + cb(err); }); } From 28085ddd5e8d5a53425103677d73c1503f25af5a Mon Sep 17 00:00:00 2001 From: Scott Gress Date: Sun, 14 Dec 2014 15:07:27 -0600 Subject: [PATCH 071/235] Smarter error message when Sails fails to lift --- bin/sails-lift.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/bin/sails-lift.js b/bin/sails-lift.js index 609fac7453..14f5206289 100644 --- a/bin/sails-lift.js +++ b/bin/sails-lift.js @@ -63,7 +63,10 @@ module.exports = function() { function afterwards (err, sails) { - if (err) { sails ? sails.log.error(err) : log.error(err); process.exit(1); } + if (err) { + var message = err.stack ? err.stack : err; + sails ? sails.log.error(message) : log.error(message); process.exit(1); + } // try {console.timeEnd('cli_lift');}catch(e){} } }; From 1b934c83204a678f42e5fdbd982538e9783c7dfb Mon Sep 17 00:00:00 2001 From: Scott Gress Date: Sun, 14 Dec 2014 15:11:47 -0600 Subject: [PATCH 072/235] Remove unused "ready" hook property and moved hook timeout logic --- lib/app/load.js | 54 ++++++++---------------------------- lib/app/private/loadHooks.js | 21 +++++++++++++- lib/hooks/index.js | 3 -- lib/hooks/policies/index.js | 2 -- lib/hooks/request/index.js | 2 -- 5 files changed, 31 insertions(+), 51 deletions(-) diff --git a/lib/app/load.js b/lib/app/load.js index c45b20c583..fab9ba37b3 100644 --- a/lib/app/load.js +++ b/lib/app/load.js @@ -185,52 +185,20 @@ module.exports = function(sails) { function ready__(cb) { return function(err) { if (err) { - // sails.log.error('Sails encountered the following error:'); - sails.log.error(err); return cb && cb(err); } - // Wait until all hooks are ready - sails.log.verbose('Waiting for all hooks to declare that they\'re ready...'); - var hookTimeout = setTimeout(function tooLong() { - var hooksTookTooLongErr = 'Hooks are taking way too long to get ready... ' + - 'Something is amiss.\nAre you using any custom hooks?\nIf so, make sure the hook\'s ' + - '`initialize()` method is triggering its callback.'; - sails.log.error(hooksTookTooLongErr); - process.exit(1); - }, 10000); - - var WHILST_POLL_INTERVAL = 75; - - async.whilst( - function checkIfAllHooksAreReady() { - return _.any(sails.hooks, function(hook) { - return !hook.ready; - }); - }, - function waitABit(whilst_cb) { - setTimeout(whilst_cb, WHILST_POLL_INTERVAL); - }, - function hooksLoaded(err) { - clearTimeout(hookTimeout); - if (err) { - var msg = 'Error loading hooks.'; - sails.log.error(msg); - return cb && cb(msg); - } - - sails.log.verbose('All hooks were loaded successfully.'); - - // If userconfig hook is turned off, still load globals. - if (sails.config.hooks && sails.config.hooks.userconfig === false || - (sails.config.loadHooks && sails.config.loadHooks.indexOf('userconfig') == -1)) { - sails.exposeGlobals(); - } - - - cb && cb(null, sails); - } - ); + + sails.log.verbose('All hooks were loaded successfully.'); + + // If userconfig hook is turned off, still load globals. + if (sails.config.hooks && sails.config.hooks.userconfig === false || + (sails.config.loadHooks && sails.config.loadHooks.indexOf('userconfig') == -1)) { + sails.exposeGlobals(); + } + + + cb && cb(null, sails); }; } }; diff --git a/lib/app/private/loadHooks.js b/lib/app/private/loadHooks.js index 51d36139cb..5f8c3d51dd 100644 --- a/lib/app/private/loadHooks.js +++ b/lib/app/private/loadHooks.js @@ -78,9 +78,28 @@ module.exports = function(sails) { // Load a hook (bind its routes, load any modules and initialize it) function loadHook(id, cb) { + + var timeoutInterval = (sails.config[hooks[id].configKey || id] && sails.config[hooks[id].configKey || id]._hookTimeout) || 3000; + var hookTimeout; + if (id != 'userhooks') { + hookTimeout = setTimeout(function tooLong() { + var hooksTookTooLongErr = 'The hook `'+id+'` is taking too long to load.\n' + + 'Make sure it is triggering its `initialize()` callback, ' + + 'or else set `sails.config.' + (hooks[id].configKey || id) + + '._hookTimeout to a higher value (currently ' + timeoutInterval + ')'; + var err = new Error(hooksTookTooLongErr); + err.code = 'E_HOOK_TIMEOUT'; + cb(err); + }, timeoutInterval); + } hooks[id].load(function(err) { + if (id != 'userhooks') { + clearTimeout(hookTimeout); + } if (err) { - sails.log.error('A hook (`' + id + '`) failed to load!'); + if (id != 'userhooks') { + sails.log.error('A hook (`' + id + '`) failed to load!'); + } sails.emit('hook:' + id + ':error'); return cb(err); } diff --git a/lib/hooks/index.js b/lib/hooks/index.js index 21449fbdc6..eb78ef4da6 100644 --- a/lib/hooks/index.js +++ b/lib/hooks/index.js @@ -157,9 +157,6 @@ module.exports = function(sails) { def.routes.before = def.routes.before || {}; def.routes.after = def.routes.after || {}; - // Always set ready to true-- doesn't need to do anything asynchronous - def.ready = true; - return def; } } diff --git a/lib/hooks/policies/index.js b/lib/hooks/policies/index.js index 2cabfd15c3..9eb3f081c7 100644 --- a/lib/hooks/policies/index.js +++ b/lib/hooks/policies/index.js @@ -104,8 +104,6 @@ module.exports = function(sails) { sails.log.verbose('Policy-controller bindings complete!'); sails.emit('hook:policies:bound'); - - this.ready = true; }, diff --git a/lib/hooks/request/index.js b/lib/hooks/request/index.js index a05fe9d817..06ad93a833 100644 --- a/lib/hooks/request/index.js +++ b/lib/hooks/request/index.js @@ -68,8 +68,6 @@ module.exports = function(sails) { }); - this.ready = true; - cb(); }, From 84ce7cf09f6dafa8b7a2362f9edb5cc316d59065 Mon Sep 17 00:00:00 2001 From: Scott Gress Date: Sun, 14 Dec 2014 15:12:40 -0600 Subject: [PATCH 073/235] Set default hook timeout back to 10 seconds --- lib/app/private/loadHooks.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/app/private/loadHooks.js b/lib/app/private/loadHooks.js index 5f8c3d51dd..f05e1fd406 100644 --- a/lib/app/private/loadHooks.js +++ b/lib/app/private/loadHooks.js @@ -79,7 +79,7 @@ module.exports = function(sails) { // Load a hook (bind its routes, load any modules and initialize it) function loadHook(id, cb) { - var timeoutInterval = (sails.config[hooks[id].configKey || id] && sails.config[hooks[id].configKey || id]._hookTimeout) || 3000; + var timeoutInterval = (sails.config[hooks[id].configKey || id] && sails.config[hooks[id].configKey || id]._hookTimeout) || 10000; var hookTimeout; if (id != 'userhooks') { hookTimeout = setTimeout(function tooLong() { From 245c72e433c1aca684d60942e0b467b468e84305 Mon Sep 17 00:00:00 2001 From: Scott Gress Date: Sun, 14 Dec 2014 20:56:08 -0600 Subject: [PATCH 074/235] Default hook's configKey to its identity This will normally be taken care of by the moduleloader, but in case a hook is loaded using `require` while loading Sails programmatically, this will ensure that configuration still works. --- lib/app/private/loadHooks.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/app/private/loadHooks.js b/lib/app/private/loadHooks.js index f05e1fd406..06f25364d8 100644 --- a/lib/app/private/loadHooks.js +++ b/lib/app/private/loadHooks.js @@ -55,7 +55,7 @@ module.exports = function(sails) { // If a config key was defined for this hook when it was loaded, // (probably because a user is overridding the default config key) // set it on the hook definition - def.configKey = hookPrototype.configKey; + def.configKey = hookPrototype.configKey || def.identity; // New up an actual Hook instance hooks[id] = new Hook(def); From 0a3cf5b69c43bb2a68f95b4e994c238669f8f08a Mon Sep 17 00:00:00 2001 From: Mike McNeil Date: Sun, 14 Dec 2014 22:06:12 -0600 Subject: [PATCH 075/235] Significant progress on separating out cookieParsing/session mware for socket requests. --- lib/hooks/session/index.js | 91 +++----- lib/router/index.js | 420 +++++++++++++++++++++++++++++++++++-- lib/router/req.js | 157 +++++++++++++- lib/router/res.js | 62 ++---- 4 files changed, 590 insertions(+), 140 deletions(-) diff --git a/lib/hooks/session/index.js b/lib/hooks/session/index.js index a3e56e368a..1fc5a043e1 100644 --- a/lib/hooks/session/index.js +++ b/lib/hooks/session/index.js @@ -2,13 +2,14 @@ * Module dependencies. */ -var util = require('sails-util'), - uuid = require('node-uuid'), - path = require('path'), - generateSecret = require('./generateSecret'), - cookie = require('express/node_modules/cookie'), - parseSignedCookie = require('cookie-parser').signedCookie, - ConnectSession = require('express/node_modules/connect').middleware.session.Session; +var util = require('sails-util'); +var _ = require('lodash'); +var uuid = require('node-uuid'); +var path = require('path'); +var generateSecret = require('./generateSecret'); +var cookie = require('express/node_modules/cookie'); +var parseSignedCookie = require('cookie-parser').signedCookie; +var ConnectSession = require('express/node_modules/connect').middleware.session.SessionHook; module.exports = function(sails) { @@ -25,49 +26,8 @@ module.exports = function(sails) { - /** - * Prototype for the connect session store wrapper used by the sockets hook. - * Includes a save() method to persist the session data. - */ - function SocketIOSession(options) { - var sid = options.sid, - data = options.data; - - - this.save = function(cb) { - - if (!sid) { - sails.log.error('Trying to save session, but could not determine session ID.'); - sails.log.error('This probably means a requesting socket did not send a cookie.'); - sails.log.error('Usually, this happens when a socket from an old browser tab ' + - ' tries to reconnect.'); - sails.log.error('(this can also occur when trying to connect a cross-origin socket.)'); - if (cb) cb('Could not save session.'); - return; - } - - // Merge data directly into instance to allow easy access on `req.session` later - util.defaults(this, data); - - // Persist session - Session.set(sid, sails.util.cloneDeep(this), function(err) { - - if (err) { - sails.log.error('Could not save session:'); - sails.log.error(err); - } - if (cb) cb(err); - }); - }; - - // Set the data on this object, since it will be used as req.session - util.extend(this, options.data); - } - - - - // Session hook - var Session = { + // `session` hook definition + var SessionHook = { defaults: { @@ -77,6 +37,7 @@ module.exports = function(sails) { } }, + /** * Normalize and validate configuration for this hook. * Then fold any modifications back into `sails.config` @@ -86,7 +47,7 @@ module.exports = function(sails) { // Validate config // Ensure that secret is specified if a custom session store is used if (sails.config.session) { - if (!util.isObject(sails.config.session)) { + if (!_.isObject(sails.config.session)) { throw new Error('Invalid custom session store configuration!\n' + '\n' + 'Basic usage ::\n' + @@ -101,10 +62,10 @@ module.exports = function(sails) { // If session config is set, but secret is undefined, set a secure, one-time use secret if (!sails.config.session || !sails.config.session.secret) { - sails.log.verbose('Session secret not defined-- automatically generating one for now...'); + sails.log.verbose('SessionHook secret not defined-- automatically generating one for now...'); if (sails.config.environment === 'production') { - sails.log.warn('Session secret must be identified!'); + sails.log.warn('SessionHook secret must be identified!'); sails.log.warn('Automatically generating one for now...'); sails.log.error('This generated session secret is NOT OK for production!'); @@ -212,7 +173,7 @@ module.exports = function(sails) { } // Save reference in `sails.session` - sails.session = Session; + sails.session = SessionHook; return cb(); }, @@ -243,7 +204,7 @@ module.exports = function(sails) { }); // Next, persist the new session - Session.set(session.id, session, function(err) { + SessionHook.set(session.id, session, function(err) { if (err) return cb(err); sails.log.verbose('Generated new session (', session.id, ') for socket....'); @@ -262,7 +223,7 @@ module.exports = function(sails) { */ get: function(sessionId, cb) { if (!util.isFunction(cb)) { - throw new Error('Invalid usage :: `Session.get(sessionId, cb)`'); + throw new Error('Invalid usage :: `SessionHook.get(sessionId, cb)`'); } return sails.config.session.store.get(sessionId, cb); }, @@ -317,12 +278,12 @@ module.exports = function(sails) { socket.handshake.sessionID = parseSignedCookie(socket.handshake.cookie[sails.config.session.key], sails.config.session.secret); // Generate and persist a new session in the store - Session.generate(socket.handshake, function(err, sessionData) { + SessionHook.generate(socket.handshake, function(err, sessionData) { if (err) return cb(err); sails.log.silly('socket.handshake.sessionID is now :: ', socket.handshake.sessionID); // Provide access to adapter-agnostic `.save()` - return cb(null, new SocketIOSession({ + return cb(null, new RawSession({ sid: sessionData.id, data: sessionData })); @@ -350,11 +311,11 @@ module.exports = function(sails) { // If sid DOES exist, it's easy to look up in the socket var sid = socket.handshake.sessionID; - // Cache the handshake in case it gets wiped out during the call to Session.get + // Cache the handshake in case it gets wiped out during the call to SessionHook.get var handshake = socket.handshake; // Retrieve session data from store - Session.get(sid, function(err, sessionData) { + SessionHook.get(sid, function(err, sessionData) { if (err) { sails.log.error('Error retrieving session from socket.'); @@ -370,11 +331,11 @@ module.exports = function(sails) { sails.log.verbose('A socket (' + socket.id + ') is trying to connect with an invalid or expired session ID (' + sid + ').'); sails.log.verbose('Regnerating empty session...'); - Session.generate(handshake, function(err, sessionData) { + SessionHook.generate(handshake, function(err, sessionData) { if (err) return cb(err); // Provide access to adapter-agnostic `.save()` - return cb(null, new SocketIOSession({ + return cb(null, new RawSession({ sid: sessionData.id, data: sessionData })); @@ -383,9 +344,9 @@ module.exports = function(sails) { // Otherwise session exists and everything is ok. - // Instantiate SocketIOSession (provides .save() method) + // Instantiate RawSession (provides .save() method) // And extend it with session data - else return cb(null, new SocketIOSession({ + else return cb(null, new RawSession({ data: sessionData, sid: sid })); @@ -394,5 +355,5 @@ module.exports = function(sails) { }; - return Session; + return SessionHook; }; diff --git a/lib/router/index.js b/lib/router/index.js index 4321d3efe6..282154b679 100644 --- a/lib/router/index.js +++ b/lib/router/index.js @@ -7,12 +7,15 @@ var Readable = require('stream').Readable; var Writable = require('stream').Writable; var QS = require('querystring'); var _ = require('lodash'); -var express = require('express'); +var Express = require('express'); var buildReq = require('./req'); var buildRes = require('./res'); var defaultHandlers = require('./bindDefaultHandlers'); +var parseSignedCookie = require('cookie-parser').signedCookie; +var Cookie = require('express/node_modules/cookie'); +var ConnectSession = require('express/node_modules/connect').middleware.session.Session; /** * Expose new instance of `Router` @@ -45,7 +48,7 @@ function Router(options) { // TODO: // instead, use: https://www.npmjs.org/package/path-to-regexp // (or: https://www.npmjs.org/package/path-match) - this._privateRouter = express(); + this._privateRouter = Express(); // Bind the context of all instance methods this.load = _.bind(this.load, this); @@ -153,6 +156,7 @@ Router.prototype.route = function(req, res) { req = buildReq(req, res); res = buildRes(req, res); + console.log('Received request to %s',req.url); // Deprecation error: res._cb = function noRouteCbSpecified(err) { @@ -167,31 +171,46 @@ Router.prototype.route = function(req, res) { return res.send(400, err && err.stack); } - sails.log.silly('Handling virtual request :: Running virtual body parser...'); - bodyParser(req,res, function (err) { + // Parse cookies + parseCookies(req, res, function(err){ if (err) { return res.send(400, err && err.stack); } - // Use our private router to route the request - _privateRouter.router(req, res, function handleUnmatchedNext(err) { - // - // In the event of an unmatched `next()`, `next('foo')`, - // or `next('foo', errorCode)`... - // - - // Use the default server error handler + // Load session (if relevant) + loadSession(req, res, function (err) { if (err) { - sails.log.silly('Handling virtual request :: Running final "error" handler...'); - sails.emit('router:request:500', err, req, res); - return; + return res.send(400, err && err.stack); } - // Or the default not found handler - sails.log.silly('Handling virtual request :: Running final "not found" handler...'); - sails.emit('router:request:404', req, res); - return; + sails.log.silly('Handling virtual request :: Running virtual body parser...'); + bodyParser(req,res, function (err) { + if (err) { + return res.send(400, err && err.stack); + } + + // Use our private router to route the request + _privateRouter.router(req, res, function handleUnmatchedNext(err) { + // + // In the event of an unmatched `next()`, `next('foo')`, + // or `next('foo', errorCode)`... + // + + // Use the default server error handler + if (err) { + sails.log.silly('Handling virtual request :: Running final "error" handler...'); + sails.emit('router:request:500', err, req, res); + return; + } + + // Or the default not found handler + sails.log.silly('Handling virtual request :: Running final "not found" handler...'); + sails.emit('router:request:404', req, res); + return; + }); + }); }); + }); }); @@ -310,6 +329,23 @@ Router.prototype.flush = function(routes) { + + + + + + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// +// || Private functions +// \/ +// +//////////////////////////////////////////////////////////////////////////////////////////////////// + + + + + // Extremely simple query string parser (`req.query`) function qsParser(req,res,next) { var queryStringPos = req.url.indexOf('?'); @@ -351,3 +387,349 @@ function bodyParser (req, res, next) { next(); }); } + + + + + + + +/** + * [parseCookies description] + * @param {[type]} req [description] + * @param {[type]} res [description] + * @param {Function} next [description] + * @return {[type]} [description] + */ +function parseCookies (req, res, next){ + + // TODO: this line of code does not need to be run for every request + var _cookieParser = Express.cookieParser(req._sails && req._sails.config.session && req._sails.config.session.secret); + + // Run the middleware + return _cookieParser(req, res, next); +} + + + +/** + * [loadSession description] + * @param {[type]} req [description] + * @param {[type]} res [description] + * @param {Function} next [description] + * @return {[type]} [description] + */ +function loadSession (req, res, next){ + + + // If a Connect session store is configured, hook it up as req.session + if (req._sails.config.session && req._sails.config.session.store) { + + // TODO: this line of code does not need to be run for every request + var _sessionMiddleware = Express.session(req._sails.config.session); + + // Run the middleware + return _sessionMiddleware(req, res, next); + } + + // Otherwise don't even worry about it. + return next(); +} + + + + + + + + +// /** +// * Create a session transaction +// * +// * Load data from the session store using the `sessionID` as parsed from the cookie. +// * Mix-in the session.save() method for persisting the data back to the session store. +// * +// * Functionality is roughly equivalent to that of Connect's sessionStore middleware. +// */ + +// function loadSession (req, cb) { + +// // If no cookie was provided, we'll give up trying to load the session. +// // Stub out a fake Session object so that its methods exist, etc. +// if (!req.headers.cookie) { +// req._sails.log.verbose('Could not load session for request, because no cookie was provided.'); +// req._sails.log.verbose('This will result in an empty session, i.e. (req.session === {})'); +// req.session = {}; +// return cb(); +// } + +// // If sid doesn't exit in socket, we have to do a little work first to get it +// // (or generate a new one-- and therefore a new empty session as well) +// if (!socket.handshake.sessionID && !socket.handshake.headers.cookie) { + +// // If no cookie exists, generate a random one (this will create a new session!) +// var generatedCookie = req._sails.config.session.key + '=' + uuid.v1(); +// req.headers.cookie = generatedCookie; +// req._sails.log.verbose('Could not fetch session, since connecting socket (', socket.id, ') has no cookie.'); +// req._sails.log.verbose('Is this a cross-origin socket..?)'); +// req._sails.log.verbose('Generated a one-time-use cookie:', generatedCookie); +// req._sails.log.verbose('This will result in an empty session, i.e. (req.session === {})'); + + +// // Convert cookie into `sid` using session secret +// // Maintain sid in socket so that the session can be queried before processing each incoming message +// socket.handshake.cookie = cookie.parse(generatedCookie); +// // Parse and decrypt cookie and save it in the socket.handshake +// socket.handshake.sessionID = parseSignedCookie(socket.handshake.cookie[sails.config.session.key], sails.config.session.secret); + +// // Generate and persist a new session in the store +// SessionHook.generate(socket.handshake, function(err, sessionData) { +// if (err) return cb(err); +// sails.log.silly('socket.handshake.sessionID is now :: ', socket.handshake.sessionID); + +// // Provide access to adapter-agnostic `.save()` +// return cb(null, new RawSession({ +// sid: sessionData.id, +// data: sessionData +// })); +// }); +// return; +// } + + +// try { +// // Convert cookie into `sid` using session secret +// // Maintain sid in socket so that the session can be queried before processing each incoming message +// socket.handshake.cookie = cookie.parse(socket.handshake.headers.cookie); +// // Parse and decrypt cookie and save it in the socket.handshake +// socket.handshake.sessionID = parseSignedCookie(socket.handshake.cookie[sails.config.session.key], sails.config.session.secret); +// } catch (e) { +// sails.log.error('Could not load session for socket #' + socket.id); +// sails.log.error('The socket\'s cookie could not be parsed into a sessionID.'); +// sails.log.error('Unless you\'re overriding the `authorization` function, make sure ' + +// 'you pass in a valid `' + sails.config.session.key + '` cookie'); +// sails.log.error('(or omit the cookie altogether to have a new session created and an ' + +// 'encrypted cookie sent in the response header to your socket.io upgrade request)'); +// return cb(e); +// } + +// // If sid DOES exist, it's easy to look up in the socket +// var sid = socket.handshake.sessionID; + +// // Cache the handshake in case it gets wiped out during the call to SessionHook.get +// var handshake = socket.handshake; + +// // Retrieve session data from store +// SessionHook.get(sid, function(err, sessionData) { + +// if (err) { +// sails.log.error('Error retrieving session from socket.'); +// return cb(err); +// } + +// // sid is not known-- the session secret probably changed +// // Or maybe server restarted and it was: +// // (a) using an auto-generated secret, or +// // (b) using the session memory store +// // and so it doesn't recognize the socket's session ID. +// else if (!sessionData) { +// sails.log.verbose('A socket (' + socket.id + ') is trying to connect with an invalid or expired session ID (' + sid + ').'); +// sails.log.verbose('Regnerating empty session...'); + +// SessionHook.generate(handshake, function(err, sessionData) { +// if (err) return cb(err); + +// // Provide access to adapter-agnostic `.save()` +// return cb(null, new RawSession({ +// sid: sessionData.id, +// data: sessionData +// })); +// }); +// } + +// // Otherwise session exists and everything is ok. + +// // Instantiate RawSession (provides .save() method) +// // And extend it with session data +// else return cb(null, new RawSession({ +// data: sessionData, +// sid: sid +// })); +// }); +// } + + + + + +// /** +// * Constructor for the connect session store wrapper used by the sockets hook. +// * Includes a save() method to persist the session data. +// */ +// function RawSession(options) { +// var sid = options.sid; +// var data = options.data; + + +// /** +// * [save description] +// * @param {Function} cb [description] +// * @return {[type]} [description] +// */ +// this.save = function(cb) { + +// if (!sid) { +// return _.isFunction(cb) && cb((function (){ +// var err = new Error('Could not save session'); +// err.code = 'E_SESSION_SAVE'; +// err.details = 'Trying to save session, but could not determine session ID.\n'+ +// 'This probably means a requesting socket from socket.io did not send a cookie.\n'+ +// 'Usually, this happens when a socket from an old browser tab tries to reconnect.\n'+ +// '(this can also occur when trying to connect a cross-origin socket.)'; +// return err; +// })()); +// } + +// // Merge data directly into instance to allow easy access on `req.session` later +// _.defaults(this, data); + +// // Persist session +// SessionHook.set(sid, _.cloneDeep(this), function(err) { +// if (err) { +// return _.isFunction(cb) && cb((function (){ +// err.code = 'E_SESSION_SAVE'; +// return err; +// })()); +// } +// return _.isFunction(cb) && cb(); +// }); +// }; + +// // Set the data on this object, since it will be used as req.session +// util.extend(this, options.data); +// } + + + + + + + + +// /** +// * [loadSession description] +// * @param {[type]} req [description] +// * @param {Function} cb [description] +// * @return {[type]} [description] +// */ +// function loadSession (req, cb){ + +// if (!req._sails || !req._sails.config.session) { +// req.session = {}; +// (req._sails && req._sails.log && req._sails.log.verbose || console.log)('Skipping session...'); +// return cb(); +// } + +// // Populate req.session using shared session store +// sails.session.fromSocket(req.socket, function sessionReady (err, session) { +// if (err) return cb(err); + +// // Provide access to session data as req.session +// req.session = session || {}; + +// return cb(); +// }); + +// // console.log('About to try and parse cookie...'); + +// // // Decrypt cookie into session id using session secret to get `sessionID`. +// // // +// // // (this allows us to query the session before processing each incoming message from this +// // // socket in the future) +// // var cookie; +// // var sessionID; +// // try { + +// // if (!req.headers.cookie) { +// // return cb((function _buildError(){ +// // var err = new Error('No cookie sent with request'); +// // return err; +// // })()); +// // } + +// // cookie = Cookie.parse(req.headers.cookie); + +// // if (!req._sails.config.session.key) { +// // return cb((function _buildError(){ +// // var err = new Error('No session key configured (sails.config.session.key)'); +// // return err; +// // })()); +// // } +// // if (!req._sails.config.session.secret) { +// // return cb((function _buildError(){ +// // var err = new Error('No session secret configured (sails.config.session.secret)'); +// // return err; +// // }())); +// // } + +// // sessionID = parseSignedCookie(cookie[req._sails.config.session.key], req._sails.config.session.secret); +// // } catch (e) { +// // return cb((function _buildError(){ +// // var err = new Error('Cannot load session. Cookie could not be parsed:\n'+util.inspect(e&&e.stack)); +// // err.code = 'E_PARSE_COOKIE'; +// // err.status = 400; +// // return err; +// // })()); +// // } + +// // // Look up this socket's session id in the Connect session store +// // // and see if we already have a record of 'em. +// // req._sails.session.get(sessionID, function(err, sessionData) { + +// // // An error occurred, so refuse the connection +// // if (err) { +// // return cb('Error loading session during socket connection! \n' + util.inspect(err, false, null)); +// // } + +// // // Cookie is present (there is a session id), but it doesn't +// // // correspond to a known session in the session store. +// // // So generate a new, blank session. +// // if (!sessionData) { +// // var newSession = new ConnectSession({sessionID: sessionID}, { +// // cookie: { +// // // Prevent access from client-side javascript +// // httpOnly: true +// // } +// // }); +// // req._sails.log.verbose("Generated new session for socket....", {sessionID: sessionID}); +// // req.session = newSession; +// // return cb(); +// // } + +// // // Parsed cookie matches a known session- onward! +// // // +// // // Instantiate a session object, passing our just-acquired session handshake +// // var existingSession = new ConnectSession({sessionID: sessionID}, sessionData); +// // req._sails.log.verbose("Connected socket to existing session...."); +// // req.session = existingSession; +// // cb(); +// // }); +// } + + + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// When err.code === E_PARSE_COOKIE +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// var TROUBLESHOOTING = +// 'Perhaps you have an old browser tab open? In that case, you can ignore this warning.' + '\n' + +// 'Otherwise, are you are trying to access your Sails.js ' + '\n' + +// 'server from a socket.io javascript client hosted on a 3rd party domain? ' + '\n' + +// ' *-> You can override the cookie for a user entirely by setting ?cookie=... in the querystring of ' + '\n' + +// 'your socket.io connection url on the client.' + '\n' + +// ' *-> You can send a JSONP request first from the javascript client on the other domain ' + '\n' + +// 'to the Sails.js server to get the cookie, then connect the socket.' + '\n'; +// var socketSpecificMsg = +// 'Unable to parse the cookie that was transmitted for an incoming socket.io connect request.'+ +// '\n' + TROUBLESHOOTING + '\n'+ +// ''; diff --git a/lib/router/req.js b/lib/router/req.js index c0468c4e27..6631e2a038 100644 --- a/lib/router/req.js +++ b/lib/router/req.js @@ -1,6 +1,8 @@ /** * Module dependencies */ + +var util = require('util'); var _ = require('lodash'); var defaultsDeep = require('merge-defaults'); var MockReq = require('mock-req'); @@ -63,11 +65,10 @@ module.exports = function buildRequest (_req) { // Track request start time req._startTime = new Date(); - // TODO: Load the session // TODO: add all the other methods in core - // Set session - req.session = (_req && _req.session) || new FakeSession(); + // Set session (or build up a fake one) + req.session = _req.session || {}; // Provide defaults for other request state and methods req = defaultsDeep(req, { @@ -109,8 +110,148 @@ module.exports = function buildRequest (_req) { ////////// ///////////////////////////////////////////////////////////////////////////// -function FakeSession() { - // TODO: mimic the session store impl in sockets hook - // (all of this can drastically simplify that hook and consolidate - // request interpreter logic) -} +// function FakeSession() { +// // TODO: mimic the session store impl in sockets hook +// // (all of this can drastically simplify that hook and consolidate +// // request interpreter logic) +// } + + + + + +// function ToLoadSession(sails) { + + +// /** +// * Module dependencies. +// */ + +// var util = require('util'); +// var parseSignedCookie = require('cookie-parser').signedCookie; +// var cookie = require('express/node_modules/cookie'); +// var ConnectSession = require('express/node_modules/connect').middleware.session.Session; +// var getSDKMetadata = require('./getSDKMetadata'); + + + +// /** +// * Fired after a socket is connected +// */ + +// return function socketAttemptingToConnect(handshake, accept) { + +// // If a cookie override was provided in the query string, use it. +// // (e.g. ?cookie=sails.sid=a4g8dsgajsdgadsgasd) +// if (handshake.query.cookie) { +// handshake.headers.cookie = handshake.query.cookie; +// } + +// var sdk = getSDKMetadata(handshake); +// sails.log.verbose(util.format('%s client (v%s) is trying to connect a socket...', sdk.platform, sdk.version)); + +// var TROUBLESHOOTING = +// 'Perhaps you have an old browser tab open? In that case, you can ignore this warning.' + '\n' + +// 'Otherwise, are you are trying to access your Sails.js ' + '\n' + +// 'server from a socket.io javascript client hosted on a 3rd party domain? ' + '\n' + +// ' *-> You can override the cookie for a user entirely by setting ?cookie=... in the querystring of ' + '\n' + +// 'your socket.io connection url on the client.' + '\n' + +// ' *-> You can send a JSONP request first from the javascript client on the other domain ' + '\n' + +// 'to the Sails.js server to get the cookie first, then connect the socket.' + '\n' + +// ' *-> For complete customizability, to override the built-in session assignment logic in Sails ' + '\n' + +// 'for socket.io requests, you can override socket.io\'s `authorization` logic with your own function ' + '\n' + +// 'in `config/sockets.js`. ' + '\n' + +// 'Or disable authorization for incoming socket connection requests entirely by setting `authorization: false`.' + '\n'; + + +// // Parse and decrypt cookie and save it in the handshake +// if (!handshake.headers.cookie) { +// return socketConnectionError(accept, +// 'Cannot load session for an incoming socket.io connection... ' + '\n' + +// 'No cookie was sent!\n' + +// TROUBLESHOOTING, +// 'Cannot load session. No cookie transmitted.' +// ); +// } + +// // Decrypt cookie into session id using session secret +// // Maintain sessionID in socket handshake so that the session +// // can be queried before processing each incoming message from this +// // socket in the future. +// try { +// handshake.cookie = cookie.parse(handshake.headers.cookie); +// handshake.sessionID = parseSignedCookie(handshake.cookie[sails.config.session.key], sails.config.session.secret); +// } catch (e) { +// return socketConnectionError(accept, +// 'Unable to parse the cookie that was transmitted for an incoming socket.io connect request:\n' + +// util.inspect(e) + '\n' + TROUBLESHOOTING, +// 'Cannot load session. Cookie could not be parsed.' +// ); +// } + + +// // Look up this socket's session id in the Connect session store +// // and see if we already have a record of 'em. +// sails.session.get(handshake.sessionID, function(err, session) { + +// // An error occurred, so refuse the connection +// if (err) { +// return socketConnectionError(accept, +// 'Error loading session during socket connection! \n' + err, +// 'Error loading session.'); +// } + +// // Cookie is present (there is a session id), but it doesn't +// // correspond to a known session in the session store. +// // So generate a new, blank session. +// else if (!session) { +// handshake.session = new ConnectSession(handshake, { +// cookie: { +// // Prevent access from client-side javascript +// httpOnly: true +// } +// }); +// sails.log.verbose("Generated new session for socket....", handshake); + +// // TO_TEST: +// // do we need to set handshake.sessionID with the id of the new session? + +// // TO_TEST: +// // do we need to revoke/replace the cookie as well? +// // how can we do that w/ socket.io? +// // can we access the response headers in the http UPGRADE response? +// // or should we just clear the cookie from the handshake and call it good? +// // e.g +// // var date = new Date(); +// // date.setTime(date.getTime()+(days*24*60*60*1000)); +// // var expires = "; expires="+date.toGMTString(); +// // handshake.headers.cookie = name+"="+value+expires+"; path=/"; + +// accept(null, true); +// } + +// // Parsed cookie matches a known session- onward! +// else { + +// // Create a session object, passing our just-acquired session handshake +// handshake.session = new ConnectSession(handshake, session); +// sails.log.verbose("Connected socket to existing session...."); +// accept(null, true); +// } +// }); +// }; + + +// /** +// * Fired when an internal server error occurs while authorizing the socket +// */ + +// function socketConnectionError(accept, devMsg, prodMsg) { +// var msg; +// if (sails.config.environment === 'development') { +// msg = devMsg; +// } else msg = prodMsg; +// return accept(msg, false); +// } + +// }; diff --git a/lib/router/res.js b/lib/router/res.js index eec1d0e510..4e1498d640 100644 --- a/lib/router/res.js +++ b/lib/router/res.js @@ -53,6 +53,20 @@ module.exports = function buildResponse (req, _res) { res._clientRes = new MockClientResponse(); } + // Save session + if (_.isObject(req.session) && _.isFunction(req.session.save)) { + req.session.save(function (err){ + if (err) { + if (req._sails && req._sails.log){ + req._sails.log.error('Session could not be persisted. Details:', err); + } + else { + console.error(err); + } + } + }); + } + // The stream should trigger the callback when it finishes or errors. res._clientRes.on('finish', function() { return res._clientCallback(res._clientRes); @@ -294,51 +308,3 @@ MockClientResponse.prototype._transform = function(chunk, encoding, next) { this.push(chunk); next(); }; - - - - - - -// ////////////////////////////////////////////////////////////////////////// -///// ////////////////////////////////////////////////////////////////////////// -/// || //////////////////////////////////////////////////////////////////////// -///// TODO \/ ///////////////////////////////////////////////////////////////////////////// -//////// ///////////////////////////////////////////////////////////////////////////// -////////// ///////////////////////////////////////////////////////////////////////////// - - /** - * @param {Sails} app - */ - -function ToSaveSession(app) { - - /** - * saveSession() - * - * Used to save session automatically when: - * + res.send() or res.json() is called - * + res.redirect() is called - * + TODO: res receives an 'end' event from a stream piped into it - * - * @param {Session} session - * @param {Function} cb - */ - return function saveSession(session, cb) { - - // If we have a session on the request, save it - // Otherwise, process the callback on the next tick - (function selfInvokingFn(next){ - if (session) { - session.save(next); - } else { - process.nextTick(next); - } - })(function eitherWay(err) { - if (err) { - app.log.error('Session could not be persisted. Details:', err); - } - cb(); - }); - }; -} From 7cbb510219382e74ef79cd1ffc6d0a741d40ce1c Mon Sep 17 00:00:00 2001 From: Mike McNeil Date: Sun, 14 Dec 2014 22:28:59 -0600 Subject: [PATCH 076/235] Strip undefined headers in interpreter. --- lib/hooks/session/index.js | 222 ++++++++++++++++++------------------- lib/router/index.js | 2 + lib/router/req.js | 7 ++ lib/router/res.js | 2 + 4 files changed, 122 insertions(+), 111 deletions(-) diff --git a/lib/hooks/session/index.js b/lib/hooks/session/index.js index 1fc5a043e1..8692cd215e 100644 --- a/lib/hooks/session/index.js +++ b/lib/hooks/session/index.js @@ -9,7 +9,7 @@ var path = require('path'); var generateSecret = require('./generateSecret'); var cookie = require('express/node_modules/cookie'); var parseSignedCookie = require('cookie-parser').signedCookie; -var ConnectSession = require('express/node_modules/connect').middleware.session.SessionHook; +var ConnectSession = require('express/node_modules/connect').middleware.session.Session; module.exports = function(sails) { @@ -242,116 +242,116 @@ module.exports = function(sails) { - /** - * Create a session transaction - * - * Load the Connect session data using the sessionID in the socket.io handshake object - * Mix-in the session.save() method for persisting the data back to the session store. - * - * Functionally equivalent to connect's sessionStore middleware. - */ - - fromSocket: function(socket, cb) { - - // If a socket makes it here, even though its associated session is not specified, - // it's authorized as far as the app is concerned, so no need to do that again. - // Instead, use the cookie to look up the sid, and then the sid to look up the session data - - - // If sid doesn't exit in socket, we have to do a little work first to get it - // (or generate a new one-- and therefore a new empty session as well) - if (!socket.handshake.sessionID && !socket.handshake.headers.cookie) { - - // If no cookie exists, generate a random one (this will create a new session!) - var generatedCookie = sails.config.session.key + '=' + uuid.v1(); - socket.handshake.headers.cookie = generatedCookie; - sails.log.verbose('Could not fetch session, since connecting socket (', socket.id, ') has no cookie.'); - sails.log.verbose('Is this a cross-origin socket..?)'); - sails.log.verbose('Generated a one-time-use cookie:', generatedCookie); - sails.log.verbose('This will result in an empty session, i.e. (req.session === {})'); - - - // Convert cookie into `sid` using session secret - // Maintain sid in socket so that the session can be queried before processing each incoming message - socket.handshake.cookie = cookie.parse(generatedCookie); - // Parse and decrypt cookie and save it in the socket.handshake - socket.handshake.sessionID = parseSignedCookie(socket.handshake.cookie[sails.config.session.key], sails.config.session.secret); - - // Generate and persist a new session in the store - SessionHook.generate(socket.handshake, function(err, sessionData) { - if (err) return cb(err); - sails.log.silly('socket.handshake.sessionID is now :: ', socket.handshake.sessionID); - - // Provide access to adapter-agnostic `.save()` - return cb(null, new RawSession({ - sid: sessionData.id, - data: sessionData - })); - }); - return; - } - - - try { - // Convert cookie into `sid` using session secret - // Maintain sid in socket so that the session can be queried before processing each incoming message - socket.handshake.cookie = cookie.parse(socket.handshake.headers.cookie); - // Parse and decrypt cookie and save it in the socket.handshake - socket.handshake.sessionID = parseSignedCookie(socket.handshake.cookie[sails.config.session.key], sails.config.session.secret); - } catch (e) { - sails.log.error('Could not load session for socket #' + socket.id); - sails.log.error('The socket\'s cookie could not be parsed into a sessionID.'); - sails.log.error('Unless you\'re overriding the `authorization` function, make sure ' + - 'you pass in a valid `' + sails.config.session.key + '` cookie'); - sails.log.error('(or omit the cookie altogether to have a new session created and an ' + - 'encrypted cookie sent in the response header to your socket.io upgrade request)'); - return cb(e); - } - - // If sid DOES exist, it's easy to look up in the socket - var sid = socket.handshake.sessionID; - - // Cache the handshake in case it gets wiped out during the call to SessionHook.get - var handshake = socket.handshake; - - // Retrieve session data from store - SessionHook.get(sid, function(err, sessionData) { - - if (err) { - sails.log.error('Error retrieving session from socket.'); - return cb(err); - } - - // sid is not known-- the session secret probably changed - // Or maybe server restarted and it was: - // (a) using an auto-generated secret, or - // (b) using the session memory store - // and so it doesn't recognize the socket's session ID. - else if (!sessionData) { - sails.log.verbose('A socket (' + socket.id + ') is trying to connect with an invalid or expired session ID (' + sid + ').'); - sails.log.verbose('Regnerating empty session...'); - - SessionHook.generate(handshake, function(err, sessionData) { - if (err) return cb(err); - - // Provide access to adapter-agnostic `.save()` - return cb(null, new RawSession({ - sid: sessionData.id, - data: sessionData - })); - }); - } - - // Otherwise session exists and everything is ok. - - // Instantiate RawSession (provides .save() method) - // And extend it with session data - else return cb(null, new RawSession({ - data: sessionData, - sid: sid - })); - }); - } + // /** + // * Create a session transaction + // * + // * Load the Connect session data using the sessionID in the socket.io handshake object + // * Mix-in the session.save() method for persisting the data back to the session store. + // * + // * Functionally equivalent to connect's sessionStore middleware. + // */ + + // fromSocket: function(socket, cb) { + + // // If a socket makes it here, even though its associated session is not specified, + // // it's authorized as far as the app is concerned, so no need to do that again. + // // Instead, use the cookie to look up the sid, and then the sid to look up the session data + + + // // If sid doesn't exit in socket, we have to do a little work first to get it + // // (or generate a new one-- and therefore a new empty session as well) + // if (!socket.handshake.sessionID && !socket.handshake.headers.cookie) { + + // // If no cookie exists, generate a random one (this will create a new session!) + // var generatedCookie = sails.config.session.key + '=' + uuid.v1(); + // socket.handshake.headers.cookie = generatedCookie; + // sails.log.verbose('Could not fetch session, since connecting socket (', socket.id, ') has no cookie.'); + // sails.log.verbose('Is this a cross-origin socket..?)'); + // sails.log.verbose('Generated a one-time-use cookie:', generatedCookie); + // sails.log.verbose('This will result in an empty session, i.e. (req.session === {})'); + + + // // Convert cookie into `sid` using session secret + // // Maintain sid in socket so that the session can be queried before processing each incoming message + // socket.handshake.cookie = cookie.parse(generatedCookie); + // // Parse and decrypt cookie and save it in the socket.handshake + // socket.handshake.sessionID = parseSignedCookie(socket.handshake.cookie[sails.config.session.key], sails.config.session.secret); + + // // Generate and persist a new session in the store + // SessionHook.generate(socket.handshake, function(err, sessionData) { + // if (err) return cb(err); + // sails.log.silly('socket.handshake.sessionID is now :: ', socket.handshake.sessionID); + + // // Provide access to adapter-agnostic `.save()` + // return cb(null, new RawSession({ + // sid: sessionData.id, + // data: sessionData + // })); + // }); + // return; + // } + + + // try { + // // Convert cookie into `sid` using session secret + // // Maintain sid in socket so that the session can be queried before processing each incoming message + // socket.handshake.cookie = cookie.parse(socket.handshake.headers.cookie); + // // Parse and decrypt cookie and save it in the socket.handshake + // socket.handshake.sessionID = parseSignedCookie(socket.handshake.cookie[sails.config.session.key], sails.config.session.secret); + // } catch (e) { + // sails.log.error('Could not load session for socket #' + socket.id); + // sails.log.error('The socket\'s cookie could not be parsed into a sessionID.'); + // sails.log.error('Unless you\'re overriding the `authorization` function, make sure ' + + // 'you pass in a valid `' + sails.config.session.key + '` cookie'); + // sails.log.error('(or omit the cookie altogether to have a new session created and an ' + + // 'encrypted cookie sent in the response header to your socket.io upgrade request)'); + // return cb(e); + // } + + // // If sid DOES exist, it's easy to look up in the socket + // var sid = socket.handshake.sessionID; + + // // Cache the handshake in case it gets wiped out during the call to SessionHook.get + // var handshake = socket.handshake; + + // // Retrieve session data from store + // SessionHook.get(sid, function(err, sessionData) { + + // if (err) { + // sails.log.error('Error retrieving session from socket.'); + // return cb(err); + // } + + // // sid is not known-- the session secret probably changed + // // Or maybe server restarted and it was: + // // (a) using an auto-generated secret, or + // // (b) using the session memory store + // // and so it doesn't recognize the socket's session ID. + // else if (!sessionData) { + // sails.log.verbose('A socket (' + socket.id + ') is trying to connect with an invalid or expired session ID (' + sid + ').'); + // sails.log.verbose('Regnerating empty session...'); + + // SessionHook.generate(handshake, function(err, sessionData) { + // if (err) return cb(err); + + // // Provide access to adapter-agnostic `.save()` + // return cb(null, new RawSession({ + // sid: sessionData.id, + // data: sessionData + // })); + // }); + // } + + // // Otherwise session exists and everything is ok. + + // // Instantiate RawSession (provides .save() method) + // // And extend it with session data + // else return cb(null, new RawSession({ + // data: sessionData, + // sid: sid + // })); + // }); + // } }; diff --git a/lib/router/index.js b/lib/router/index.js index 282154b679..8257807751 100644 --- a/lib/router/index.js +++ b/lib/router/index.js @@ -403,6 +403,8 @@ function bodyParser (req, res, next) { */ function parseCookies (req, res, next){ + console.log('ATTEMPTING TO PARSE COOKIE-headers::',req.headers); + // TODO: this line of code does not need to be run for every request var _cookieParser = Express.cookieParser(req._sails && req._sails.config.session && req._sails.config.session.secret); diff --git a/lib/router/req.js b/lib/router/req.js index 6631e2a038..2f02157804 100644 --- a/lib/router/req.js +++ b/lib/router/req.js @@ -34,6 +34,13 @@ module.exports = function buildRequest (_req) { // TODO: send a PR to mock-req with a fix for this if (_req.headers && typeof _req.headers === 'object') { + // Strip undefined headers + _.each(_req.headers, function (headerVal, headerKey) { + if (_.isUndefined(headerVal)){ + delete _req.headers[headerKey]; + } + }); + // Make sure all remaining headers are strings _req.headers = _.mapValues(_req.headers, function (headerVal, headerKey) { if (typeof headerVal !== 'string') { headerVal = ''+headerVal+''; diff --git a/lib/router/res.js b/lib/router/res.js index 4e1498d640..0e4cf60cd9 100644 --- a/lib/router/res.js +++ b/lib/router/res.js @@ -57,6 +57,8 @@ module.exports = function buildResponse (req, _res) { if (_.isObject(req.session) && _.isFunction(req.session.save)) { req.session.save(function (err){ if (err) { + err = _.isObject(err) ? err : new Error(err); + err.code = 'E_SESSION_SAVE'; if (req._sails && req._sails.log){ req._sails.log.error('Session could not be persisted. Details:', err); } From 58e93f5a5f2e667e3fbeddf5b4b356f813e3555e Mon Sep 17 00:00:00 2001 From: Mike McNeil Date: Mon, 15 Dec 2014 10:05:15 -0600 Subject: [PATCH 077/235] Use express-session directly in core interepeter --- lib/router/index.js | 21 +++++++++++++++++---- lib/router/req.js | 2 +- lib/router/res.js | 34 ++++++++++++++++++++-------------- 3 files changed, 38 insertions(+), 19 deletions(-) diff --git a/lib/router/index.js b/lib/router/index.js index 8257807751..74c39bb785 100644 --- a/lib/router/index.js +++ b/lib/router/index.js @@ -8,14 +8,12 @@ var Writable = require('stream').Writable; var QS = require('querystring'); var _ = require('lodash'); var Express = require('express'); +var uuid = require('node-uuid'); var buildReq = require('./req'); var buildRes = require('./res'); var defaultHandlers = require('./bindDefaultHandlers'); -var parseSignedCookie = require('cookie-parser').signedCookie; -var Cookie = require('express/node_modules/cookie'); -var ConnectSession = require('express/node_modules/connect').middleware.session.Session; /** * Expose new instance of `Router` @@ -183,6 +181,8 @@ Router.prototype.route = function(req, res) { return res.send(400, err && err.stack); } + console.log('Session is now: ',req.session); + sails.log.silly('Handling virtual request :: Running virtual body parser...'); bodyParser(req,res, function (err) { if (err) { @@ -403,9 +403,19 @@ function bodyParser (req, res, next) { */ function parseCookies (req, res, next){ - console.log('ATTEMPTING TO PARSE COOKIE-headers::',req.headers); + // If no cookie exists, generate a random one + // (this will create a new session w/ every request) + if (!req.headers.cookie) { + try { + req.headers.cookie = req._sails.config.session.key + '=' + uuid.v1(); + } + catch (e){ + req._sails.log.verbose('Failed to generate new cookie. Error:',e); + } + } // TODO: this line of code does not need to be run for every request + req._sails.log.verbose('Parsing cookie:',req.headers.cookie); var _cookieParser = Express.cookieParser(req._sails && req._sails.config.session && req._sails.config.session.secret); // Run the middleware @@ -428,6 +438,9 @@ function loadSession (req, res, next){ if (req._sails.config.session && req._sails.config.session.store) { // TODO: this line of code does not need to be run for every request + req._sails.log.verbose('Running session middleware on interpreted virtual request with cookie:', req.headers.cookie); + console.log('Running session middleware on interpreted virtual request with cookie:', req.headers.cookie); + console.log('Session config:',req._sails.config.session); var _sessionMiddleware = Express.session(req._sails.config.session); // Run the middleware diff --git a/lib/router/req.js b/lib/router/req.js index 2f02157804..8ffa7260ba 100644 --- a/lib/router/req.js +++ b/lib/router/req.js @@ -75,7 +75,7 @@ module.exports = function buildRequest (_req) { // TODO: add all the other methods in core // Set session (or build up a fake one) - req.session = _req.session || {}; + // req.session = _req.session || {}; // Provide defaults for other request state and methods req = defaultsDeep(req, { diff --git a/lib/router/res.js b/lib/router/res.js index 0e4cf60cd9..be4127fa99 100644 --- a/lib/router/res.js +++ b/lib/router/res.js @@ -54,23 +54,24 @@ module.exports = function buildResponse (req, _res) { } // Save session - if (_.isObject(req.session) && _.isFunction(req.session.save)) { - req.session.save(function (err){ - if (err) { - err = _.isObject(err) ? err : new Error(err); - err.code = 'E_SESSION_SAVE'; - if (req._sails && req._sails.log){ - req._sails.log.error('Session could not be persisted. Details:', err); - } - else { - console.error(err); - } - } - }); - } + // if (_.isObject(req.session) && _.isFunction(req.session.save)) { + // req.session.save(function (err){ + // if (err) { + // err = _.isObject(err) ? err : new Error(err); + // err.code = 'E_SESSION_SAVE'; + // if (req._sails && req._sails.log){ + // req._sails.log.error('Session could not be persisted. Details:', err); + // } + // else { + // console.error(err); + // } + // } + // }); + // } // The stream should trigger the callback when it finishes or errors. res._clientRes.on('finish', function() { + console.log('Finished sending response- headers =',res.headers); return res._clientCallback(res._clientRes); }); res._clientRes.on('error', function(err) { @@ -115,11 +116,14 @@ module.exports = function buildResponse (req, _res) { return; } + console.log('Calling res.send() :: Session is now: ',req.session); + // Ensure charset is set res.charset = res.charset || 'utf-8'; // Ensure headers are set res.headers = res.headers || {}; + res.emit('header'); // Ensure statusCode is set // (override `this.statusCode` if `statusCode` argument specified) @@ -139,6 +143,8 @@ module.exports = function buildResponse (req, _res) { // to the provider of that stream. // (this has to happen in `send()` because the code/headers might have just changed) if (res._clientRes) { + + // TODO: try `res.emit('header')` to trigger .on('header') listeners res._clientRes.headers = res.headers; res._clientRes.statusCode = res.statusCode; } From 056f042db3e6e6285c40afc01795866ce9b18f9d Mon Sep 17 00:00:00 2001 From: Mike McNeil Date: Mon, 15 Dec 2014 10:12:44 -0600 Subject: [PATCH 078/235] Added req.session test. --- test/unit/req.session.test.js | 56 +++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 test/unit/req.session.test.js diff --git a/test/unit/req.session.test.js b/test/unit/req.session.test.js new file mode 100644 index 0000000000..2e965be8c9 --- /dev/null +++ b/test/unit/req.session.test.js @@ -0,0 +1,56 @@ +/** + * Module dependencies + */ + +var assert = require('assert'); +var util = require('util'); +var _ = require('lodash'); +var async = require('async'); +var Sails = require('../../lib').Sails; + + +describe('req.session', function (){ + + var app; + + before(function (done){ + app = Sails(); + app.load({ + globals: false, + loadHooks: [ + 'moduleloader', + 'userconfig', + 'http', + 'views' + ] + }, done); + }); + + + describe('when responding to a virtual request',function (){ + + + it('should exist', function (done) { + var doesSessionExist; + var isSessionAnObject; + app.get('/dogs', function (req, res){ + doesSessionExist = !!req.session; + isSessionAnObject = _.isObject(req.session); + res.send(); + }); + app.request('get /dogs', {}, function (err, res, body){ + if (err) return done(err); + if (res.statusCode !== 200) return done(new Error('Expected 200 status code')); + if (!doesSessionExist) return done(new Error('req.session should exist.')); + if (!isSessionAnObject) return done(new Error('req.session should be an object.')); + return done(); + }); + }); + }); + + after(function (done) { + done(); + }); + +}); + From 2f917be7e10b887c9f8c8f043973706f3f064147 Mon Sep 17 00:00:00 2001 From: Mike McNeil Date: Mon, 15 Dec 2014 10:20:13 -0600 Subject: [PATCH 079/235] Allow (opts,cb)-style usage of sails.request() --- lib/app/request.js | 39 ++++++++++++++++++++++++----------- test/unit/req.session.test.js | 20 ++++++++++++------ 2 files changed, 41 insertions(+), 18 deletions(-) diff --git a/lib/app/request.js b/lib/app/request.js index e920a6eda7..68932ca129 100644 --- a/lib/app/request.js +++ b/lib/app/request.js @@ -9,7 +9,7 @@ var Transform = require('stream').Transform; /** - * Originate a new request instance and lob it at this Sails + * Originate a new client request instance and lob it at this Sails * app at the specified route `address`. * * Particularly useful for running unit/integration tests without @@ -35,19 +35,33 @@ module.exports = function request( /* address, body, cb */ ) { // - // - // TODO: - // Support other methods on req and res, and parse the querystring. - // Implement basic bodyParser shim for non-http requests for consistency - // in testing and usage. - // (i.e. merge w/ lib/router/req.js and lib/hooks/sockets/lib/interpreter/*.js) - // - - // Normalize usage var address = arguments[0]; var body; var cb; + + var method; + var headers; + var url; + + // Usage: + // sails.request(opts, cb) + // • opts.url + // • opts.method + // • opts.params + // • opts.headers + // + // (`opts.url` is required) + if (_.isObject(arguments[0]) && arguments[0].url) { + url = arguments[0].url; + method = arguments[0].method; + headers = arguments[0].headers || {}; + params = arguments[0].params || {}; + } + + + // Usage: + // sails.request(address, [params], cb) if (arguments[2]) { cb = arguments[2]; body = arguments[1]; @@ -59,9 +73,10 @@ module.exports = function request( /* address, body, cb */ ) { } // If route has an HTTP verb (e.g. `get /foo/bar`, `put /bar/foo`, etc.) parse it out, - var method = sails.util.detectVerb(address).verb; + // (unless method or url was explicitly defined) + method = method || sails.util.detectVerb(address).verb; method = method ? method.toUpperCase() : 'GET'; - var url = sails.util.detectVerb(address).original; + url = url || sails.util.detectVerb(address).original; // Parse query string (`req.query`) var queryStringPos = url.indexOf('?'); diff --git a/test/unit/req.session.test.js b/test/unit/req.session.test.js index 2e965be8c9..51999ed239 100644 --- a/test/unit/req.session.test.js +++ b/test/unit/req.session.test.js @@ -9,7 +9,7 @@ var async = require('async'); var Sails = require('../../lib').Sails; -describe('req.session', function (){ +describe('req.session (with no session hook)', function (){ var app; @@ -19,10 +19,13 @@ describe('req.session', function (){ globals: false, loadHooks: [ 'moduleloader', - 'userconfig', - 'http', - 'views' - ] + 'userconfig' + ], + session: { + adapter: 'memory', + key: 'sails.sid', + secret: 'af9442683372850a85a87150c47b4a31' + } }, done); }); @@ -38,7 +41,12 @@ describe('req.session', function (){ isSessionAnObject = _.isObject(req.session); res.send(); }); - app.request('get /dogs', {}, function (err, res, body){ + app.request({ + url: '/dogs', + method: 'GET', + params: {}, + headers: {} + }, function (err, res, body){ if (err) return done(err); if (res.statusCode !== 200) return done(new Error('Expected 200 status code')); if (!doesSessionExist) return done(new Error('req.session should exist.')); From ee40c964acbc3deca285d49554541665b7ac55ca Mon Sep 17 00:00:00 2001 From: Mike McNeil Date: Mon, 15 Dec 2014 10:24:37 -0600 Subject: [PATCH 080/235] Logs, named default res._cb as '_cbIsDeprecated' --- lib/router/index.js | 22 ++++++++++++---------- lib/router/res.js | 1 - 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/lib/router/index.js b/lib/router/index.js index 74c39bb785..9bca2b88f6 100644 --- a/lib/router/index.js +++ b/lib/router/index.js @@ -157,7 +157,7 @@ Router.prototype.route = function(req, res) { console.log('Received request to %s',req.url); // Deprecation error: - res._cb = function noRouteCbSpecified(err) { + res._cb = function _cbIsDeprecated(err) { throw new Error('As of v0.10, `_cb()` shim is no longer supported in the Sails router.'); }; @@ -180,7 +180,8 @@ Router.prototype.route = function(req, res) { if (err) { return res.send(400, err && err.stack); } - + console.log('res is now:\n',res); + console.log('\n\n'); console.log('Session is now: ',req.session); sails.log.silly('Handling virtual request :: Running virtual body parser...'); @@ -405,17 +406,18 @@ function parseCookies (req, res, next){ // If no cookie exists, generate a random one // (this will create a new session w/ every request) - if (!req.headers.cookie) { - try { - req.headers.cookie = req._sails.config.session.key + '=' + uuid.v1(); - } - catch (e){ - req._sails.log.verbose('Failed to generate new cookie. Error:',e); - } - } + // if (!req.headers.cookie) { + // try { + // req.headers.cookie = req._sails.config.session.key + '=' + uuid.v1(); + // } + // catch (e){ + // req._sails.log.verbose('Failed to generate new cookie. Error:',e); + // } + // } // TODO: this line of code does not need to be run for every request req._sails.log.verbose('Parsing cookie:',req.headers.cookie); + console.log('Parsing cookie:',req.headers.cookie); var _cookieParser = Express.cookieParser(req._sails && req._sails.config.session && req._sails.config.session.secret); // Run the middleware diff --git a/lib/router/res.js b/lib/router/res.js index be4127fa99..77ca1f72ca 100644 --- a/lib/router/res.js +++ b/lib/router/res.js @@ -123,7 +123,6 @@ module.exports = function buildResponse (req, _res) { // Ensure headers are set res.headers = res.headers || {}; - res.emit('header'); // Ensure statusCode is set // (override `this.statusCode` if `statusCode` argument specified) From 4c76af537842f9a139b3e435308b1bf2bd85bad5 Mon Sep 17 00:00:00 2001 From: Mike McNeil Date: Mon, 15 Dec 2014 10:25:59 -0600 Subject: [PATCH 081/235] Logs, named default res._cb as '_cbIsDeprecated' --- lib/router/index.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/router/index.js b/lib/router/index.js index 9bca2b88f6..b81f62e982 100644 --- a/lib/router/index.js +++ b/lib/router/index.js @@ -175,6 +175,8 @@ Router.prototype.route = function(req, res) { return res.send(400, err && err.stack); } + console.log('Ran cookie parser Req is now::\n',req); + // Load session (if relevant) loadSession(req, res, function (err) { if (err) { From 5b16647730cced18572c3072d8ad0264aa0f4ad0 Mon Sep 17 00:00:00 2001 From: Mike McNeil Date: Mon, 15 Dec 2014 10:35:01 -0600 Subject: [PATCH 082/235] First raw session test passes --- lib/router/index.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/router/index.js b/lib/router/index.js index b81f62e982..b32d82e985 100644 --- a/lib/router/index.js +++ b/lib/router/index.js @@ -175,16 +175,16 @@ Router.prototype.route = function(req, res) { return res.send(400, err && err.stack); } - console.log('Ran cookie parser Req is now::\n',req); + console.log('Ran cookie parser'); // Load session (if relevant) loadSession(req, res, function (err) { if (err) { return res.send(400, err && err.stack); } - console.log('res is now:\n',res); + // console.log('res is now:\n',res); console.log('\n\n'); - console.log('Session is now: ',req.session); + console.log('Ran session middleware. Now req.session= ',req.session); sails.log.silly('Handling virtual request :: Running virtual body parser...'); bodyParser(req,res, function (err) { @@ -437,9 +437,12 @@ function parseCookies (req, res, next){ */ function loadSession (req, res, next){ + // console.log('Session config:',req._sails.config.session); // If a Connect session store is configured, hook it up as req.session - if (req._sails.config.session && req._sails.config.session.store) { + // if (req._sails.config.session && req._sails.config.session.store) { + + if (req._sails.config.session) { // TODO: this line of code does not need to be run for every request req._sails.log.verbose('Running session middleware on interpreted virtual request with cookie:', req.headers.cookie); From ba9618af56f93515b4f2b893a304d516651d9c95 Mon Sep 17 00:00:00 2001 From: sgress454 Date: Mon, 15 Dec 2014 11:05:40 -0600 Subject: [PATCH 083/235] Changed owner of "multiple blueprint prefixes" feature --- ROADMAP.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ROADMAP.md b/ROADMAP.md index ba981d3d70..7abe29aa60 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -47,7 +47,7 @@ The backlog consists of features which are not currently in the immediate-term r Feature | Owner | Details :---------------------------------------------- | :------------------------------------------------- | :------ Watch+reload controllers, models, etc. w/o re-lifting | [@jbielick](https://github.com/jbielick) | Reload controllers/models/config/services/etc. without restarting the server. Show a "rebuilding" page while re-bootstrapping. - Support for multiple blueprint prefixes | [@mnaughto](https://github.com/mnaughto) | https://github.com/balderdashy/sails/issues/2031 + Support for multiple blueprint prefixes | [@mnaughto](https://github.com/konstantinzolotarev) | https://github.com/balderdashy/sails/issues/2031 SPDY protocol support | [@mikermcneil](https://github.com/mikermcneil) | https://github.com/balderdashy/sails/issues/80 Sockets hook: drop-in Primus alternative | [@alejandroiglesias](https://github.com/alejandroiglesias) | https://github.com/balderdashy/sails/issues/945 Have a `sails migrate` or `sails create-db` command | [@globegitter](https://github.com/Globegitter) | For production environments it would be nice to have a save/secure command that creates the db automatically for you From 5e33b163f745174473537629fc0cffafc5d9029b Mon Sep 17 00:00:00 2001 From: Mike McNeil Date: Mon, 15 Dec 2014 11:07:18 -0600 Subject: [PATCH 084/235] Added more session tests --- lib/router/res.js | 2 +- test/unit/req.session.test.js | 41 ++++++++++++++++++++++++++++++----- 2 files changed, 37 insertions(+), 6 deletions(-) diff --git a/lib/router/res.js b/lib/router/res.js index 77ca1f72ca..e36175d79a 100644 --- a/lib/router/res.js +++ b/lib/router/res.js @@ -71,7 +71,6 @@ module.exports = function buildResponse (req, _res) { // The stream should trigger the callback when it finishes or errors. res._clientRes.on('finish', function() { - console.log('Finished sending response- headers =',res.headers); return res._clientCallback(res._clientRes); }); res._clientRes.on('error', function(err) { @@ -117,6 +116,7 @@ module.exports = function buildResponse (req, _res) { } console.log('Calling res.send() :: Session is now: ',req.session); + console.log('Calling res.send() :: res.headers are now: ',res.headers); // Ensure charset is set res.charset = res.charset || 'utf-8'; diff --git a/test/unit/req.session.test.js b/test/unit/req.session.test.js index 51999ed239..8a03971ab6 100644 --- a/test/unit/req.session.test.js +++ b/test/unit/req.session.test.js @@ -32,17 +32,46 @@ describe('req.session (with no session hook)', function (){ describe('when responding to a virtual request',function (){ + var doesSessionExist; + var isSessionAnObject; + var doesTestPropertyStillExist; - it('should exist', function (done) { - var doesSessionExist; - var isSessionAnObject; - app.get('/dogs', function (req, res){ + before(function setupTestRoute(){ + app.post('/sessionTest', function (req, res){ + doesSessionExist = !!req.session; + isSessionAnObject = _.isObject(req.session); + req.session.something = 'some string'; + res.send(); + }); + + app.get('/sessionTest', function (req, res){ doesSessionExist = !!req.session; isSessionAnObject = _.isObject(req.session); + doesTestPropertyStillExist = req.session.something === 'some string'; res.send(); }); + + }); + + it('should exist', function (done) { app.request({ - url: '/dogs', + url: '/sessionTest', + method: 'POST', + params: {}, + headers: {} + }, function (err, res, body){ + if (err) return done(err); + if (res.statusCode !== 200) return done(new Error('Expected 200 status code')); + if (!doesSessionExist) return done(new Error('req.session should exist.')); + if (!isSessionAnObject) return done(new Error('req.session should be an object.')); + console.log('res.headers',res.headers); + return done(); + }); + }); + + it('should persist data between requests', function (done){ + app.request({ + url: '/sessionTest', method: 'GET', params: {}, headers: {} @@ -51,9 +80,11 @@ describe('req.session (with no session hook)', function (){ if (res.statusCode !== 200) return done(new Error('Expected 200 status code')); if (!doesSessionExist) return done(new Error('req.session should exist.')); if (!isSessionAnObject) return done(new Error('req.session should be an object.')); + if (!doesTestPropertyStillExist) return done(new Error('`req.session.something` should still exist for subsequent requests.')); return done(); }); }); + }); after(function (done) { From 312de0d9d349a4b9b9a54a0abd4d027b97400cd3 Mon Sep 17 00:00:00 2001 From: Mike McNeil Date: Mon, 15 Dec 2014 11:30:02 -0600 Subject: [PATCH 085/235] Added res.setHeader and res.writeHead() --- lib/router/index.js | 2 ++ lib/router/res.js | 45 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/lib/router/index.js b/lib/router/index.js index b32d82e985..76b059eebd 100644 --- a/lib/router/index.js +++ b/lib/router/index.js @@ -176,6 +176,7 @@ Router.prototype.route = function(req, res) { } console.log('Ran cookie parser'); + // console.log('res.writeHead= ',res.writeHead); // Load session (if relevant) loadSession(req, res, function (err) { @@ -185,6 +186,7 @@ Router.prototype.route = function(req, res) { // console.log('res is now:\n',res); console.log('\n\n'); console.log('Ran session middleware. Now req.session= ',req.session); + console.log('req.sessionID= ',req.sessionID); sails.log.silly('Handling virtual request :: Running virtual body parser...'); bodyParser(req,res, function (err) { diff --git a/lib/router/res.js b/lib/router/res.js index e36175d79a..a83dd63314 100644 --- a/lib/router/res.js +++ b/lib/router/res.js @@ -93,6 +93,51 @@ module.exports = function buildResponse (req, _res) { // // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + + // res.writeHead() is wrapped in closure by the `on-header` module, + // but it still needs the underlying impl + res.writeHead = function ( /* statusCode, [reasonPhrase], headers */) { + console.log('res.writeHead(%s)', Array.prototype.slice.call(arguments)); + var statusCode = +arguments[0]; + var reasonPhrase = (function(){ + if (arguments[2] && _.isString(arguments[1])) { + return arguments[1]; + } + return undefined; + })(); + var newHeaders = (function (){ + if (arguments[2] && _.isObject(arguments[2])) { + return arguments[2]; + } + return arguments[1]; + })(); + + if (!statusCode) { + throw new Error('`statusCode` must be passed to res.writeHead().'); + } + if (!_.isObject(newHeaders)) { + throw new Error('`headers` must be passed to res.writeHead() as an object.'); + } + + // Set status code + res.statusCode = statusCode; + + // Set new headers + _.extend(res.headers, newHeaders); + }; + + + // But instead we need `setHeader` + // see http://nodejs.org/api/http.html#http_response_setheader_name_value + // + // Usage: + // response.setHeader("Set-Cookie", ["type=ninja", "language=javascript"]); + res.setHeader = function (headerName, value){ + console.log('set header',headerName, '=',value); + res.headers[headerName] = value; + }; + // res.status() res.status = res.status || function status_shim (statusCode) { res.statusCode = statusCode; From 2a73ab2f6c4f0e360f1f99e086155967be42beea Mon Sep 17 00:00:00 2001 From: Mike McNeil Date: Mon, 15 Dec 2014 11:47:47 -0600 Subject: [PATCH 086/235] res.writeHead() works --- lib/router/res.js | 46 +++++++++++++++++++++++++++++++++++----------- 1 file changed, 35 insertions(+), 11 deletions(-) diff --git a/lib/router/res.js b/lib/router/res.js index a83dd63314..434c8ea5ea 100644 --- a/lib/router/res.js +++ b/lib/router/res.js @@ -94,6 +94,9 @@ module.exports = function buildResponse (req, _res) { // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // Track whether headers have been written + // (TODO: pull all this into mock-res via a PR) + var headersWritten = false; // res.writeHead() is wrapped in closure by the `on-header` module, // but it still needs the underlying impl @@ -116,27 +119,48 @@ module.exports = function buildResponse (req, _res) { if (!statusCode) { throw new Error('`statusCode` must be passed to res.writeHead().'); } - if (!_.isObject(newHeaders)) { - throw new Error('`headers` must be passed to res.writeHead() as an object.'); - } - // Set status code res.statusCode = statusCode; - // Set new headers - _.extend(res.headers, newHeaders); + + if (newHeaders) { + if (!_.isObject(newHeaders)) { + throw new Error('`headers` must be passed to res.writeHead() as an object. Got: '+util.inspect(newHeaders, false, null)); + } + // Set new headers + _.extend(res.headers, newHeaders); + } + + // Flag headers as written. + headersWritten = true; + }; - // But instead we need `setHeader` + + // Wrap res.write() and res.end() to get them to call writeHead() + var prevWrite = res.write; + res.write = function (){ + res.writeHead(res.statusCode, res.headers); + prevWrite.apply(res, arguments); + }; + var prevEnd = res.end; + res.end = function (){ + res.writeHead(res.statusCode, res.headers); + prevEnd.apply(res, arguments); + }; + + + + // we get `setHeader` from mock-res // see http://nodejs.org/api/http.html#http_response_setheader_name_value // // Usage: // response.setHeader("Set-Cookie", ["type=ninja", "language=javascript"]); - res.setHeader = function (headerName, value){ - console.log('set header',headerName, '=',value); - res.headers[headerName] = value; - }; + // res.setHeader = function (headerName, value){ + // console.log('set header',headerName, '=',value); + // res.headers[headerName] = value; + // }; // res.status() res.status = res.status || function status_shim (statusCode) { From af12034248f49c5c1c3deefa0c5d9464cc90ac68 Mon Sep 17 00:00:00 2001 From: Mike McNeil Date: Mon, 15 Dec 2014 12:48:59 -0600 Subject: [PATCH 087/235] fix logic error in new write/end overrides. --- lib/router/res.js | 18 +++++++++++------- test/unit/req.session.test.js | 1 + 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/lib/router/res.js b/lib/router/res.js index 434c8ea5ea..3b1b150ae8 100644 --- a/lib/router/res.js +++ b/lib/router/res.js @@ -137,17 +137,23 @@ module.exports = function buildResponse (req, _res) { }; + var prevSetHeader = res.setHeader; + res.setHeader = function (){ + console.log('called res.setHeader() w/ args=',arguments); + prevSetHeader.apply(res, Array.prototype.slice.call(arguments)); + }; // Wrap res.write() and res.end() to get them to call writeHead() var prevWrite = res.write; res.write = function (){ - res.writeHead(res.statusCode, res.headers); - prevWrite.apply(res, arguments); + res.writeHead(res.statusCode, res._headers); + console.log('res.write():: res.headers=',res.headers); + prevWrite.apply(res, Array.prototype.slice.call(arguments)); }; var prevEnd = res.end; res.end = function (){ - res.writeHead(res.statusCode, res.headers); - prevEnd.apply(res, arguments); + res.writeHead(res.statusCode, res._headers); + prevEnd.apply(res, Array.prototype.slice.call(arguments)); }; @@ -184,14 +190,12 @@ module.exports = function buildResponse (req, _res) { return; } - console.log('Calling res.send() :: Session is now: ',req.session); - console.log('Calling res.send() :: res.headers are now: ',res.headers); // Ensure charset is set res.charset = res.charset || 'utf-8'; // Ensure headers are set - res.headers = res.headers || {}; + res.headers = _.extend(res.headers || {}, res._headers || {}); // Ensure statusCode is set // (override `this.statusCode` if `statusCode` argument specified) diff --git a/test/unit/req.session.test.js b/test/unit/req.session.test.js index 8a03971ab6..db713ff83d 100644 --- a/test/unit/req.session.test.js +++ b/test/unit/req.session.test.js @@ -76,6 +76,7 @@ describe('req.session (with no session hook)', function (){ params: {}, headers: {} }, function (err, res, body){ + console.log('headers in final clientRes :', res.headers); if (err) return done(err); if (res.statusCode !== 200) return done(new Error('Expected 200 status code')); if (!doesSessionExist) return done(new Error('req.session should exist.')); From d97bb71a8a7cc413b56d74e2d7b9ef3f0f1b1d98 Mon Sep 17 00:00:00 2001 From: Mike McNeil Date: Mon, 15 Dec 2014 13:08:22 -0600 Subject: [PATCH 088/235] Headers now passed to res._clientRes (also comment out logs) --- lib/app/request.js | 2 ++ lib/router/index.js | 10 +++---- lib/router/res.js | 63 ++++++++++++++++++++++++--------------------- 3 files changed, 40 insertions(+), 35 deletions(-) diff --git a/lib/app/request.js b/lib/app/request.js index 68932ca129..487d582780 100644 --- a/lib/app/request.js +++ b/lib/app/request.js @@ -100,6 +100,8 @@ module.exports = function request( /* address, body, cb */ ) { var clientRes = new MockClientResponse(); clientRes.on('finish', function() { + // console.log('clientRes finished. Headers:',clientRes.headers); + // Only dump the buffer if a callback was supplied if (cb) { clientRes.body = Buffer.concat(clientRes._readableState.buffer).toString(); diff --git a/lib/router/index.js b/lib/router/index.js index 76b059eebd..a4b91cdc5e 100644 --- a/lib/router/index.js +++ b/lib/router/index.js @@ -175,7 +175,7 @@ Router.prototype.route = function(req, res) { return res.send(400, err && err.stack); } - console.log('Ran cookie parser'); + // console.log('Ran cookie parser'); // console.log('res.writeHead= ',res.writeHead); // Load session (if relevant) @@ -184,9 +184,9 @@ Router.prototype.route = function(req, res) { return res.send(400, err && err.stack); } // console.log('res is now:\n',res); - console.log('\n\n'); - console.log('Ran session middleware. Now req.session= ',req.session); - console.log('req.sessionID= ',req.sessionID); + // console.log('\n\n'); + // console.log('Ran session middleware. Now req.session= ',req.session); + // console.log('req.sessionID= ',req.sessionID); sails.log.silly('Handling virtual request :: Running virtual body parser...'); bodyParser(req,res, function (err) { @@ -421,7 +421,7 @@ function parseCookies (req, res, next){ // TODO: this line of code does not need to be run for every request req._sails.log.verbose('Parsing cookie:',req.headers.cookie); - console.log('Parsing cookie:',req.headers.cookie); + // console.log('Parsing cookie:',req.headers.cookie); var _cookieParser = Express.cookieParser(req._sails && req._sails.config.session && req._sails.config.session.secret); // Run the middleware diff --git a/lib/router/res.js b/lib/router/res.js index 3b1b150ae8..b61fd61b3b 100644 --- a/lib/router/res.js +++ b/lib/router/res.js @@ -35,7 +35,7 @@ module.exports = function buildResponse (req, _res) { // Ensure res.headers and res.locals exist. // res = _.merge(res, {locals: {}, headers: {}}, _res); - res = _.extend(res, {locals: {}, headers: {}}); + res = _.extend(res, {locals: {}, headers: {}, _headers: {}}); res = _.extend(res, _res); // Now that we're sure `res` is a Transform stream, we'll handle the two different @@ -101,7 +101,7 @@ module.exports = function buildResponse (req, _res) { // res.writeHead() is wrapped in closure by the `on-header` module, // but it still needs the underlying impl res.writeHead = function ( /* statusCode, [reasonPhrase], headers */) { - console.log('res.writeHead(%s)', Array.prototype.slice.call(arguments)); + // console.log('\n\n• res.writeHead(%s)', Array.prototype.slice.call(arguments)); var statusCode = +arguments[0]; var reasonPhrase = (function(){ if (arguments[2] && _.isString(arguments[1])) { @@ -122,6 +122,8 @@ module.exports = function buildResponse (req, _res) { // Set status code res.statusCode = statusCode; + // Ensure `._headers` have been merged into `.headers` + _.extend(res.headers, res._headers); if (newHeaders) { if (!_.isObject(newHeaders)) { @@ -131,6 +133,16 @@ module.exports = function buildResponse (req, _res) { _.extend(res.headers, newHeaders); } + // Set status code and headers on the `_clientRes` stream so they are accessible + // to the provider of that stream. + // (this has to happen in `send()` because the code/headers might have just changed) + if (res._clientRes) { + // console.log('Setting headers on clientRes- res.headers = ',res.headers); + res._clientRes.headers = res.headers; + res._clientRes.statusCode = res.statusCode; + } + + // Flag headers as written. headersWritten = true; @@ -139,20 +151,21 @@ module.exports = function buildResponse (req, _res) { var prevSetHeader = res.setHeader; res.setHeader = function (){ - console.log('called res.setHeader() w/ args=',arguments); + // console.log('called res.setHeader() w/ args=',arguments); prevSetHeader.apply(res, Array.prototype.slice.call(arguments)); }; // Wrap res.write() and res.end() to get them to call writeHead() var prevWrite = res.write; res.write = function (){ - res.writeHead(res.statusCode, res._headers); - console.log('res.write():: res.headers=',res.headers); + res.writeHead(res.statusCode, _.extend(res._headers,res.headers)); + // console.log('res.write():: called writeHead with headers=',_.extend(res._headers,res.headers)); prevWrite.apply(res, Array.prototype.slice.call(arguments)); }; var prevEnd = res.end; res.end = function (){ - res.writeHead(res.statusCode, res._headers); + res.writeHead(res.statusCode, _.extend(res._headers,res.headers)); + // console.log('res.end():: called writeHead with headers=',_.extend(res._headers,res.headers)); prevEnd.apply(res, Array.prototype.slice.call(arguments)); }; @@ -186,6 +199,7 @@ module.exports = function buildResponse (req, _res) { req._sails.log.error(e); return; } + // TODO: use debug() console.error(e); return; } @@ -195,7 +209,7 @@ module.exports = function buildResponse (req, _res) { res.charset = res.charset || 'utf-8'; // Ensure headers are set - res.headers = _.extend(res.headers || {}, res._headers || {}); + _.extend(res.headers, res._headers); // Ensure statusCode is set // (override `this.statusCode` if `statusCode` argument specified) @@ -205,20 +219,10 @@ module.exports = function buildResponse (req, _res) { if (res._clientCallback) { // Manually plug in res.body. - res.body = args.other; - res._clientRes.body = res.body; // (but don't include body if it is empty) - if (!res.body) delete res.body; - if (!res._clientRes.body) delete res._clientRes.body; - - // Set status code and headers on the `_clientRes` stream so they are accessible - // to the provider of that stream. - // (this has to happen in `send()` because the code/headers might have just changed) - if (res._clientRes) { - - // TODO: try `res.emit('header')` to trigger .on('header') listeners - res._clientRes.headers = res.headers; - res._clientRes.statusCode = res.statusCode; + if (res.body) { + res.body = args.other; + res._clientRes.body = res.body; } // End the `res` stream (which will in turn end the `res._clientRes` stream) @@ -226,8 +230,14 @@ module.exports = function buildResponse (req, _res) { return; } + // // Otherwise, the hook using the interpreter must have provided us with a `res._clientRes` stream, // so we'll need to serialize everything to work w/ that stream. + // + + // console.log('\n---\nwriting to clientRes stream...'); + // console.log('res.headers =>',res.headers); + // console.log('res._headers =>',res._headers); // Write body to `res` stream if (args.other) { @@ -238,7 +248,7 @@ module.exports = function buildResponse (req, _res) { try { toWrite = JSON.stringify(args.other); - // original method: + // original way: // toWrite = util.inspect(toWrite); } catch(e) { @@ -246,11 +256,12 @@ module.exports = function buildResponse (req, _res) { 'Failed to stringify specified JSON response body :: ' + util.inspect(args.other) + '\nError:\n' + util.inspect(e) ); - console.log('failed to stringify!'); + // console.log('failed to stringify!'); if (req._sails && req._sails.log && req._sails.log.error) { req._sails.log.error(failedToStringify); } else { + // todo: use debug() console.error(failedToStringify); } toWrite = failedStringify.message; @@ -260,14 +271,6 @@ module.exports = function buildResponse (req, _res) { res.write(toWrite); } - // Set status code and headers on the `_clientRes` stream so they are accessible - // to the provider of that stream. - // (this has to happen in `send()` because the code/headers might have just changed) - if (res._clientRes) { - res._clientRes.headers = res.headers; - res._clientRes.statusCode = res.statusCode; - } - // End the `res` stream. res.end(); }; From 46a69d9fe526b43b1675e946ae303c05e7f93a83 Mon Sep 17 00:00:00 2001 From: Mike McNeil Date: Mon, 15 Dec 2014 13:28:00 -0600 Subject: [PATCH 089/235] Fixed app.get()/app.post()-style usage --- lib/app/Sails.js | 24 +++++++++++++++++------- lib/app/request.js | 7 +++++-- lib/router/index.js | 4 ++-- test/unit/req.session.test.js | 35 ++++++++++++++++++++++++++++------- 4 files changed, 52 insertions(+), 18 deletions(-) diff --git a/lib/app/Sails.js b/lib/app/Sails.js index 8e7f9a61b3..360c0555b6 100644 --- a/lib/app/Sails.js +++ b/lib/app/Sails.js @@ -84,16 +84,26 @@ Sails.prototype.getBaseUrl = Sails.prototype.getBaseurl; Sails.prototype.request = require('./request'); // Expose Express-esque synonyms for low-level usage of router -var _routerBindWrapper = function (path, action) { +Sails.prototype.all = function(path, action) { this.router.bind(path, action); return this; }; -Sails.prototype.all = _routerBindWrapper; -Sails.prototype.get = _routerBindWrapper; -Sails.prototype.post = _routerBindWrapper; -Sails.prototype.put = _routerBindWrapper; -Sails.prototype.del = Sails.prototype['delete'] = _routerBindWrapper; - +Sails.prototype.get = function(path, action) { + this.router.bind(path, action, 'get'); + return this; +}; +Sails.prototype.post = function(path, action) { + this.router.bind(path, action, 'post'); + return this; +}; +Sails.prototype.put = function(path, action) { + this.router.bind(path, action, 'put'); + return this; +}; +Sails.prototype.del = Sails.prototype['delete'] = function(path, action) { + this.router.bind(path, action, 'delete'); + return this; +}; // Private methods: //////////////////////////////////////////////////////// diff --git a/lib/app/request.js b/lib/app/request.js index 487d582780..31681eceb1 100644 --- a/lib/app/request.js +++ b/lib/app/request.js @@ -56,8 +56,10 @@ module.exports = function request( /* address, body, cb */ ) { url = arguments[0].url; method = arguments[0].method; headers = arguments[0].headers || {}; - params = arguments[0].params || {}; + params = arguments[0].params || arguments[0].data || {}; } + console.log('headers: ',headers); + console.log('method: ',method); // Usage: @@ -136,7 +138,8 @@ module.exports = function request( /* address, body, cb */ ) { sails.router.route({ method: method, url: url, - body: body + body: body, + headers: headers || {} }, { _clientRes: clientRes }); diff --git a/lib/router/index.js b/lib/router/index.js index a4b91cdc5e..2c815adaed 100644 --- a/lib/router/index.js +++ b/lib/router/index.js @@ -154,7 +154,7 @@ Router.prototype.route = function(req, res) { req = buildReq(req, res); res = buildRes(req, res); - console.log('Received request to %s',req.url); + console.log('Received request to %s %s',req.method,req.url); // Deprecation error: res._cb = function _cbIsDeprecated(err) { @@ -447,7 +447,7 @@ function loadSession (req, res, next){ if (req._sails.config.session) { // TODO: this line of code does not need to be run for every request - req._sails.log.verbose('Running session middleware on interpreted virtual request with cookie:', req.headers.cookie); + req._sails.log.verbose('Running session middleware on interpreted virtual request with cookie:', util.inspect(req.headers.cookie, false, null)); console.log('Running session middleware on interpreted virtual request with cookie:', req.headers.cookie); console.log('Session config:',req._sails.config.session); var _sessionMiddleware = Express.session(req._sails.config.session); diff --git a/test/unit/req.session.test.js b/test/unit/req.session.test.js index db713ff83d..3745fa2cd2 100644 --- a/test/unit/req.session.test.js +++ b/test/unit/req.session.test.js @@ -38,6 +38,7 @@ describe('req.session (with no session hook)', function (){ before(function setupTestRoute(){ app.post('/sessionTest', function (req, res){ + console.log('RAN POST /sessionTest route...'); doesSessionExist = !!req.session; isSessionAnObject = _.isObject(req.session); req.session.something = 'some string'; @@ -45,6 +46,7 @@ describe('req.session (with no session hook)', function (){ }); app.get('/sessionTest', function (req, res){ + console.log('RAN GET /sessionTest route...'); doesSessionExist = !!req.session; isSessionAnObject = _.isObject(req.session); doesTestPropertyStillExist = req.session.something === 'some string'; @@ -61,10 +63,10 @@ describe('req.session (with no session hook)', function (){ headers: {} }, function (err, res, body){ if (err) return done(err); + console.log('res.headers',res.headers); if (res.statusCode !== 200) return done(new Error('Expected 200 status code')); if (!doesSessionExist) return done(new Error('req.session should exist.')); if (!isSessionAnObject) return done(new Error('req.session should be an object.')); - console.log('res.headers',res.headers); return done(); }); }); @@ -72,17 +74,36 @@ describe('req.session (with no session hook)', function (){ it('should persist data between requests', function (done){ app.request({ url: '/sessionTest', - method: 'GET', + method: 'POST', params: {}, headers: {} - }, function (err, res, body){ - console.log('headers in final clientRes :', res.headers); + }, function (err, clientRes, body){ if (err) return done(err); - if (res.statusCode !== 200) return done(new Error('Expected 200 status code')); + console.log('\n* * *\nclientRes.headers:\n',clientRes.headers,'\n'); + if (clientRes.statusCode !== 200) return done(new Error('Expected 200 status code')); if (!doesSessionExist) return done(new Error('req.session should exist.')); if (!isSessionAnObject) return done(new Error('req.session should be an object.')); - if (!doesTestPropertyStillExist) return done(new Error('`req.session.something` should still exist for subsequent requests.')); - return done(); + + console.log('Cookie:',clientRes.headers['set-cookie']); + app.request({ + url: '/sessionTest', + method: 'GET', + params: {}, + headers: { + cookie: clientRes.headers['set-cookie'] + } + }, function (err, clientRes, body){ + if (err) return done(err); + console.log('\n\n\n----------callback-------\n'); + console.log('err', err); + console.log('clientRes', clientRes); + console.log('body', body); + if (clientRes.statusCode !== 200) return done(new Error('Expected 200 status code')); + if (!doesSessionExist) return done(new Error('req.session should exist.')); + if (!isSessionAnObject) return done(new Error('req.session should be an object.')); + if (!doesTestPropertyStillExist) return done(new Error('`req.session.something` should still exist for subsequent requests.')); + return done(); + }); }); }); From b2f81e30c4584ada7379029d3cdfac81fac1296c Mon Sep 17 00:00:00 2001 From: Mike McNeil Date: Mon, 15 Dec 2014 14:05:15 -0600 Subject: [PATCH 090/235] Logging --- lib/app/request.js | 5 +++-- lib/router/index.js | 6 +++--- lib/router/res.js | 1 + test/unit/req.session.test.js | 10 +++++----- 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/lib/app/request.js b/lib/app/request.js index 31681eceb1..a21b786fe6 100644 --- a/lib/app/request.js +++ b/lib/app/request.js @@ -58,8 +58,9 @@ module.exports = function request( /* address, body, cb */ ) { headers = arguments[0].headers || {}; params = arguments[0].params || arguments[0].data || {}; } - console.log('headers: ',headers); - console.log('method: ',method); + console.log('called sails.request() '); + // console.log('headers: ',headers); + // console.log('method: ',method); // Usage: diff --git a/lib/router/index.js b/lib/router/index.js index 2c815adaed..239fbed870 100644 --- a/lib/router/index.js +++ b/lib/router/index.js @@ -154,7 +154,7 @@ Router.prototype.route = function(req, res) { req = buildReq(req, res); res = buildRes(req, res); - console.log('Received request to %s %s',req.method,req.url); + console.log('\n\n\n\n=======================\nReceived request to %s %s',req.method,req.url); // Deprecation error: res._cb = function _cbIsDeprecated(err) { @@ -185,8 +185,8 @@ Router.prototype.route = function(req, res) { } // console.log('res is now:\n',res); // console.log('\n\n'); - // console.log('Ran session middleware. Now req.session= ',req.session); - // console.log('req.sessionID= ',req.sessionID); + console.log('Ran session middleware. Now req.session= ',req.session); + console.log('req.sessionID= ',req.sessionID); sails.log.silly('Handling virtual request :: Running virtual body parser...'); bodyParser(req,res, function (err) { diff --git a/lib/router/res.js b/lib/router/res.js index b61fd61b3b..12ddfadb81 100644 --- a/lib/router/res.js +++ b/lib/router/res.js @@ -165,6 +165,7 @@ module.exports = function buildResponse (req, _res) { var prevEnd = res.end; res.end = function (){ res.writeHead(res.statusCode, _.extend(res._headers,res.headers)); + console.log('our res.end() was triggered'); // console.log('res.end():: called writeHead with headers=',_.extend(res._headers,res.headers)); prevEnd.apply(res, Array.prototype.slice.call(arguments)); }; diff --git a/test/unit/req.session.test.js b/test/unit/req.session.test.js index 3745fa2cd2..98869f80d2 100644 --- a/test/unit/req.session.test.js +++ b/test/unit/req.session.test.js @@ -47,6 +47,7 @@ describe('req.session (with no session hook)', function (){ app.get('/sessionTest', function (req, res){ console.log('RAN GET /sessionTest route...'); + console.log('req.session:\n', req.session); doesSessionExist = !!req.session; isSessionAnObject = _.isObject(req.session); doesTestPropertyStillExist = req.session.something === 'some string'; @@ -63,7 +64,6 @@ describe('req.session (with no session hook)', function (){ headers: {} }, function (err, res, body){ if (err) return done(err); - console.log('res.headers',res.headers); if (res.statusCode !== 200) return done(new Error('Expected 200 status code')); if (!doesSessionExist) return done(new Error('req.session should exist.')); if (!isSessionAnObject) return done(new Error('req.session should be an object.')); @@ -94,10 +94,10 @@ describe('req.session (with no session hook)', function (){ } }, function (err, clientRes, body){ if (err) return done(err); - console.log('\n\n\n----------callback-------\n'); - console.log('err', err); - console.log('clientRes', clientRes); - console.log('body', body); + // console.log('\n\n\n----------callback-------\n'); + // console.log('err', err); + // console.log('clientRes', clientRes); + // console.log('body', body); if (clientRes.statusCode !== 200) return done(new Error('Expected 200 status code')); if (!doesSessionExist) return done(new Error('req.session should exist.')); if (!isSessionAnObject) return done(new Error('req.session should be an object.')); From b64812f14b9eeabf9c7a46c0eb2ffd989a6a6fa7 Mon Sep 17 00:00:00 2001 From: Mike McNeil Date: Mon, 15 Dec 2014 14:10:27 -0600 Subject: [PATCH 091/235] Made test pass, but uses a major hack. Now just need to pass in the session store generated in the session hook. --- lib/router/index.js | 16 ++++++++++------ test/unit/req.session.test.js | 7 +++++++ 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/lib/router/index.js b/lib/router/index.js index 239fbed870..71a8973142 100644 --- a/lib/router/index.js +++ b/lib/router/index.js @@ -439,20 +439,21 @@ function parseCookies (req, res, next){ */ function loadSession (req, res, next){ - // console.log('Session config:',req._sails.config.session); - // If a Connect session store is configured, hook it up as req.session - // if (req._sails.config.session && req._sails.config.session.store) { + // console.log('Session config:',req._sails.config.session); - if (req._sails.config.session) { + if (!_sessionMiddleware && req._sails.config.session) { // TODO: this line of code does not need to be run for every request req._sails.log.verbose('Running session middleware on interpreted virtual request with cookie:', util.inspect(req.headers.cookie, false, null)); console.log('Running session middleware on interpreted virtual request with cookie:', req.headers.cookie); console.log('Session config:',req._sails.config.session); - var _sessionMiddleware = Express.session(req._sails.config.session); + _sessionMiddleware = Express.session(req._sails.config.session); + } - // Run the middleware + + // Run the middleware + if (_sessionMiddleware) { return _sessionMiddleware(req, res, next); } @@ -462,6 +463,9 @@ function loadSession (req, res, next){ +// MAJOR HACK: +var _sessionMiddleware; + diff --git a/test/unit/req.session.test.js b/test/unit/req.session.test.js index 98869f80d2..0f85d2c666 100644 --- a/test/unit/req.session.test.js +++ b/test/unit/req.session.test.js @@ -71,6 +71,13 @@ describe('req.session (with no session hook)', function (){ }); }); + + // + // To test: + // + // DEBUG=express-session mocha test/unit/req.session.test.js -b -g 'should persist' + // + it('should persist data between requests', function (done){ app.request({ url: '/sessionTest', From a75f3c7e54ba441e17ab0b55d3e059ce09346327 Mon Sep 17 00:00:00 2001 From: Mike McNeil Date: Mon, 15 Dec 2014 22:27:47 -0600 Subject: [PATCH 092/235] Clean out some more stuff from the session hook which is no longer necessary. --- lib/hooks/session/index.js | 68 ++++++++++++++++++++------------------ 1 file changed, 35 insertions(+), 33 deletions(-) diff --git a/lib/hooks/session/index.js b/lib/hooks/session/index.js index 8692cd215e..501416a08b 100644 --- a/lib/hooks/session/index.js +++ b/lib/hooks/session/index.js @@ -180,39 +180,39 @@ module.exports = function(sails) { - /** - * Create a new sid and build an empty session for it. - * - * @param {Object} handshake - a socket "handshake" -- basically, this is like `req` - * @param {Function} cb - * @returns live session, with `id` property === new sid - */ - generate: function(handshake, cb) { - - // Generate a session object w/ sid - // This is important since we need this sort of object as the basis for the data - // we'll save directly into the session store. - // (handshake is a pretend `req` object, and 2nd argument is cookie config) - var session = new ConnectSession(handshake, { - cookie: { - // Prevent access from client-side javascript - httpOnly: true, - - // Restrict to path - path: '/' - } - }); + // /** + // * Create a new sid and build an empty session for it. + // * + // * @param {Object} handshake - a socket "handshake" -- basically, this is like `req` + // * @param {Function} cb + // * @returns live session, with `id` property === new sid + // */ + // generate: function(handshake, cb) { + + // // Generate a session object w/ sid + // // This is important since we need this sort of object as the basis for the data + // // we'll save directly into the session store. + // // (handshake is a pretend `req` object, and 2nd argument is cookie config) + // var session = new ConnectSession(handshake, { + // cookie: { + // // Prevent access from client-side javascript + // httpOnly: true, + + // // Restrict to path + // path: '/' + // } + // }); - // Next, persist the new session - SessionHook.set(session.id, session, function(err) { - if (err) return cb(err); - sails.log.verbose('Generated new session (', session.id, ') for socket....'); + // // Next, persist the new session + // SessionHook.set(session.id, session, function(err) { + // if (err) return cb(err); + // sails.log.verbose('Generated new session (', session.id, ') for socket....'); - // Pass back final session object - return cb(null, session); - }); + // // Pass back final session object + // return cb(null, session); + // }); - }, + // }, /** @@ -222,8 +222,8 @@ module.exports = function(sails) { * @api private */ get: function(sessionId, cb) { - if (!util.isFunction(cb)) { - throw new Error('Invalid usage :: `SessionHook.get(sessionId, cb)`'); + if (!_.isFunction(cb)) { + throw new Error('Invalid usage :: `sails.hooks.session.get(sessionId, cb)`'); } return sails.config.session.store.get(sessionId, cb); }, @@ -236,7 +236,9 @@ module.exports = function(sails) { * @api private */ set: function(sessionId, data, cb) { - cb = util.optional(cb); + if (!_.isFunction(cb)) { + throw new Error('Invalid usage :: `sails.hooks.session.set(sessionId, data, cb)`'); + } return sails.config.session.store.set(sessionId, data, cb); }, From 1ab6a4d51f005a7ef896134ee77e0dbe30c48f90 Mon Sep 17 00:00:00 2001 From: Mike McNeil Date: Mon, 15 Dec 2014 22:30:01 -0600 Subject: [PATCH 093/235] More cleanup in session hook --- lib/hooks/session/index.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/hooks/session/index.js b/lib/hooks/session/index.js index 501416a08b..ab0a7e2a98 100644 --- a/lib/hooks/session/index.js +++ b/lib/hooks/session/index.js @@ -7,9 +7,6 @@ var _ = require('lodash'); var uuid = require('node-uuid'); var path = require('path'); var generateSecret = require('./generateSecret'); -var cookie = require('express/node_modules/cookie'); -var parseSignedCookie = require('cookie-parser').signedCookie; -var ConnectSession = require('express/node_modules/connect').middleware.session.Session; module.exports = function(sails) { @@ -165,7 +162,7 @@ module.exports = function(sails) { var CustomStore = SessionAdapter(require('express')); sessionConfig.store = new CustomStore(sessionConfig); } catch (e) { - // TODO: negotiate error + // TODO: negotiate error and give better error msg depending on code return cb(COULD_NOT_REQUIRE_CONNECT_ADAPTER_ERR(sessionConfig.adapter, adapterPackage, e)); } } From dcee82278cc0169402166ea0524480579d72cfdf Mon Sep 17 00:00:00 2001 From: Mike McNeil Date: Mon, 15 Dec 2014 22:37:56 -0600 Subject: [PATCH 094/235] Made new session support in core interpreter work without the dirty hacks. --- lib/hooks/session/index.js | 1 + lib/router/index.js | 26 +++++++------------------- test/unit/req.session.test.js | 5 +++-- 3 files changed, 11 insertions(+), 21 deletions(-) diff --git a/lib/hooks/session/index.js b/lib/hooks/session/index.js index ab0a7e2a98..0d6bf50ea1 100644 --- a/lib/hooks/session/index.js +++ b/lib/hooks/session/index.js @@ -94,6 +94,7 @@ module.exports = function(sails) { */ initialize: function(cb) { var sessionConfig = sails.config.session; + console.log('Initializing session hook...'); // Intepret session adapter config and "new up" a session store if (util.isObject(sessionConfig) && !util.isObject(sessionConfig.store)) { diff --git a/lib/router/index.js b/lib/router/index.js index 71a8973142..7f6f9c1187 100644 --- a/lib/router/index.js +++ b/lib/router/index.js @@ -439,22 +439,14 @@ function parseCookies (req, res, next){ */ function loadSession (req, res, next){ - // If a Connect session store is configured, hook it up as req.session - // console.log('Session config:',req._sails.config.session); - - - if (!_sessionMiddleware && req._sails.config.session) { - // TODO: this line of code does not need to be run for every request + // If a session store is configured, hook it up as `req.session` by passing + // it down to the session middleware + if (req._sails.config.session && req._sails.config.session.store) { req._sails.log.verbose('Running session middleware on interpreted virtual request with cookie:', util.inspect(req.headers.cookie, false, null)); - console.log('Running session middleware on interpreted virtual request with cookie:', req.headers.cookie); - console.log('Session config:',req._sails.config.session); - _sessionMiddleware = Express.session(req._sails.config.session); - } - - - // Run the middleware - if (_sessionMiddleware) { - return _sessionMiddleware(req, res, next); + // console.log('Running session middleware on interpreted virtual request with cookie:', req.headers.cookie); + // console.log('Session config:',req._sails.config.session); + var sessionMiddleware = Express.session(req._sails.config.session); + return sessionMiddleware(req, res, next); } // Otherwise don't even worry about it. @@ -463,10 +455,6 @@ function loadSession (req, res, next){ -// MAJOR HACK: -var _sessionMiddleware; - - diff --git a/test/unit/req.session.test.js b/test/unit/req.session.test.js index 0f85d2c666..c030810b4b 100644 --- a/test/unit/req.session.test.js +++ b/test/unit/req.session.test.js @@ -9,7 +9,7 @@ var async = require('async'); var Sails = require('../../lib').Sails; -describe('req.session (with no session hook)', function (){ +describe('req.session', function (){ var app; @@ -19,7 +19,8 @@ describe('req.session (with no session hook)', function (){ globals: false, loadHooks: [ 'moduleloader', - 'userconfig' + 'userconfig', + 'session' ], session: { adapter: 'memory', From 138c8aaa733fb596630f15482be9cfec7aab8aa8 Mon Sep 17 00:00:00 2001 From: Mike McNeil Date: Mon, 15 Dec 2014 22:41:04 -0600 Subject: [PATCH 095/235] Clean out unused code --- lib/router/index.js | 303 +------------------------------------------- 1 file changed, 5 insertions(+), 298 deletions(-) diff --git a/lib/router/index.js b/lib/router/index.js index 7f6f9c1187..5b8bf6a435 100644 --- a/lib/router/index.js +++ b/lib/router/index.js @@ -419,9 +419,10 @@ function parseCookies (req, res, next){ // } // } - // TODO: this line of code does not need to be run for every request req._sails.log.verbose('Parsing cookie:',req.headers.cookie); // console.log('Parsing cookie:',req.headers.cookie); + + // TODO: this line of code does not need to be run for every request var _cookieParser = Express.cookieParser(req._sails && req._sails.config.session && req._sails.config.session.secret); // Run the middleware @@ -445,6 +446,9 @@ function loadSession (req, res, next){ req._sails.log.verbose('Running session middleware on interpreted virtual request with cookie:', util.inspect(req.headers.cookie, false, null)); // console.log('Running session middleware on interpreted virtual request with cookie:', req.headers.cookie); // console.log('Session config:',req._sails.config.session); + + // TODO: this line of code does not need to be run for every request + // (i.e. store preconfigured session middleware as a private property on i.e. the app instance) var sessionMiddleware = Express.session(req._sails.config.session); return sessionMiddleware(req, res, next); } @@ -452,300 +456,3 @@ function loadSession (req, res, next){ // Otherwise don't even worry about it. return next(); } - - - - - - - -// /** -// * Create a session transaction -// * -// * Load data from the session store using the `sessionID` as parsed from the cookie. -// * Mix-in the session.save() method for persisting the data back to the session store. -// * -// * Functionality is roughly equivalent to that of Connect's sessionStore middleware. -// */ - -// function loadSession (req, cb) { - -// // If no cookie was provided, we'll give up trying to load the session. -// // Stub out a fake Session object so that its methods exist, etc. -// if (!req.headers.cookie) { -// req._sails.log.verbose('Could not load session for request, because no cookie was provided.'); -// req._sails.log.verbose('This will result in an empty session, i.e. (req.session === {})'); -// req.session = {}; -// return cb(); -// } - -// // If sid doesn't exit in socket, we have to do a little work first to get it -// // (or generate a new one-- and therefore a new empty session as well) -// if (!socket.handshake.sessionID && !socket.handshake.headers.cookie) { - -// // If no cookie exists, generate a random one (this will create a new session!) -// var generatedCookie = req._sails.config.session.key + '=' + uuid.v1(); -// req.headers.cookie = generatedCookie; -// req._sails.log.verbose('Could not fetch session, since connecting socket (', socket.id, ') has no cookie.'); -// req._sails.log.verbose('Is this a cross-origin socket..?)'); -// req._sails.log.verbose('Generated a one-time-use cookie:', generatedCookie); -// req._sails.log.verbose('This will result in an empty session, i.e. (req.session === {})'); - - -// // Convert cookie into `sid` using session secret -// // Maintain sid in socket so that the session can be queried before processing each incoming message -// socket.handshake.cookie = cookie.parse(generatedCookie); -// // Parse and decrypt cookie and save it in the socket.handshake -// socket.handshake.sessionID = parseSignedCookie(socket.handshake.cookie[sails.config.session.key], sails.config.session.secret); - -// // Generate and persist a new session in the store -// SessionHook.generate(socket.handshake, function(err, sessionData) { -// if (err) return cb(err); -// sails.log.silly('socket.handshake.sessionID is now :: ', socket.handshake.sessionID); - -// // Provide access to adapter-agnostic `.save()` -// return cb(null, new RawSession({ -// sid: sessionData.id, -// data: sessionData -// })); -// }); -// return; -// } - - -// try { -// // Convert cookie into `sid` using session secret -// // Maintain sid in socket so that the session can be queried before processing each incoming message -// socket.handshake.cookie = cookie.parse(socket.handshake.headers.cookie); -// // Parse and decrypt cookie and save it in the socket.handshake -// socket.handshake.sessionID = parseSignedCookie(socket.handshake.cookie[sails.config.session.key], sails.config.session.secret); -// } catch (e) { -// sails.log.error('Could not load session for socket #' + socket.id); -// sails.log.error('The socket\'s cookie could not be parsed into a sessionID.'); -// sails.log.error('Unless you\'re overriding the `authorization` function, make sure ' + -// 'you pass in a valid `' + sails.config.session.key + '` cookie'); -// sails.log.error('(or omit the cookie altogether to have a new session created and an ' + -// 'encrypted cookie sent in the response header to your socket.io upgrade request)'); -// return cb(e); -// } - -// // If sid DOES exist, it's easy to look up in the socket -// var sid = socket.handshake.sessionID; - -// // Cache the handshake in case it gets wiped out during the call to SessionHook.get -// var handshake = socket.handshake; - -// // Retrieve session data from store -// SessionHook.get(sid, function(err, sessionData) { - -// if (err) { -// sails.log.error('Error retrieving session from socket.'); -// return cb(err); -// } - -// // sid is not known-- the session secret probably changed -// // Or maybe server restarted and it was: -// // (a) using an auto-generated secret, or -// // (b) using the session memory store -// // and so it doesn't recognize the socket's session ID. -// else if (!sessionData) { -// sails.log.verbose('A socket (' + socket.id + ') is trying to connect with an invalid or expired session ID (' + sid + ').'); -// sails.log.verbose('Regnerating empty session...'); - -// SessionHook.generate(handshake, function(err, sessionData) { -// if (err) return cb(err); - -// // Provide access to adapter-agnostic `.save()` -// return cb(null, new RawSession({ -// sid: sessionData.id, -// data: sessionData -// })); -// }); -// } - -// // Otherwise session exists and everything is ok. - -// // Instantiate RawSession (provides .save() method) -// // And extend it with session data -// else return cb(null, new RawSession({ -// data: sessionData, -// sid: sid -// })); -// }); -// } - - - - - -// /** -// * Constructor for the connect session store wrapper used by the sockets hook. -// * Includes a save() method to persist the session data. -// */ -// function RawSession(options) { -// var sid = options.sid; -// var data = options.data; - - -// /** -// * [save description] -// * @param {Function} cb [description] -// * @return {[type]} [description] -// */ -// this.save = function(cb) { - -// if (!sid) { -// return _.isFunction(cb) && cb((function (){ -// var err = new Error('Could not save session'); -// err.code = 'E_SESSION_SAVE'; -// err.details = 'Trying to save session, but could not determine session ID.\n'+ -// 'This probably means a requesting socket from socket.io did not send a cookie.\n'+ -// 'Usually, this happens when a socket from an old browser tab tries to reconnect.\n'+ -// '(this can also occur when trying to connect a cross-origin socket.)'; -// return err; -// })()); -// } - -// // Merge data directly into instance to allow easy access on `req.session` later -// _.defaults(this, data); - -// // Persist session -// SessionHook.set(sid, _.cloneDeep(this), function(err) { -// if (err) { -// return _.isFunction(cb) && cb((function (){ -// err.code = 'E_SESSION_SAVE'; -// return err; -// })()); -// } -// return _.isFunction(cb) && cb(); -// }); -// }; - -// // Set the data on this object, since it will be used as req.session -// util.extend(this, options.data); -// } - - - - - - - - -// /** -// * [loadSession description] -// * @param {[type]} req [description] -// * @param {Function} cb [description] -// * @return {[type]} [description] -// */ -// function loadSession (req, cb){ - -// if (!req._sails || !req._sails.config.session) { -// req.session = {}; -// (req._sails && req._sails.log && req._sails.log.verbose || console.log)('Skipping session...'); -// return cb(); -// } - -// // Populate req.session using shared session store -// sails.session.fromSocket(req.socket, function sessionReady (err, session) { -// if (err) return cb(err); - -// // Provide access to session data as req.session -// req.session = session || {}; - -// return cb(); -// }); - -// // console.log('About to try and parse cookie...'); - -// // // Decrypt cookie into session id using session secret to get `sessionID`. -// // // -// // // (this allows us to query the session before processing each incoming message from this -// // // socket in the future) -// // var cookie; -// // var sessionID; -// // try { - -// // if (!req.headers.cookie) { -// // return cb((function _buildError(){ -// // var err = new Error('No cookie sent with request'); -// // return err; -// // })()); -// // } - -// // cookie = Cookie.parse(req.headers.cookie); - -// // if (!req._sails.config.session.key) { -// // return cb((function _buildError(){ -// // var err = new Error('No session key configured (sails.config.session.key)'); -// // return err; -// // })()); -// // } -// // if (!req._sails.config.session.secret) { -// // return cb((function _buildError(){ -// // var err = new Error('No session secret configured (sails.config.session.secret)'); -// // return err; -// // }())); -// // } - -// // sessionID = parseSignedCookie(cookie[req._sails.config.session.key], req._sails.config.session.secret); -// // } catch (e) { -// // return cb((function _buildError(){ -// // var err = new Error('Cannot load session. Cookie could not be parsed:\n'+util.inspect(e&&e.stack)); -// // err.code = 'E_PARSE_COOKIE'; -// // err.status = 400; -// // return err; -// // })()); -// // } - -// // // Look up this socket's session id in the Connect session store -// // // and see if we already have a record of 'em. -// // req._sails.session.get(sessionID, function(err, sessionData) { - -// // // An error occurred, so refuse the connection -// // if (err) { -// // return cb('Error loading session during socket connection! \n' + util.inspect(err, false, null)); -// // } - -// // // Cookie is present (there is a session id), but it doesn't -// // // correspond to a known session in the session store. -// // // So generate a new, blank session. -// // if (!sessionData) { -// // var newSession = new ConnectSession({sessionID: sessionID}, { -// // cookie: { -// // // Prevent access from client-side javascript -// // httpOnly: true -// // } -// // }); -// // req._sails.log.verbose("Generated new session for socket....", {sessionID: sessionID}); -// // req.session = newSession; -// // return cb(); -// // } - -// // // Parsed cookie matches a known session- onward! -// // // -// // // Instantiate a session object, passing our just-acquired session handshake -// // var existingSession = new ConnectSession({sessionID: sessionID}, sessionData); -// // req._sails.log.verbose("Connected socket to existing session...."); -// // req.session = existingSession; -// // cb(); -// // }); -// } - - - -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// When err.code === E_PARSE_COOKIE -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// var TROUBLESHOOTING = -// 'Perhaps you have an old browser tab open? In that case, you can ignore this warning.' + '\n' + -// 'Otherwise, are you are trying to access your Sails.js ' + '\n' + -// 'server from a socket.io javascript client hosted on a 3rd party domain? ' + '\n' + -// ' *-> You can override the cookie for a user entirely by setting ?cookie=... in the querystring of ' + '\n' + -// 'your socket.io connection url on the client.' + '\n' + -// ' *-> You can send a JSONP request first from the javascript client on the other domain ' + '\n' + -// 'to the Sails.js server to get the cookie, then connect the socket.' + '\n'; -// var socketSpecificMsg = -// 'Unable to parse the cookie that was transmitted for an incoming socket.io connect request.'+ -// '\n' + TROUBLESHOOTING + '\n'+ -// ''; From ef6eb53de42c9c59f5f12d02ff71074a33761b75 Mon Sep 17 00:00:00 2001 From: Mike McNeil Date: Tue, 16 Dec 2014 09:37:39 -0600 Subject: [PATCH 096/235] Fix bug in sending response body when responding to virtual requests --- lib/router/index.js | 2 +- lib/router/res.js | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/router/index.js b/lib/router/index.js index 5b8bf6a435..0e97405f16 100644 --- a/lib/router/index.js +++ b/lib/router/index.js @@ -154,7 +154,7 @@ Router.prototype.route = function(req, res) { req = buildReq(req, res); res = buildRes(req, res); - console.log('\n\n\n\n=======================\nReceived request to %s %s',req.method,req.url); + // console.log('\n\n\n\n=======================\nReceived request to %s %s\nwith req.body:\n',req.method,req.url, req.body); // Deprecation error: res._cb = function _cbIsDeprecated(err) { diff --git a/lib/router/res.js b/lib/router/res.js index 12ddfadb81..cc1b78e1fa 100644 --- a/lib/router/res.js +++ b/lib/router/res.js @@ -192,6 +192,7 @@ module.exports = function buildResponse (req, _res) { var args = normalizeResArgs(arguments); // Don't allow users to respond/redirect more than once per request + // TODO: prbly move this check to our `res.writeHead()` impl try { onlyAllowOneResponse(res); } @@ -219,10 +220,11 @@ module.exports = function buildResponse (req, _res) { // if a `_clientCallback` was specified, we'll skip the streaming stuff for res.send(). if (res._clientCallback) { - // Manually plug in res.body. + // Hard-code `res.body` rather than writing to the stream. // (but don't include body if it is empty) - if (res.body) { + if (args.other) { res.body = args.other; + // Then expose on res._clientRes.body res._clientRes.body = res.body; } From add89a2d765869f077ee6b7709a382d1774aa720 Mon Sep 17 00:00:00 2001 From: Mike McNeil Date: Tue, 16 Dec 2014 09:58:41 -0600 Subject: [PATCH 097/235] Trivial (logs) --- lib/router/index.js | 3 ++- lib/router/res.js | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/router/index.js b/lib/router/index.js index 0e97405f16..2d5b5e4018 100644 --- a/lib/router/index.js +++ b/lib/router/index.js @@ -185,8 +185,9 @@ Router.prototype.route = function(req, res) { } // console.log('res is now:\n',res); // console.log('\n\n'); - console.log('Ran session middleware. Now req.session= ',req.session); + console.log('Ran session middleware'); console.log('req.sessionID= ',req.sessionID); + // console.log('The loaded req.session= ',req.session); sails.log.silly('Handling virtual request :: Running virtual body parser...'); bodyParser(req,res, function (err) { diff --git a/lib/router/res.js b/lib/router/res.js index cc1b78e1fa..c0f98beba5 100644 --- a/lib/router/res.js +++ b/lib/router/res.js @@ -165,7 +165,7 @@ module.exports = function buildResponse (req, _res) { var prevEnd = res.end; res.end = function (){ res.writeHead(res.statusCode, _.extend(res._headers,res.headers)); - console.log('our res.end() was triggered'); + // console.log('our res.end() was triggered'); // console.log('res.end():: called writeHead with headers=',_.extend(res._headers,res.headers)); prevEnd.apply(res, Array.prototype.slice.call(arguments)); }; From e208de7e44a3ff3657912fdde8fbd9d1851805c4 Mon Sep 17 00:00:00 2001 From: Mike McNeil Date: Tue, 16 Dec 2014 10:59:36 -0600 Subject: [PATCH 098/235] New session tests pass. --- lib/app/request.js | 2 +- lib/hooks/session/index.js | 2 +- lib/router/index.js | 4 ++-- test/unit/req.session.test.js | 10 +++++----- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/app/request.js b/lib/app/request.js index a21b786fe6..f2e6f9223c 100644 --- a/lib/app/request.js +++ b/lib/app/request.js @@ -58,7 +58,7 @@ module.exports = function request( /* address, body, cb */ ) { headers = arguments[0].headers || {}; params = arguments[0].params || arguments[0].data || {}; } - console.log('called sails.request() '); + // console.log('called sails.request() '); // console.log('headers: ',headers); // console.log('method: ',method); diff --git a/lib/hooks/session/index.js b/lib/hooks/session/index.js index 0d6bf50ea1..e686634dba 100644 --- a/lib/hooks/session/index.js +++ b/lib/hooks/session/index.js @@ -94,7 +94,7 @@ module.exports = function(sails) { */ initialize: function(cb) { var sessionConfig = sails.config.session; - console.log('Initializing session hook...'); + // console.log('Initializing session hook...'); // Intepret session adapter config and "new up" a session store if (util.isObject(sessionConfig) && !util.isObject(sessionConfig.store)) { diff --git a/lib/router/index.js b/lib/router/index.js index 2d5b5e4018..c76b8cdd6e 100644 --- a/lib/router/index.js +++ b/lib/router/index.js @@ -185,8 +185,8 @@ Router.prototype.route = function(req, res) { } // console.log('res is now:\n',res); // console.log('\n\n'); - console.log('Ran session middleware'); - console.log('req.sessionID= ',req.sessionID); + // console.log('Ran session middleware'); + // console.log('req.sessionID= ',req.sessionID); // console.log('The loaded req.session= ',req.session); sails.log.silly('Handling virtual request :: Running virtual body parser...'); diff --git a/test/unit/req.session.test.js b/test/unit/req.session.test.js index c030810b4b..7020f346da 100644 --- a/test/unit/req.session.test.js +++ b/test/unit/req.session.test.js @@ -39,7 +39,7 @@ describe('req.session', function (){ before(function setupTestRoute(){ app.post('/sessionTest', function (req, res){ - console.log('RAN POST /sessionTest route...'); + // console.log('RAN POST /sessionTest route...'); doesSessionExist = !!req.session; isSessionAnObject = _.isObject(req.session); req.session.something = 'some string'; @@ -47,8 +47,8 @@ describe('req.session', function (){ }); app.get('/sessionTest', function (req, res){ - console.log('RAN GET /sessionTest route...'); - console.log('req.session:\n', req.session); + // console.log('RAN GET /sessionTest route...'); + // console.log('req.session:\n', req.session); doesSessionExist = !!req.session; isSessionAnObject = _.isObject(req.session); doesTestPropertyStillExist = req.session.something === 'some string'; @@ -87,12 +87,12 @@ describe('req.session', function (){ headers: {} }, function (err, clientRes, body){ if (err) return done(err); - console.log('\n* * *\nclientRes.headers:\n',clientRes.headers,'\n'); + // console.log('\n* * *\nclientRes.headers:\n',clientRes.headers,'\n'); if (clientRes.statusCode !== 200) return done(new Error('Expected 200 status code')); if (!doesSessionExist) return done(new Error('req.session should exist.')); if (!isSessionAnObject) return done(new Error('req.session should be an object.')); - console.log('Cookie:',clientRes.headers['set-cookie']); + // console.log('Cookie:',clientRes.headers['set-cookie']); app.request({ url: '/sessionTest', method: 'GET', From 9b85d9d89bd276c022a66d377c6671cae001d3c5 Mon Sep 17 00:00:00 2001 From: Mike McNeil Date: Tue, 16 Dec 2014 11:05:56 -0600 Subject: [PATCH 099/235] Minor cleanup in session hook --- lib/hooks/session/index.js | 210 +++++----------------------------- lib/router/req.js | 156 ------------------------- test/unit/req.session.test.js | 1 - 3 files changed, 30 insertions(+), 337 deletions(-) diff --git a/lib/hooks/session/index.js b/lib/hooks/session/index.js index e686634dba..be15a1fd20 100644 --- a/lib/hooks/session/index.js +++ b/lib/hooks/session/index.js @@ -2,14 +2,13 @@ * Module dependencies. */ -var util = require('sails-util'); -var _ = require('lodash'); -var uuid = require('node-uuid'); var path = require('path'); +var util = require('util'); +var _ = require('lodash'); var generateSecret = require('./generateSecret'); -module.exports = function(sails) { +module.exports = function(app) { ////////////////////////////////////////////////////////////////////////////// @@ -43,8 +42,8 @@ module.exports = function(sails) { // Validate config // Ensure that secret is specified if a custom session store is used - if (sails.config.session) { - if (!_.isObject(sails.config.session)) { + if (app.config.session) { + if (!_.isObject(app.config.session)) { throw new Error('Invalid custom session store configuration!\n' + '\n' + 'Basic usage ::\n' + @@ -57,32 +56,32 @@ module.exports = function(sails) { } // If session config is set, but secret is undefined, set a secure, one-time use secret - if (!sails.config.session || !sails.config.session.secret) { + if (!app.config.session || !app.config.session.secret) { - sails.log.verbose('SessionHook secret not defined-- automatically generating one for now...'); + app.log.verbose('Session secret not defined-- automatically generating one for now...'); - if (sails.config.environment === 'production') { - sails.log.warn('SessionHook secret must be identified!'); - sails.log.warn('Automatically generating one for now...'); + if (app.config.environment === 'production') { + app.log.warn('Session secret should be manually specified in production!'); + app.log.warn('Automatically generating one for now...'); - sails.log.error('This generated session secret is NOT OK for production!'); - sails.log.error('It will change each time the server starts and break multi-instance deployments.'); - sails.log.blank(); - sails.log.error('To set up a session secret, add or update it in `config/session.js`:'); - sails.log.error('module.exports.session = { secret: "keyboardcat" }'); - sails.log.blank(); + app.log.error('This generated session secret is NOT OK for production!'); + app.log.error('It will change each time the server starts and break multi-instance deployments.'); + app.log.blank(); + app.log.error('To set up a session secret, add or update it in `config/session.js`:'); + app.log.error('module.exports.session = { secret: "keyboardcat" }'); + app.log.blank(); } - sails.config.session.secret = generateSecret(); + app.config.session.secret = generateSecret(); } // Backwards-compatibility / shorthand notation // (allow mongo or redis session stores to be specified directly) - if (sails.config.session.adapter === 'redis') { - sails.config.session.adapter = 'connect-redis'; + if (app.config.session.adapter === 'redis') { + app.config.session.adapter = 'connect-redis'; } - else if (sails.config.session.adapter === 'mongo') { - sails.config.session.adapter = 'connect-mongo'; + else if (app.config.session.adapter === 'mongo') { + app.config.session.adapter = 'connect-mongo'; } }, @@ -93,11 +92,11 @@ module.exports = function(sails) { * @api private */ initialize: function(cb) { - var sessionConfig = sails.config.session; + var sessionConfig = app.config.session; // console.log('Initializing session hook...'); // Intepret session adapter config and "new up" a session store - if (util.isObject(sessionConfig) && !util.isObject(sessionConfig.store)) { + if (_.isObject(sessionConfig) && !_.isObject(sessionConfig.store)) { // Unless the session is explicitly disabled, require the appropriate adapter if (sessionConfig.adapter) { @@ -150,11 +149,11 @@ module.exports = function(sails) { // Determine the path to the adapter by using the "main" described in its package.json file: var pathToAdapterDependency; - var pathToAdapterPackage = path.resolve(sails.config.appPath, 'node_modules', sessionConfig.adapter ,'package.json'); + var pathToAdapterPackage = path.resolve(app.config.appPath, 'node_modules', sessionConfig.adapter ,'package.json'); var adapterPackage; try { adapterPackage = require(pathToAdapterPackage); - pathToAdapterDependency = path.resolve(sails.config.appPath, 'node_modules', sessionConfig.adapter, adapterPackage.main); + pathToAdapterDependency = path.resolve(app.config.appPath, 'node_modules', sessionConfig.adapter, adapterPackage.main); } catch (e) { return cb(COULD_NOT_REQUIRE_CONNECT_ADAPTER_ERR(sessionConfig.adapter, adapterPackage, e)); @@ -170,49 +169,13 @@ module.exports = function(sails) { } } - // Save reference in `sails.session` - sails.session = SessionHook; + // Expose hook as `sails.session` + app.session = SessionHook; return cb(); }, - - // /** - // * Create a new sid and build an empty session for it. - // * - // * @param {Object} handshake - a socket "handshake" -- basically, this is like `req` - // * @param {Function} cb - // * @returns live session, with `id` property === new sid - // */ - // generate: function(handshake, cb) { - - // // Generate a session object w/ sid - // // This is important since we need this sort of object as the basis for the data - // // we'll save directly into the session store. - // // (handshake is a pretend `req` object, and 2nd argument is cookie config) - // var session = new ConnectSession(handshake, { - // cookie: { - // // Prevent access from client-side javascript - // httpOnly: true, - - // // Restrict to path - // path: '/' - // } - // }); - - // // Next, persist the new session - // SessionHook.set(session.id, session, function(err) { - // if (err) return cb(err); - // sails.log.verbose('Generated new session (', session.id, ') for socket....'); - - // // Pass back final session object - // return cb(null, session); - // }); - - // }, - - /** * @param {String} sessionId * @param {Function} cb @@ -223,7 +186,7 @@ module.exports = function(sails) { if (!_.isFunction(cb)) { throw new Error('Invalid usage :: `sails.hooks.session.get(sessionId, cb)`'); } - return sails.config.session.store.get(sessionId, cb); + return app.config.session.store.get(sessionId, cb); }, /** @@ -237,121 +200,8 @@ module.exports = function(sails) { if (!_.isFunction(cb)) { throw new Error('Invalid usage :: `sails.hooks.session.set(sessionId, data, cb)`'); } - return sails.config.session.store.set(sessionId, data, cb); - }, - - - - // /** - // * Create a session transaction - // * - // * Load the Connect session data using the sessionID in the socket.io handshake object - // * Mix-in the session.save() method for persisting the data back to the session store. - // * - // * Functionally equivalent to connect's sessionStore middleware. - // */ - - // fromSocket: function(socket, cb) { - - // // If a socket makes it here, even though its associated session is not specified, - // // it's authorized as far as the app is concerned, so no need to do that again. - // // Instead, use the cookie to look up the sid, and then the sid to look up the session data - - - // // If sid doesn't exit in socket, we have to do a little work first to get it - // // (or generate a new one-- and therefore a new empty session as well) - // if (!socket.handshake.sessionID && !socket.handshake.headers.cookie) { - - // // If no cookie exists, generate a random one (this will create a new session!) - // var generatedCookie = sails.config.session.key + '=' + uuid.v1(); - // socket.handshake.headers.cookie = generatedCookie; - // sails.log.verbose('Could not fetch session, since connecting socket (', socket.id, ') has no cookie.'); - // sails.log.verbose('Is this a cross-origin socket..?)'); - // sails.log.verbose('Generated a one-time-use cookie:', generatedCookie); - // sails.log.verbose('This will result in an empty session, i.e. (req.session === {})'); - - - // // Convert cookie into `sid` using session secret - // // Maintain sid in socket so that the session can be queried before processing each incoming message - // socket.handshake.cookie = cookie.parse(generatedCookie); - // // Parse and decrypt cookie and save it in the socket.handshake - // socket.handshake.sessionID = parseSignedCookie(socket.handshake.cookie[sails.config.session.key], sails.config.session.secret); - - // // Generate and persist a new session in the store - // SessionHook.generate(socket.handshake, function(err, sessionData) { - // if (err) return cb(err); - // sails.log.silly('socket.handshake.sessionID is now :: ', socket.handshake.sessionID); - - // // Provide access to adapter-agnostic `.save()` - // return cb(null, new RawSession({ - // sid: sessionData.id, - // data: sessionData - // })); - // }); - // return; - // } - - - // try { - // // Convert cookie into `sid` using session secret - // // Maintain sid in socket so that the session can be queried before processing each incoming message - // socket.handshake.cookie = cookie.parse(socket.handshake.headers.cookie); - // // Parse and decrypt cookie and save it in the socket.handshake - // socket.handshake.sessionID = parseSignedCookie(socket.handshake.cookie[sails.config.session.key], sails.config.session.secret); - // } catch (e) { - // sails.log.error('Could not load session for socket #' + socket.id); - // sails.log.error('The socket\'s cookie could not be parsed into a sessionID.'); - // sails.log.error('Unless you\'re overriding the `authorization` function, make sure ' + - // 'you pass in a valid `' + sails.config.session.key + '` cookie'); - // sails.log.error('(or omit the cookie altogether to have a new session created and an ' + - // 'encrypted cookie sent in the response header to your socket.io upgrade request)'); - // return cb(e); - // } - - // // If sid DOES exist, it's easy to look up in the socket - // var sid = socket.handshake.sessionID; - - // // Cache the handshake in case it gets wiped out during the call to SessionHook.get - // var handshake = socket.handshake; - - // // Retrieve session data from store - // SessionHook.get(sid, function(err, sessionData) { - - // if (err) { - // sails.log.error('Error retrieving session from socket.'); - // return cb(err); - // } - - // // sid is not known-- the session secret probably changed - // // Or maybe server restarted and it was: - // // (a) using an auto-generated secret, or - // // (b) using the session memory store - // // and so it doesn't recognize the socket's session ID. - // else if (!sessionData) { - // sails.log.verbose('A socket (' + socket.id + ') is trying to connect with an invalid or expired session ID (' + sid + ').'); - // sails.log.verbose('Regnerating empty session...'); - - // SessionHook.generate(handshake, function(err, sessionData) { - // if (err) return cb(err); - - // // Provide access to adapter-agnostic `.save()` - // return cb(null, new RawSession({ - // sid: sessionData.id, - // data: sessionData - // })); - // }); - // } - - // // Otherwise session exists and everything is ok. - - // // Instantiate RawSession (provides .save() method) - // // And extend it with session data - // else return cb(null, new RawSession({ - // data: sessionData, - // sid: sid - // })); - // }); - // } + return app.config.session.store.set(sessionId, data, cb); + } }; diff --git a/lib/router/req.js b/lib/router/req.js index 8ffa7260ba..da163302f5 100644 --- a/lib/router/req.js +++ b/lib/router/req.js @@ -106,159 +106,3 @@ module.exports = function buildRequest (_req) { return req; }; - - - -// ////////////////////////////////////////////////////////////////////////// -///// ////////////////////////////////////////////////////////////////////////// -/// || //////////////////////////////////////////////////////////////////////// -///// TODO \/ ///////////////////////////////////////////////////////////////////////////// -//////// ///////////////////////////////////////////////////////////////////////////// -////////// ///////////////////////////////////////////////////////////////////////////// - - -// function FakeSession() { -// // TODO: mimic the session store impl in sockets hook -// // (all of this can drastically simplify that hook and consolidate -// // request interpreter logic) -// } - - - - - -// function ToLoadSession(sails) { - - -// /** -// * Module dependencies. -// */ - -// var util = require('util'); -// var parseSignedCookie = require('cookie-parser').signedCookie; -// var cookie = require('express/node_modules/cookie'); -// var ConnectSession = require('express/node_modules/connect').middleware.session.Session; -// var getSDKMetadata = require('./getSDKMetadata'); - - - -// /** -// * Fired after a socket is connected -// */ - -// return function socketAttemptingToConnect(handshake, accept) { - -// // If a cookie override was provided in the query string, use it. -// // (e.g. ?cookie=sails.sid=a4g8dsgajsdgadsgasd) -// if (handshake.query.cookie) { -// handshake.headers.cookie = handshake.query.cookie; -// } - -// var sdk = getSDKMetadata(handshake); -// sails.log.verbose(util.format('%s client (v%s) is trying to connect a socket...', sdk.platform, sdk.version)); - -// var TROUBLESHOOTING = -// 'Perhaps you have an old browser tab open? In that case, you can ignore this warning.' + '\n' + -// 'Otherwise, are you are trying to access your Sails.js ' + '\n' + -// 'server from a socket.io javascript client hosted on a 3rd party domain? ' + '\n' + -// ' *-> You can override the cookie for a user entirely by setting ?cookie=... in the querystring of ' + '\n' + -// 'your socket.io connection url on the client.' + '\n' + -// ' *-> You can send a JSONP request first from the javascript client on the other domain ' + '\n' + -// 'to the Sails.js server to get the cookie first, then connect the socket.' + '\n' + -// ' *-> For complete customizability, to override the built-in session assignment logic in Sails ' + '\n' + -// 'for socket.io requests, you can override socket.io\'s `authorization` logic with your own function ' + '\n' + -// 'in `config/sockets.js`. ' + '\n' + -// 'Or disable authorization for incoming socket connection requests entirely by setting `authorization: false`.' + '\n'; - - -// // Parse and decrypt cookie and save it in the handshake -// if (!handshake.headers.cookie) { -// return socketConnectionError(accept, -// 'Cannot load session for an incoming socket.io connection... ' + '\n' + -// 'No cookie was sent!\n' + -// TROUBLESHOOTING, -// 'Cannot load session. No cookie transmitted.' -// ); -// } - -// // Decrypt cookie into session id using session secret -// // Maintain sessionID in socket handshake so that the session -// // can be queried before processing each incoming message from this -// // socket in the future. -// try { -// handshake.cookie = cookie.parse(handshake.headers.cookie); -// handshake.sessionID = parseSignedCookie(handshake.cookie[sails.config.session.key], sails.config.session.secret); -// } catch (e) { -// return socketConnectionError(accept, -// 'Unable to parse the cookie that was transmitted for an incoming socket.io connect request:\n' + -// util.inspect(e) + '\n' + TROUBLESHOOTING, -// 'Cannot load session. Cookie could not be parsed.' -// ); -// } - - -// // Look up this socket's session id in the Connect session store -// // and see if we already have a record of 'em. -// sails.session.get(handshake.sessionID, function(err, session) { - -// // An error occurred, so refuse the connection -// if (err) { -// return socketConnectionError(accept, -// 'Error loading session during socket connection! \n' + err, -// 'Error loading session.'); -// } - -// // Cookie is present (there is a session id), but it doesn't -// // correspond to a known session in the session store. -// // So generate a new, blank session. -// else if (!session) { -// handshake.session = new ConnectSession(handshake, { -// cookie: { -// // Prevent access from client-side javascript -// httpOnly: true -// } -// }); -// sails.log.verbose("Generated new session for socket....", handshake); - -// // TO_TEST: -// // do we need to set handshake.sessionID with the id of the new session? - -// // TO_TEST: -// // do we need to revoke/replace the cookie as well? -// // how can we do that w/ socket.io? -// // can we access the response headers in the http UPGRADE response? -// // or should we just clear the cookie from the handshake and call it good? -// // e.g -// // var date = new Date(); -// // date.setTime(date.getTime()+(days*24*60*60*1000)); -// // var expires = "; expires="+date.toGMTString(); -// // handshake.headers.cookie = name+"="+value+expires+"; path=/"; - -// accept(null, true); -// } - -// // Parsed cookie matches a known session- onward! -// else { - -// // Create a session object, passing our just-acquired session handshake -// handshake.session = new ConnectSession(handshake, session); -// sails.log.verbose("Connected socket to existing session...."); -// accept(null, true); -// } -// }); -// }; - - -// /** -// * Fired when an internal server error occurs while authorizing the socket -// */ - -// function socketConnectionError(accept, devMsg, prodMsg) { -// var msg; -// if (sails.config.environment === 'development') { -// msg = devMsg; -// } else msg = prodMsg; -// return accept(msg, false); -// } - -// }; diff --git a/test/unit/req.session.test.js b/test/unit/req.session.test.js index 7020f346da..72aa99c971 100644 --- a/test/unit/req.session.test.js +++ b/test/unit/req.session.test.js @@ -48,7 +48,6 @@ describe('req.session', function (){ app.get('/sessionTest', function (req, res){ // console.log('RAN GET /sessionTest route...'); - // console.log('req.session:\n', req.session); doesSessionExist = !!req.session; isSessionAnObject = _.isObject(req.session); doesTestPropertyStillExist = req.session.something === 'some string'; From 36c762780906a45e4d77383247fd8bf3064ea150 Mon Sep 17 00:00:00 2001 From: Mike McNeil Date: Tue, 16 Dec 2014 11:14:37 -0600 Subject: [PATCH 100/235] Allow overriding core hooks using hooks in node_modules, but make hooks in api/hooks/ take precenence. --- lib/hooks/moduleloader/index.js | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/lib/hooks/moduleloader/index.js b/lib/hooks/moduleloader/index.js index f77fb234a1..4329a57689 100644 --- a/lib/hooks/moduleloader/index.js +++ b/lib/hooks/moduleloader/index.js @@ -405,11 +405,24 @@ module.exports = function(sails) { else { hookName = identity.match(/^sails-hook-/) ? identity.replace(/^sails-hook-/,'') : identity; } - // If we have a core hook or a hook in api/hooks with this name, throw an error - if (sails.hooks[hookName] || hooks[hookName]) { - var err = new Error("Tried to load hook `" + hookName + "`, but a hook with that name already exists."); - err.code = 'E_INVALID_HOOK_NAME'; - throw err; + + // Allow overriding core hooks + if (sails.hooks[hookName]) { + sails.log.verbose('Found hook: `'+hookName+'` in `node_modules/`. Overriding core hook w/ the same identity...'); + } + + // If we have a hook in api/hooks with this name, throw an error + if (hooks[hookName]) { + var err = (function (){ + var msg = + 'Found hook: `' + hookName + '`, in `node_modules/`, but a hook with that identity already exists in `api/hooks/`. '+ + 'The hook defined in your `api/hooks/` folder will take precedence.'; + var err = new Error(msg); + err.code = 'E_INVALID_HOOK_NAME'; + return err; + }); + sails.log.warn(err); + return memo; } // Load the hook code From 9921adf54ab06c5629de2d76f4048efb5ffc4324 Mon Sep 17 00:00:00 2001 From: Mike McNeil Date: Tue, 16 Dec 2014 11:19:46 -0600 Subject: [PATCH 101/235] Only use req.flash() in req.validate() if req.session() exists. --- lib/hooks/request/validate.js | 4 +++- test/hooks/request/req.validate.test.js | 12 +++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/hooks/request/validate.js b/lib/hooks/request/validate.js index 8b4ed1ee63..6c39caef2d 100644 --- a/lib/hooks/request/validate.js +++ b/lib/hooks/request/validate.js @@ -45,7 +45,9 @@ module.exports = function (req, res) { }); if (redirectTo) { - req.flash('error', e); + if (req.session && req.flash) { + req.flash('error', e); + } res.redirect(redirectTo); } else { diff --git a/test/hooks/request/req.validate.test.js b/test/hooks/request/req.validate.test.js index a625fc51ef..2265edaabd 100644 --- a/test/hooks/request/req.validate.test.js +++ b/test/hooks/request/req.validate.test.js @@ -67,25 +67,23 @@ describe('Request hook', function (){ sails.request(ROUTEADDRESS+'?foo=hi'); }); - it('should redirect and use req.flash to store error in session if `redirectTo` is specified', function (done) { + it('should redirect if `redirectTo` is specified', function (done) { var ROUTEADDRESS = '/req_validate2'; - var fakeSession = {}; sails.router.bind(ROUTEADDRESS, function (req, res, next) { req.validate({ bar: 'string' }, '/somewhereElse'); return res.send(200); - }) - .emit('router:request', { + }); + + sails.emit('router:request', { url: ROUTEADDRESS, query: { foo: 'hi' - }, - session: fakeSession + } }, { redirect: function fakeRedirect (dest) { assert(dest === '/somewhereElse'); - assert(fakeSession.flash.error); return done(); } }); From c9b04d15a92c8776675869251350ba21ee4b9d6a Mon Sep 17 00:00:00 2001 From: Mike McNeil Date: Tue, 16 Dec 2014 13:35:59 -0600 Subject: [PATCH 102/235] Add session id parsing method to session hook. --- lib/hooks/session/index.js | 61 +++++++++++++++++++++++++++++++++++++- package.json | 3 +- 2 files changed, 62 insertions(+), 2 deletions(-) diff --git a/lib/hooks/session/index.js b/lib/hooks/session/index.js index be15a1fd20..7a2d43dcad 100644 --- a/lib/hooks/session/index.js +++ b/lib/hooks/session/index.js @@ -5,8 +5,10 @@ var path = require('path'); var util = require('util'); var _ = require('lodash'); -var generateSecret = require('./generateSecret'); +var parseCookie = require('cookie').parse; +var unsignCookie = require('cookie-signature').unsign; +var generateSecret = require('./generateSecret'); module.exports = function(app) { @@ -176,6 +178,63 @@ module.exports = function(app) { }, + + /** + * Parse and unsign (i.e. decrypt) the provided cookie to get the session id. + * + * (adapted from code in the `express-session`) + * (TODO: pull out into separate module as part of jshttp/pillarjs) + * + * @param {String} cookie + * @return {String} [sessionId] + * + * @throws {Error} If cookie cannot be parsed or unsigned + */ + parseSessionIdFromCookie: function (cookie){ + + // e.g. "sails.sid" + var sessionKey = app.config.session.key; + + // e.g. "lolcatparty" + var sessionSecret = app.config.session.secret; + + // Parse cookie + var parsedSidCookie = parseCookie(cookie)[app.config.session.key]; + + if (typeof parsedSidCookie !== 'string') { + throw (function createError(){ + var err = new Error('No sid cookie exists'); + err.status = 401; + err.code = 'E_SESSION_PARSE_COOKIE'; + return err; + })(); + } + + if (parsedSidCookie.substr(0, 2) !== 's:') { + throw (function createError(){ + var err = new Error('Cookie unsigned'); + err.status = 401; + err.code = 'E_SESSION_PARSE_COOKIE'; + return err; + })(); + } + + // Unsign cookie + var sessionId = unsignCookie(parsedSidCookie.slice(2), sessionSecret); + + if (sessionId === false) { + throw (function createError(){ + var err = new Error('Cookie signature invalid'); + err.status = 401; + err.code = 'E_SESSION_PARSE_COOKIE'; + return err; + })(); + } + + return sessionId; + }, + + /** * @param {String} sessionId * @param {Function} cb diff --git a/package.json b/package.json index 312aabfe44..bf76a07198 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,8 @@ "waterline": "~0.10.11", "express": "3.16.0", "method-override": "~2.3.0", - "cookie-parser": "~1.3.3", + "cookie": "0.1.2", + "cookie-signature": "1.0.4", "rc": "~0.5.0", "sails-stringfile": "~0.3.0", "async": "~0.2.9", From 7e1f76a111250e2573d874d37203a47149f731ce Mon Sep 17 00:00:00 2001 From: Mike McNeil Date: Tue, 16 Dec 2014 13:47:40 -0600 Subject: [PATCH 103/235] If session cannot be saved or loaded, that's an error. --- lib/hooks/session/index.js | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/lib/hooks/session/index.js b/lib/hooks/session/index.js index 7a2d43dcad..d4e861419a 100644 --- a/lib/hooks/session/index.js +++ b/lib/hooks/session/index.js @@ -245,7 +245,18 @@ module.exports = function(app) { if (!_.isFunction(cb)) { throw new Error('Invalid usage :: `sails.hooks.session.get(sessionId, cb)`'); } - return app.config.session.store.get(sessionId, cb); + return app.config.session.store.get(sessionId, function (err, session){ + if (err) return cb(err); + if (!session) { + return cb((function _createError(){ + var e = new Error('Session could not be loaded'); + e.code = 'E_SESSION'; + return e; + })()); + } + return cb(null, session); + }); + // TODO: ensure that a valid session object is sent back }, /** @@ -259,7 +270,18 @@ module.exports = function(app) { if (!_.isFunction(cb)) { throw new Error('Invalid usage :: `sails.hooks.session.set(sessionId, data, cb)`'); } - return app.config.session.store.set(sessionId, data, cb); + return app.config.session.store.set(sessionId, data, function (err, session){ + if (err) return cb(err); + if (!session) { + return cb((function _createError(){ + var e = new Error('Session could not be saved'); + e.code = 'E_SESSION'; + return e; + })()); + } + return cb(null, session); + }); + // TODO: ensure that a valid session object is sent back } }; From da546e69630a9ae54968c835412849d92ed1579f Mon Sep 17 00:00:00 2001 From: Mike McNeil Date: Tue, 16 Dec 2014 14:48:31 -0600 Subject: [PATCH 104/235] Explain deps. --- lib/hooks/session/index.js | 67 +++++++++++++++++++++++++++++++++++--- 1 file changed, 63 insertions(+), 4 deletions(-) diff --git a/lib/hooks/session/index.js b/lib/hooks/session/index.js index d4e861419a..311423c3d5 100644 --- a/lib/hooks/session/index.js +++ b/lib/hooks/session/index.js @@ -5,10 +5,18 @@ var path = require('path'); var util = require('util'); var _ = require('lodash'); + +// generateSecret is used to generate a one-off session secret if one wasn't configured +var generateSecret = require('./generateSecret'); + +// (these two dependencies are only here for sails.session.parseSessionIdFromCookie(), +// which is only here to enable socket lifecycle callbacks) var parseCookie = require('cookie').parse; var unsignCookie = require('cookie-signature').unsign; -var generateSecret = require('./generateSecret'); +// (this dependency is only here for sails.session.generate()- +// which is really only here to enable socket lifecycle callbacks) +var ConnectSession = require('express/node_modules/connect').middleware.session.Session; module.exports = function(app) { @@ -256,13 +264,12 @@ module.exports = function(app) { } return cb(null, session); }); - // TODO: ensure that a valid session object is sent back }, /** * @param {String} sessionId * @param {} data - * @param {Function} [cb] - optional + * @param {Function} cb * * @api private */ @@ -281,7 +288,59 @@ module.exports = function(app) { } return cb(null, session); }); - // TODO: ensure that a valid session object is sent back + }, + + + /** + * Generate a new Session instance and return it. + * + * @param {Function} cb + * + * @api private + */ + generate: function (cb) { + + if (typeof cb !== 'function') { + app.log.error('Failed to generate session- expected callback function as argument.\nUsage:\nsails.session.generate(callback)'); + return; + } + + var newSession; + + try { + // Generate a new session instance (and a new sid) + // ( + // 1st arg to constructor (sort of a pretend `req` object, but mainly just to get headers) + // 2nd argument is a cookie config object + // ) + newSession = new ConnectSession({headers:{}}, { + cookie: { + // Prevent access from client-side javascript + httpOnly: true, + + // Restrict to path + path: '/' + } + }); + + // Next, persist the new session + app.session.set(newSession.id, newSession, function(err) { + if (err) return cb(err); + sails.log.verbose('Generated new session (sid=', newSession.id, ') for virtual request....'); + + // Pass back final session object + return cb(null, session); + }); + } + // If new session instance cannot be created for some reason, bail out. + catch (e){ + return cb((function _createError(){ + var err = new Error('Could not generate new session for virtual request'); + err.code = 'E_SESSION'; + return err; + })()); + } + } }; From e85f3bdae791c4dae121eb91f2b59dc96d8a9126 Mon Sep 17 00:00:00 2001 From: Mike McNeil Date: Tue, 16 Dec 2014 14:50:12 -0600 Subject: [PATCH 105/235] A bit more cleanup in session hook --- lib/hooks/session/generateSecret.js | 38 +++++++++++++++-------------- lib/hooks/session/index.js | 12 +-------- 2 files changed, 21 insertions(+), 29 deletions(-) diff --git a/lib/hooks/session/generateSecret.js b/lib/hooks/session/generateSecret.js index 79b96a935f..ecbca5768f 100644 --- a/lib/hooks/session/generateSecret.js +++ b/lib/hooks/session/generateSecret.js @@ -1,30 +1,32 @@ /** * Module dependencies */ -var crypto = require('crypto'), - _ = require('lodash'); +var crypto = require('crypto'); +var _ = require('lodash'); /** * Generate session secret * @return {[type]} [description] */ -module.exports = function generateSecret () { - - // Combine random and case-specific factors into a base string - var factors = { - creationDate: (new Date()).getTime(), - random: Math.random() * (Math.random() * 1000), - nodeVersion: process.version - }; - var basestring = ''; - _.each(factors, function (val) { basestring += val; }); +module.exports = function generateSecret() { - // Build hash - var hash = crypto. - createHash('md5'). - update(basestring). - digest('hex'); + // Combine random and case-specific factors into a base string + var factors = { + creationDate: (new Date()).getTime(), + random: Math.random() * (Math.random() * 1000), + nodeVersion: process.version + }; + var basestring = ''; + _.each(factors, function(val) { + basestring += val; + }); - return hash; + // Build hash + var hash = + crypto.createHash('md5') + .update(basestring) + .digest('hex'); + + return hash; }; diff --git a/lib/hooks/session/index.js b/lib/hooks/session/index.js index 311423c3d5..77b30cdad7 100644 --- a/lib/hooks/session/index.js +++ b/lib/hooks/session/index.js @@ -18,19 +18,9 @@ var unsignCookie = require('cookie-signature').unsign; // which is really only here to enable socket lifecycle callbacks) var ConnectSession = require('express/node_modules/connect').middleware.session.Session; -module.exports = function(app) { - - - ////////////////////////////////////////////////////////////////////////////// - // TODO: - // - // All of this craziness can be replaced by making the socket.io interpreter - // 100% connect-compatible (it's close!). Then, the connect cookie parser - // can be used directly with Sails' simulated req and res objects. - // - ////////////////////////////////////////////////////////////////////////////// +module.exports = function(app) { // `session` hook definition var SessionHook = { From 0eca0501a09dd59e40946e55826af435f2bc45d4 Mon Sep 17 00:00:00 2001 From: Mike McNeil Date: Tue, 16 Dec 2014 15:21:03 -0600 Subject: [PATCH 106/235] Added uid-safe dep --- lib/hooks/session/index.js | 21 +++++++++++++++++++++ package.json | 5 +++-- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/lib/hooks/session/index.js b/lib/hooks/session/index.js index 77b30cdad7..d4cbf87b8b 100644 --- a/lib/hooks/session/index.js +++ b/lib/hooks/session/index.js @@ -6,13 +6,19 @@ var path = require('path'); var util = require('util'); var _ = require('lodash'); + // generateSecret is used to generate a one-off session secret if one wasn't configured var generateSecret = require('./generateSecret'); +// (this dependency is just for creating new cookies) +var uid = require('uid-safe').sync; + // (these two dependencies are only here for sails.session.parseSessionIdFromCookie(), // which is only here to enable socket lifecycle callbacks) var parseCookie = require('cookie').parse; +var stringifyCookie = require('cookie').serialize; var unsignCookie = require('cookie-signature').unsign; +var signCookie = require('cookie-signature').sign; // (this dependency is only here for sails.session.generate()- // which is really only here to enable socket lifecycle callbacks) @@ -176,6 +182,21 @@ module.exports = function(app) { }, + /** + * Generate a cookie to represent a new session. + * + * @return {String} + * @api private + */ + + generateNewSidCookie: function (){ + var sid = uid(24); + var signedSid = 's:' + signCookie(sid, app.config.session.secret); + var cookie = stringifyCookie(app.config.session.key, signedSid, {}); + return cookie; + }, + + /** * Parse and unsign (i.e. decrypt) the provided cookie to get the session id. diff --git a/package.json b/package.json index bf76a07198..cef8d490b6 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "scripts": { "test": "mocha -b", "prepublish": "npm prune", - "preinstall" : "node ./lib/preinstall_npmcheck.js" + "preinstall": "node ./lib/preinstall_npmcheck.js" }, "directories": { "lib": "lib" @@ -75,7 +75,8 @@ "mock-req": "0.1.0", "mock-res": "0.1.0", "semver": "~2.2.1", - "prompt": "~0.2.13" + "prompt": "~0.2.13", + "uid-safe": "^1.0.1" }, "devDependencies": { "root-require": "~0.2.0", From f85912066fe5537078c4e2a5cba08e12712fd9e1 Mon Sep 17 00:00:00 2001 From: Mike McNeil Date: Tue, 16 Dec 2014 15:48:18 -0600 Subject: [PATCH 107/235] Dont rely on sails.session.set() to send back a session instance- instead change the impl to use the same session id to use store.get() --- lib/hooks/session/index.js | 123 +++++++++++++++++++------------------ 1 file changed, 63 insertions(+), 60 deletions(-) diff --git a/lib/hooks/session/index.js b/lib/hooks/session/index.js index d4cbf87b8b..5c236383fb 100644 --- a/lib/hooks/session/index.js +++ b/lib/hooks/session/index.js @@ -288,71 +288,74 @@ module.exports = function(app) { if (!_.isFunction(cb)) { throw new Error('Invalid usage :: `sails.hooks.session.set(sessionId, data, cb)`'); } - return app.config.session.store.set(sessionId, data, function (err, session){ + return app.config.session.store.set(sessionId, data, function (err){ if (err) return cb(err); - if (!session) { - return cb((function _createError(){ - var e = new Error('Session could not be saved'); - e.code = 'E_SESSION'; - return e; - })()); - } - return cb(null, session); - }); - }, - - - /** - * Generate a new Session instance and return it. - * - * @param {Function} cb - * - * @api private - */ - generate: function (cb) { - - if (typeof cb !== 'function') { - app.log.error('Failed to generate session- expected callback function as argument.\nUsage:\nsails.session.generate(callback)'); - return; - } - - var newSession; - - try { - // Generate a new session instance (and a new sid) - // ( - // 1st arg to constructor (sort of a pretend `req` object, but mainly just to get headers) - // 2nd argument is a cookie config object - // ) - newSession = new ConnectSession({headers:{}}, { - cookie: { - // Prevent access from client-side javascript - httpOnly: true, - - // Restrict to path - path: '/' - } - }); - - // Next, persist the new session - app.session.set(newSession.id, newSession, function(err) { + return app.config.session.store.get(sessionId, function (err, session) { if (err) return cb(err); - sails.log.verbose('Generated new session (sid=', newSession.id, ') for virtual request....'); - - // Pass back final session object + if (!session) { + return cb((function _createError(){ + var e = new Error('Session could not be saved'); + e.code = 'E_SESSION'; + return e; + })()); + } return cb(null, session); }); - } - // If new session instance cannot be created for some reason, bail out. - catch (e){ - return cb((function _createError(){ - var err = new Error('Could not generate new session for virtual request'); - err.code = 'E_SESSION'; - return err; - })()); - } + }); + }, + - } + // /** + // * Generate a new Session instance and return it. + // * + // * @param {Function} cb + // * + // * @api private + // */ + // generate: function (cb) { + + // if (typeof cb !== 'function') { + // app.log.error('Failed to generate session- expected callback function as argument.\nUsage:\nsails.session.generate(callback)'); + // return; + // } + + // var newSession; + + // try { + // // Generate a new session instance (and a new sid) + // // ( + // // 1st arg to constructor (sort of a pretend `req` object, but mainly just to get headers) + // // 2nd argument is a cookie config object + // // ) + // newSession = new ConnectSession({headers:{}}, { + // cookie: { + // // Prevent access from client-side javascript + // httpOnly: true, + + // // Restrict to path + // path: '/' + // } + // }); + + // // Next, persist the new session + // app.session.set(newSession.id, newSession, function(err) { + // if (err) return cb(err); + // sails.log.verbose('Generated new session (sid=', newSession.id, ') for virtual request....'); + + // // Pass back final session object + // return cb(null, session); + // }); + // } + // // If new session instance cannot be created for some reason, bail out. + // catch (e){ + // return cb((function _createError(){ + // var err = new Error('Could not generate new session for virtual request'); + // err.code = 'E_SESSION'; + // return err; + // })()); + // } + + // } }; From 7db8a63b2835d9cc5812e1ff3212d1240e552d74 Mon Sep 17 00:00:00 2001 From: Mike McNeil Date: Tue, 16 Dec 2014 15:54:16 -0600 Subject: [PATCH 108/235] error message in tests --- test/integration/hook.sockets.interpreter.test.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/integration/hook.sockets.interpreter.test.js b/test/integration/hook.sockets.interpreter.test.js index ef8f8a6520..dbb98fb593 100644 --- a/test/integration/hook.sockets.interpreter.test.js +++ b/test/integration/hook.sockets.interpreter.test.js @@ -24,6 +24,10 @@ describe('hook:sockets :: ', function() { silly: false }, function(err, sails, _socket1, _socket2) { if (err) return done(err); + + if (!_socket1 || !_socket2) { + return done(new Error('Failed to connect test sockets')); + } sailsprocess = sails; socket1 = _socket1; socket2 = _socket2; From bb40697d3a32e3effaec8d6efe5d147833ec2372 Mon Sep 17 00:00:00 2001 From: Mike McNeil Date: Tue, 16 Dec 2014 16:02:15 -0600 Subject: [PATCH 109/235] Use sails.io/socket.io client versions at v0.11/v1.0 respectively --- package.json | 4 +-- test/integration/helpers/appHelper.js | 15 +++++++-- test/integration/helpers/sails.io.js | 44 +++++++++++++++------------ 3 files changed, 39 insertions(+), 24 deletions(-) diff --git a/package.json b/package.json index cef8d490b6..6b274cf204 100644 --- a/package.json +++ b/package.json @@ -90,10 +90,10 @@ "expect.js": "~0.2.0", "should": "~2.1.1", "supertest": "~0.8.2", - "socket.io-client": "~0.9.16", "mocha": "~1.17.1", "portfinder": "~0.2.1", - "coffee-script": "~1.7.1" + "coffee-script": "~1.7.1", + "socket.io-client": "^1.2.1" }, "repository": { "type": "git", diff --git a/test/integration/helpers/appHelper.js b/test/integration/helpers/appHelper.js index 5833a34e7c..2853ead07c 100644 --- a/test/integration/helpers/appHelper.js +++ b/test/integration/helpers/appHelper.js @@ -15,12 +15,18 @@ var exec = require('child_process').exec; var path = require('path'); var sailsBin = path.resolve('./bin/sails.js'); var spawn = require('child_process').spawn; -var _ioClient = require('./sails.io')(require('socket.io-client')); var Sails = require('../../../lib/app'); // Make existsSync not crash on older versions of Node fs.existsSync = fs.existsSync || path.existsSync; + +// Set up `_ioClient` the first time this file is required +var _ioClient = require('sails.io.js')(require('socket.io-client')); +// var _ioClient = require('./sails.io')(require('socket.io-client')); + + + /** * Uses the Sails binary to create a namespaced test app * If no appName is given use 'testApp' @@ -145,8 +151,11 @@ module.exports.liftWithTwoSockets = function(options, callback) { } module.exports.lift(options, function(err, sails) { if (err) {return callback(err);} - var socket1 = _ioClient.connect('http://localhost:1342',{'force new connection': true}); - socket1.on('connect', function() { + console.log('trying to connect socket1'); + var socket1 = _ioClient.connect('http://localhost:1342',{'force new connection': true}); + socket1.on('connect', function() { + console.log('socket1 connected'); + console.log('trying to connect socket2'); var socket2 = _ioClient.connect('http://localhost:1342',{'force new connection': true}); socket2.on('connect', function() { callback(null, sails, socket1, socket2); diff --git a/test/integration/helpers/sails.io.js b/test/integration/helpers/sails.io.js index 83bd0f5641..c672d02238 100644 --- a/test/integration/helpers/sails.io.js +++ b/test/integration/helpers/sails.io.js @@ -1,3 +1,9 @@ +/** + * Version 0.10.x of the sails.io.js JavaScript SDK. + * Here to test backwards compatibility. + */ + + /*! Socket.IO.js build:0.9.16, development. Copyright(c) 2011 LearnBoost MIT Licensed */ @@ -1618,7 +1624,7 @@ var io = ('undefined' === typeof module ? {} : module.exports); } else if (xhr.status == 403) { self.onError(xhr.responseText); } else { - self.connecting = false; + self.connecting = false; !self.reconnecting && self.onError(xhr.responseText); } } @@ -1662,7 +1668,7 @@ var io = ('undefined' === typeof module ? {} : module.exports); var self = this; self.connecting = true; - + this.handshake(function (sid, heartbeat, close, transports) { self.sessionid = sid; self.closeTimeout = close * 1000; @@ -1784,7 +1790,7 @@ var io = ('undefined' === typeof module ? {} : module.exports); this.transport.payload(this.buffer); this.buffer = []; }; - + /** * Disconnect the established connect. @@ -1844,7 +1850,7 @@ var io = ('undefined' === typeof module ? {} : module.exports); var port = global.location.port || ('https:' == global.location.protocol ? 443 : 80); - return this.options.host !== global.location.hostname + return this.options.host !== global.location.hostname || this.options.port != port; }; @@ -2125,7 +2131,7 @@ var io = ('undefined' === typeof module ? {} : module.exports); * * @api public */ - + SocketNamespace.prototype.emit = function (name) { var args = Array.prototype.slice.call(arguments, 1) , lastArg = args[args.length - 1] @@ -2363,8 +2369,8 @@ var io = ('undefined' === typeof module ? {} : module.exports); * @api public */ - // Do to a bug in the current IDevices browser, we need to wrap the send in a - // setTimeout, when they resume from sleeping the browser will crash if + // Do to a bug in the current IDevices browser, we need to wrap the send in a + // setTimeout, when they resume from sleeping the browser will crash if // we don't allow the browser time to detect the socket has been closed if (io.util.ua.iDevice) { WS.prototype.send = function (data) { @@ -2697,7 +2703,7 @@ var io = ('undefined' === typeof module ? {} : module.exports); /** * The HTMLFile transport creates a `forever iframe` based transport - * for Internet Explorer. Regular forever iframe implementations will + * for Internet Explorer. Regular forever iframe implementations will * continuously trigger the browsers buzy indicators. If the forever iframe * is created inside a `htmlfile` these indicators will not be trigged. * @@ -2911,7 +2917,7 @@ var io = ('undefined' === typeof module ? {} : module.exports); return false; }; - /** + /** * Establish a connection, for iPhone and Android this will be done once the page * is loaded. * @@ -2963,7 +2969,7 @@ var io = ('undefined' === typeof module ? {} : module.exports); function onerror () { self.retryCounter ++; if(!self.retryCounter || self.retryCounter > 3) { - self.onClose(); + self.onClose(); } else { self.get(); } @@ -3517,11 +3523,11 @@ if (typeof define === "function" && define.amd) { /** * What is the `requestQueue`? - * + * * The request queue is used to simplify app-level connection logic-- * i.e. so you don't have to wait for the socket to be connected * to start trying to synchronize data. - * + * * @api private * @param {Socket} socket */ @@ -3551,7 +3557,7 @@ if (typeof define === "function" && define.amd) { /** * Send a JSONP request. - * + * * @param {Object} opts [optional] * @param {Function} cb * @return {XMLHttpRequest} @@ -3651,7 +3657,7 @@ if (typeof define === "function" && define.amd) { - // We'll be adding methods to `io.SocketNamespace.prototype`, the prototype for the + // We'll be adding methods to `io.SocketNamespace.prototype`, the prototype for the // Socket instance returned when the browser connects with `io.connect()` var Socket = io.SocketNamespace; @@ -3870,10 +3876,10 @@ if (typeof define === "function" && define.amd) { // The environment we're running in. // (logs are not displayed when this is set to 'production') - // + // // Defaults to development unless this script was fetched from a URL // that ends in `*.min.js` or '#production' (may also be manually overridden.) - // + // environment: urlThisScriptWasFetchedFrom.match(/(\#production|\.min\.js)/g) ? 'production' : 'development' }; @@ -3910,13 +3916,13 @@ if (typeof define === "function" && define.amd) { // io.socket - // + // // The eager instance of Socket which will automatically try to connect // using the host that this js file was served from. - // + // // This can be disabled or configured by setting `io.socket.options` within the // first cycle of the event loop. - // + // // In the mean time, this eager socket will be defined as a TmpSocket // so that events bound by the user before the first cycle of the event From 77029a9866e4fe55e6bda821c681e561cfaa3dd1 Mon Sep 17 00:00:00 2001 From: Mike McNeil Date: Tue, 16 Dec 2014 16:13:06 -0600 Subject: [PATCH 110/235] Use v0.11 sails.io.js client in tests. --- package.json | 3 ++- test/integration/helpers/appHelper.js | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 6b274cf204..3c20d0b6c3 100644 --- a/package.json +++ b/package.json @@ -93,7 +93,8 @@ "mocha": "~1.17.1", "portfinder": "~0.2.1", "coffee-script": "~1.7.1", - "socket.io-client": "^1.2.1" + "socket.io-client": "^1.2.1", + "sails.io.js": "^0.11.0" }, "repository": { "type": "git", diff --git a/test/integration/helpers/appHelper.js b/test/integration/helpers/appHelper.js index 2853ead07c..b1b97d5d86 100644 --- a/test/integration/helpers/appHelper.js +++ b/test/integration/helpers/appHelper.js @@ -151,11 +151,11 @@ module.exports.liftWithTwoSockets = function(options, callback) { } module.exports.lift(options, function(err, sails) { if (err) {return callback(err);} - console.log('trying to connect socket1'); + // console.log('trying to connect socket1'); var socket1 = _ioClient.connect('http://localhost:1342',{'force new connection': true}); socket1.on('connect', function() { - console.log('socket1 connected'); - console.log('trying to connect socket2'); + // console.log('socket1 connected'); + // console.log('trying to connect socket2'); var socket2 = _ioClient.connect('http://localhost:1342',{'force new connection': true}); socket2.on('connect', function() { callback(null, sails, socket1, socket2); From fbc84bdd7c34f0d5ec2656b5fffcc3982c808a65 Mon Sep 17 00:00:00 2001 From: Mike McNeil Date: Tue, 16 Dec 2014 16:16:05 -0600 Subject: [PATCH 111/235] Trivial, update todos and cleanup. --- lib/router/req.js | 3 --- lib/router/res.js | 19 ++++++------------- 2 files changed, 6 insertions(+), 16 deletions(-) diff --git a/lib/router/req.js b/lib/router/req.js index da163302f5..26a72475b5 100644 --- a/lib/router/req.js +++ b/lib/router/req.js @@ -74,9 +74,6 @@ module.exports = function buildRequest (_req) { // TODO: add all the other methods in core - // Set session (or build up a fake one) - // req.session = _req.session || {}; - // Provide defaults for other request state and methods req = defaultsDeep(req, { params: [], diff --git a/lib/router/res.js b/lib/router/res.js index c0f98beba5..29d892e63a 100644 --- a/lib/router/res.js +++ b/lib/router/res.js @@ -32,9 +32,7 @@ module.exports = function buildResponse (req, _res) { res = new MockRes(); } - // Ensure res.headers and res.locals exist. - // res = _.merge(res, {locals: {}, headers: {}}, _res); res = _.extend(res, {locals: {}, headers: {}, _headers: {}}); res = _.extend(res, _res); @@ -149,12 +147,6 @@ module.exports = function buildResponse (req, _res) { }; - var prevSetHeader = res.setHeader; - res.setHeader = function (){ - // console.log('called res.setHeader() w/ args=',arguments); - prevSetHeader.apply(res, Array.prototype.slice.call(arguments)); - }; - // Wrap res.write() and res.end() to get them to call writeHead() var prevWrite = res.write; res.write = function (){ @@ -171,15 +163,17 @@ module.exports = function buildResponse (req, _res) { }; - // we get `setHeader` from mock-res // see http://nodejs.org/api/http.html#http_response_setheader_name_value // // Usage: // response.setHeader("Set-Cookie", ["type=ninja", "language=javascript"]); - // res.setHeader = function (headerName, value){ - // console.log('set header',headerName, '=',value); - // res.headers[headerName] = value; + + // If we ever need to wrap it... + // + // var prevSetHeader = res.setHeader; + // res.setHeader = function (){ + // prevSetHeader.apply(res, Array.prototype.slice.call(arguments)); // }; // res.status() @@ -206,7 +200,6 @@ module.exports = function buildResponse (req, _res) { return; } - // Ensure charset is set res.charset = res.charset || 'utf-8'; From f540e75afbecbaf2f9e9958b2c434e64c2b78301 Mon Sep 17 00:00:00 2001 From: Mike McNeil Date: Tue, 16 Dec 2014 20:33:23 -0600 Subject: [PATCH 112/235] Added first pass at 0.11 migration guide. --- 0.11-migration-guide.md | 150 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100644 0.11-migration-guide.md diff --git a/0.11-migration-guide.md b/0.11-migration-guide.md new file mode 100644 index 0000000000..0b9dcda275 --- /dev/null +++ b/0.11-migration-guide.md @@ -0,0 +1,150 @@ +# v0.11 Migration Guide + + +**tldr;** + +v0.11 comes with many minor improvements, as well as some internal cleanup in core. The biggest change is that Sails core is now using Socket.io v1. + +There aren't many changes that will affect how you use Sails in your project, but there are a few important changes to be aware of. We've listed them below. + + +## Differences + +### The "firehose" + +The `firehose` feature for testing sockets has been deprecated. This includes the basic usage, as well as the following methods: + + sails.sockets.drink() + + sails.sockets.spit() + + sails.sockets.squirt() + +> If you want the "firehose" back, let [Mike know on twitter](http://twitter.com/mikermcneil) (it can be brought back as a separate hook). + + +### Upgrade the Socket.io / Sails.io browser client + +Old v0.9 socket.io client will no longer work, so consequently you'll need to upgrade your sails.io.js client from v0.9 or v0.10 to v0.11. + +To do this, just remove your sails.io.js client and install the new one. We've bundled a helper generator that will do this for you, assuming your sails.io.js client is in the conventional location at `assets/js/dependencies/sails.io.js` (i.e. if you haven't moved or renamed it): + +```sh +sails generate sails.io.js --force +``` + + + +## New features + +Sails v0.11 also comes with some new stuff that we thought you'd like to know about: + + +### User-level hooks + +Hooks can now be installed directly from NPM. + +This means you can now install hooks with a single command in your terminal. For instance, consider the [`autoreload` hook]() by @sgress454, which watches for changes to your backend code so you don't need to kill and re-lift the server every time you change your controllers, routes, models, etc. + +To install the `autoreload` hook, run: + +```sh +npm install sails-hook-autoreload +``` + +This is just one example of what's possible. As you might already know, hooks are the lowest-level pluggable abstraction in Sails. They allow authors to tap into the lift process, listen for events, inject custom "shadow" routes, and, in general, take advantage of raw access to the `sails` runtime. +Most of the features you're familiar with in Sails have actually already been implemented as "core" hooks for over a year, including: + ++ `blueprints` _(which provides the blueprint API)_ ++ `sockets` _(which provides socket.io integration)_ ++ `grunt` _(which provides Grunt integration)_ ++ `orm` _(which provides integration with the Waterline ORM, and imports your projects adapters, models, etc.)_ ++ `http` _(which provides an HTTP server) ++ and 16 others. + +You can read more about how to write your own hooks in the new and improved "Extending Sails" documentation on http://sailsjs.org. + + +### Socket.io v1.x + +The upgrade to Socket.io v1.0 shouldn't actually affect your app-level code, provided you are using the layer of abstraction provided by Sails itself; everything from the `sails.sockets.*` wrapper methods and "up" (resourceful pubsub, blueprints) +If you are using underlying socket.io methods in your apps, or are just curious about what changed in Socket.io v1.0, be sure and check out the [complete Socket.io 1.0 migration guide](http://socket.io/docs/migrating-from-0-9/) from Guillermo and the socket.io team. + +### Ever-increasing modularity + +As part of the upgrade to Socket.io v1.0, we pulled out the core `sockets` hook into a separate repository. This allowed us to write some modular, hook-specific tests for the socket.io interpreter, which will make things easier to maintain, customize, and override. +This also allows the hook to grow at its own pace, and puts related issues in one place. + +Consider this a test of the pros and cons of pulling other hooks out of the sails core repo over the next few months. This will make Sails core lighter, faster, and more extensible, with fewer core dependencies, shorter "lift" time for most apps, and faster `npm install`s. + + +### `sails.request()` + +In the process of pulling the `sockets` hook _out_ of core, the logic which interprets requests has been normalized and is now located _in_ Sails core. As a result, the `sails.request()` method is now much more powerful. + +This method allows you to communicate directly with the request interpreter in Sails without lifting your server onto a port. It's the same mechanism that Sails uses to map incoming messages from Socket.io to "virtual requests" that have the familiar `req` and `res` streams. + +The primary use case for `sails.request()` is in writing faster-running unit and integration tests, but it's also handy for proxying to mounted apps (or "sub-apps"). + +For instance, here is an example (using mocha) of how you might test one of your app's routes: + +```js +var assert = require('assert'); +var Sails = require('sails').Sails; + +before(function beforeRunningAnyTests (done){ + + // Load the app (no need to "lift" to a port) + sails.load({ + log: { + level: 'warn' + }, + hooks: { + grunt: false + } + }, function whenAppIsReady(err){ + if (err) return done(err); + + // At this point, the `sails` global is exposed, although we + // could have disabled it above with our config overrides to + // `sails.load()`. In fact, you can actually use this technique + // to set any configuration setting you like. + return done(); + }); +}); + +after(function afterTestsFinish (done) { + sails.lower(done); +}); + +describe('GET /hotpockets', function (){ + + it('should respond with a 200 status code', function (done){ + + sails.request({ + method: 'get', + url: '/hotpockets', + params: { + limit: 10, + sort: 'price ASC' + } + }, function (err, clientRes, body) { + if (err) return done(err); + + assert.equal(clientRes.statusCode, 200); + return done(); + }); + + }); +}); +``` + + +## Questions? + +As always, if you run into issues upgrading, or if any of the notes above don't make sense, let me know and I'll do what I can to clarify. + +Finally, to those of you that have contributed to the project since the v0.10 release in August: I can't stress enough how much I value your continued support and encouragement. There is a pretty massive stream of issues, pull requests, documentation tweaks, and questions, but it always helps to know that we're in this together :) + +Thanks. + +-@mikermcneil + + From 034dac64d713ee124d577151eb84b88325d569d0 Mon Sep 17 00:00:00 2001 From: Mike McNeil Date: Tue, 16 Dec 2014 20:34:34 -0600 Subject: [PATCH 113/235] Made things more clear --- 0.11-migration-guide.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/0.11-migration-guide.md b/0.11-migration-guide.md index 0b9dcda275..a85e923668 100644 --- a/0.11-migration-guide.md +++ b/0.11-migration-guide.md @@ -5,7 +5,7 @@ v0.11 comes with many minor improvements, as well as some internal cleanup in core. The biggest change is that Sails core is now using Socket.io v1. -There aren't many changes that will affect how you use Sails in your project, but there are a few important changes to be aware of. We've listed them below. +Almost none of this should affect the existing code in project, but there are a few important differences and new features to be aware of. We've listed them below. ## Differences From f65e96ae4a03153d6895d08cbd93be80c77cf74a Mon Sep 17 00:00:00 2001 From: Mike McNeil Date: Tue, 16 Dec 2014 20:37:37 -0600 Subject: [PATCH 114/235] Update 0.11-migration-guide.md --- 0.11-migration-guide.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/0.11-migration-guide.md b/0.11-migration-guide.md index a85e923668..20ae2ace02 100644 --- a/0.11-migration-guide.md +++ b/0.11-migration-guide.md @@ -75,7 +75,7 @@ This also allows the hook to grow at its own pace, and puts related issues in on Consider this a test of the pros and cons of pulling other hooks out of the sails core repo over the next few months. This will make Sails core lighter, faster, and more extensible, with fewer core dependencies, shorter "lift" time for most apps, and faster `npm install`s. -### `sails.request()` +### Testing, the "virtual" request interpreter, and the `sails.request()` method In the process of pulling the `sockets` hook _out_ of core, the logic which interprets requests has been normalized and is now located _in_ Sails core. As a result, the `sails.request()` method is now much more powerful. From adfe939802144661328c2e216a69fbd8d272dc4f Mon Sep 17 00:00:00 2001 From: Mike McNeil Date: Tue, 16 Dec 2014 20:39:48 -0600 Subject: [PATCH 115/235] Update 0.11-migration-guide.md --- 0.11-migration-guide.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/0.11-migration-guide.md b/0.11-migration-guide.md index 20ae2ace02..115b2d56e0 100644 --- a/0.11-migration-guide.md +++ b/0.11-migration-guide.md @@ -17,7 +17,7 @@ The `firehose` feature for testing sockets has been deprecated. This includes t + sails.sockets.spit() + sails.sockets.squirt() -> If you want the "firehose" back, let [Mike know on twitter](http://twitter.com/mikermcneil) (it can be brought back as a separate hook). +> If you want the "firehose" back, let [Mike know on twitter](http://twitter.com/mikermcneil) (it can be brought back as a separate hook). But... yeah. ### Upgrade the Socket.io / Sails.io browser client @@ -56,7 +56,7 @@ Most of the features you're familiar with in Sails have actually already been im + `sockets` _(which provides socket.io integration)_ + `grunt` _(which provides Grunt integration)_ + `orm` _(which provides integration with the Waterline ORM, and imports your projects adapters, models, etc.)_ -+ `http` _(which provides an HTTP server) ++ `http` _(which provides an HTTP server)_ + and 16 others. You can read more about how to write your own hooks in the new and improved "Extending Sails" documentation on http://sailsjs.org. @@ -77,7 +77,7 @@ Consider this a test of the pros and cons of pulling other hooks out of the sail ### Testing, the "virtual" request interpreter, and the `sails.request()` method -In the process of pulling the `sockets` hook _out_ of core, the logic which interprets requests has been normalized and is now located _in_ Sails core. As a result, the `sails.request()` method is now much more powerful. +In the process of pulling the `sockets` hook _out_ of core, the logic which interprets requests has been normalized and is now located _in_ Sails core. As a result, the `sails.request()` method is much more powerful. This method allows you to communicate directly with the request interpreter in Sails without lifting your server onto a port. It's the same mechanism that Sails uses to map incoming messages from Socket.io to "virtual requests" that have the familiar `req` and `res` streams. From 584fa1cbff2477683c4dcda38222d7e3d72c3217 Mon Sep 17 00:00:00 2001 From: Mike McNeil Date: Tue, 16 Dec 2014 20:40:28 -0600 Subject: [PATCH 116/235] Update 0.11-migration-guide.md --- 0.11-migration-guide.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/0.11-migration-guide.md b/0.11-migration-guide.md index 115b2d56e0..cff94f8de3 100644 --- a/0.11-migration-guide.md +++ b/0.11-migration-guide.md @@ -10,7 +10,7 @@ Almost none of this should affect the existing code in project, but there are a ## Differences -### The "firehose" +#### The "firehose" The `firehose` feature for testing sockets has been deprecated. This includes the basic usage, as well as the following methods: + sails.sockets.drink() @@ -20,7 +20,7 @@ The `firehose` feature for testing sockets has been deprecated. This includes t > If you want the "firehose" back, let [Mike know on twitter](http://twitter.com/mikermcneil) (it can be brought back as a separate hook). But... yeah. -### Upgrade the Socket.io / Sails.io browser client +#### Upgrade the Socket.io / Sails.io browser client Old v0.9 socket.io client will no longer work, so consequently you'll need to upgrade your sails.io.js client from v0.9 or v0.10 to v0.11. @@ -37,7 +37,7 @@ sails generate sails.io.js --force Sails v0.11 also comes with some new stuff that we thought you'd like to know about: -### User-level hooks +#### User-level hooks Hooks can now be installed directly from NPM. @@ -62,12 +62,12 @@ Most of the features you're familiar with in Sails have actually already been im You can read more about how to write your own hooks in the new and improved "Extending Sails" documentation on http://sailsjs.org. -### Socket.io v1.x +#### Socket.io v1.x The upgrade to Socket.io v1.0 shouldn't actually affect your app-level code, provided you are using the layer of abstraction provided by Sails itself; everything from the `sails.sockets.*` wrapper methods and "up" (resourceful pubsub, blueprints) If you are using underlying socket.io methods in your apps, or are just curious about what changed in Socket.io v1.0, be sure and check out the [complete Socket.io 1.0 migration guide](http://socket.io/docs/migrating-from-0-9/) from Guillermo and the socket.io team. -### Ever-increasing modularity +#### Ever-increasing modularity As part of the upgrade to Socket.io v1.0, we pulled out the core `sockets` hook into a separate repository. This allowed us to write some modular, hook-specific tests for the socket.io interpreter, which will make things easier to maintain, customize, and override. This also allows the hook to grow at its own pace, and puts related issues in one place. @@ -75,7 +75,7 @@ This also allows the hook to grow at its own pace, and puts related issues in on Consider this a test of the pros and cons of pulling other hooks out of the sails core repo over the next few months. This will make Sails core lighter, faster, and more extensible, with fewer core dependencies, shorter "lift" time for most apps, and faster `npm install`s. -### Testing, the "virtual" request interpreter, and the `sails.request()` method +#### Testing, the "virtual" request interpreter, and the `sails.request()` method In the process of pulling the `sockets` hook _out_ of core, the logic which interprets requests has been normalized and is now located _in_ Sails core. As a result, the `sails.request()` method is much more powerful. From 9316c84d65f25f8be5133b0d5d2fa9825772f260 Mon Sep 17 00:00:00 2001 From: Mike McNeil Date: Tue, 16 Dec 2014 20:41:59 -0600 Subject: [PATCH 117/235] Update 0.11-migration-guide.md --- 0.11-migration-guide.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/0.11-migration-guide.md b/0.11-migration-guide.md index cff94f8de3..5fb64c2c14 100644 --- a/0.11-migration-guide.md +++ b/0.11-migration-guide.md @@ -12,7 +12,7 @@ Almost none of this should affect the existing code in project, but there are a #### The "firehose" -The `firehose` feature for testing sockets has been deprecated. This includes the basic usage, as well as the following methods: +The "firehose" feature for testing with sockets has been deprecated. If you don't know what that means, you have nothing to worry about. The basic usage will continue to work for a while, but it will soon be removed from core and should not be relied upon in your app. This also applies to the following methods: + sails.sockets.drink() + sails.sockets.spit() + sails.sockets.squirt() From e075a5128b3bc2a4ccc0d63e9309141f83b660a6 Mon Sep 17 00:00:00 2001 From: Mike McNeil Date: Tue, 16 Dec 2014 20:47:46 -0600 Subject: [PATCH 118/235] pull out 'low-level socket methods' into socket hook. --- lib/hooks/pubsub/index.js | 234 -------------------------------------- 1 file changed, 234 deletions(-) diff --git a/lib/hooks/pubsub/index.js b/lib/hooks/pubsub/index.js index 7bcfd6bb9b..6ff9f77dc6 100644 --- a/lib/hooks/pubsub/index.js +++ b/lib/hooks/pubsub/index.js @@ -42,10 +42,6 @@ module.exports = function(sails) { return cb( Err.dependency('pubsub', 'sockets') ); } - // Add low-level, generic socket methods. These are mostly just wrappers - - // around socket.io, to enforce a little abstraction. - addLowLevelSocketMethods(); if (!sails.hooks.orm) { return cb( Err.dependency('pubsub', 'orm') ); @@ -97,236 +93,6 @@ module.exports = function(sails) { } }; - function addLowLevelSocketMethods () { - - sails.sockets = {}; - sails.sockets.DEFAULT_EVENT_NAME = 'message'; - - sails.sockets.subscribeToFirehose = require('./drink')(sails); - sails.sockets.unsubscribeFromFirehose = require('./drink')(sails); - - sails.sockets.publishToFirehose = require('./squirt')(sails); - - - /** - * Subscribe a socket to a generic room - * @param {object} socket The socket to subscribe. - * @param {string} roomName The room to subscribe to - */ - sails.sockets.join = function(sockets, roomName) { - - if (!sails.util.isArray(sockets)) { - sockets = [sockets]; - } - - sails.util.each(sockets, function(socket) { - // If a string was sent, try to look up a socket with that ID - if (typeof socket == 'string') {socket = sails.io.sockets.socket(socket);} - - // If it's not a valid socket object, bail - if (! (socket && socket.manager) ) { - sails.log.warn("Attempted to call `sails.sockets.join`, but the first argument was not a socket."); - return; - } - // Join up! - socket.join(roomName); - - }); - - return true; - - }; - - /** - * Unsubscribe a socket from a generic room - * @param {object} socket The socket to unsubscribe. - * @param {string} roomName The room to unsubscribe from - */ - sails.sockets.leave = function(sockets, roomName) { - - if (!sails.util.isArray(sockets)) { - sockets = [sockets]; - } - - sails.util.each(sockets, function(socket) { - // If a string was sent, try to look up a socket with that ID - if (typeof socket == 'string') {socket = sails.io.sockets.socket(socket);} - - // If it's not a valid socket object, bail - if (! (socket && socket.manager) ) { - sails.log.warn("Attempted to call `sails.sockets.leave`, but the first argument was not a socket."); - return; - } - - // See ya! - socket.leave(roomName); - }); - - return true; - }; - - /** - * Broadcast a message to a room - * - * If the event name is omitted, "message" will be used by default. - * Thus, sails.sockets.broadcast(roomName, data) is also a valid usage. - * - * @param {string} roomName The room to broadcast a message to - * @param {string} eventName The event name to broadcast - * @param {object} data The data to broadcast - * @param {object} socket Optional socket to omit - */ - - sails.sockets.broadcast = function(roomName, eventName, data, socketToOmit) { - - // If the 'eventName' is an object, assume the argument was omitted and - // parse it as data instead. - if (typeof eventName === 'object') { - data = eventName; - eventName = null; - } - - // Default to the sails.sockets.DEFAULT_EVENT_NAME. - if (!eventName) { - eventName = sails.sockets.DEFAULT_EVENT_NAME; - } - - // If we were given a valid socket to omit, broadcast from there. - if (socketToOmit && socketToOmit.manager) { - socketToOmit.broadcast.to(roomName).emit(eventName, data); - } - // Otherwise broadcast to everyone - else { - sails.io.sockets.in(roomName).emit(eventName, data); - } - }; - - - - /** - * Broadcast a message to all connected sockets - * - * If the event name is omitted, sails.sockets.DEFAULT_EVENT_NAME will be used by default. - * Thus, sails.sockets.blast(data) is also a valid usage. - * - * @param {string} event The event name to broadcast - * @param {object} data The data to broadcast - * @param {object} socket Optional socket to omit - */ - - sails.sockets.blast = function(eventName, data, socketToOmit) { - - // If the 'eventName' is an object, assume the argument was omitted and - // parse it as data instead. - if (typeof eventName === 'object') { - data = eventName; - eventName = null; - } - - // Default to the sails.sockets.DEFAULT_EVENT_NAME eventName. - if (!eventName) { - eventName = sails.sockets.DEFAULT_EVENT_NAME; - } - - // If we were given a valid socket to omit, broadcast from there. - if (socketToOmit && socketToOmit.manager) { - socketToOmit.broadcast.emit(eventName, data); - } - - // Otherwise broadcast to everyone - else { - sails.io.sockets.emit(eventName, data); - } - }; - - - - - /** - * Get the ID of a socket object - * @param {object} socket The socket object to get the ID of - * @return {string} The socket's ID - */ - sails.sockets.id = function(socket) { - // If a request was passed in, get its socket - socket = socket.socket || socket; - if (socket) { - return socket.id; - } - else return undefined; - }; - - /** - * Emit a message to one or more sockets by ID - * - * If the event name is omitted, "message" will be used by default. - * Thus, sails.sockets.emit(socketIDs, data) is also a valid usage. - * - * @param {array|string} socketIDs The ID or IDs of sockets to send a message to - * @param {string} event The name of the message to send - * @param {object} data Optional data to send with the message - */ - sails.sockets.emit = function(socketIDs, eventName, data) { - if (!_.isArray(socketIDs)) { - socketIDs = [socketIDs]; - } - - if (typeof eventName === 'object') { - data = eventName; - eventName = null; - } - - if (!eventName) { - eventName = sails.sockets.DEFAULT_EVENT_NAME; - } - - _.each(socketIDs, function(socketID) { - sails.io.sockets.socket(socketID).emit(eventName, data); - }); - }; - - /** - * Get the list of sockets subscribed to a room - * @param {string} roomName The room to get subscribers of - * @param {boolean} returnSockets If true, return socket instances rather than IDs. - * @return {array} An array of socket ID strings - */ - sails.sockets.subscribers = function(roomName, returnSockets) { - - // The underlying implementation was changed a bit with the upgrade - // to Socket.io v1.0. For more information, see: - // •-> https://github.com/Automattic/socket.io/issues/1908#issuecomment-66836641 - // and •-> https://github.com/Automattic/socket.io/pull/1630#issuecomment-64389524 - - if (returnSockets) { - return sails.io.nsps['/'].adapter.rooms[roomName]; - // return sails.io.sockets.clients(roomName); - } else { - return _.keys(sails.io.nsps['/'].adapter.rooms[roomName]); - // return _.pluck(sails.io.sockets.clients(roomName), 'id'); - } - }; - - /** - * Get the list of rooms a socket is subscribed to - * @param {object} socket The socket to get rooms for - * @return {array} An array of room names - */ - sails.sockets.socketRooms = function(socket) { - return _.map(_.keys(sails.io.sockets.manager.roomClients[socket.id]), function(roomName) {return roomName.replace(/^\//,'');}); - }; - - /** - * Get the list of all rooms - * @return {array} An array of room names, minus the empty room - */ - sails.sockets.rooms = function() { - var rooms = sails.util.clone(sails.io.sockets.manager.rooms); - delete rooms[""]; - return sails.util.map(sails.util.keys(rooms), function(room){return room.substr(1);}); - }; - - } /** * These methods get appended to the Model class objects From 8c864a9fda07cc1218561c795217fd09742dd7e7 Mon Sep 17 00:00:00 2001 From: Mike McNeil Date: Wed, 17 Dec 2014 07:50:05 -0600 Subject: [PATCH 119/235] Added two firehose methods to list of differences --- 0.11-migration-guide.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/0.11-migration-guide.md b/0.11-migration-guide.md index 5fb64c2c14..18b5a9a869 100644 --- a/0.11-migration-guide.md +++ b/0.11-migration-guide.md @@ -13,6 +13,8 @@ Almost none of this should affect the existing code in project, but there are a #### The "firehose" The "firehose" feature for testing with sockets has been deprecated. If you don't know what that means, you have nothing to worry about. The basic usage will continue to work for a while, but it will soon be removed from core and should not be relied upon in your app. This also applies to the following methods: + + sails.sockets.subscribeToFirehose() + + sails.sockets.unsubscribeFromFirehose() + sails.sockets.drink() + sails.sockets.spit() + sails.sockets.squirt() From e0d8b3ea3a62c47c728141f83277a8a0047d0637 Mon Sep 17 00:00:00 2001 From: Mike McNeil Date: Wed, 17 Dec 2014 08:30:44 -0600 Subject: [PATCH 120/235] Add url to verbose methods about finalhandler not matching up --- lib/router/bindDefaultHandlers.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/router/bindDefaultHandlers.js b/lib/router/bindDefaultHandlers.js index 2d8f925556..3434235054 100644 --- a/lib/router/bindDefaultHandlers.js +++ b/lib/router/bindDefaultHandlers.js @@ -85,7 +85,7 @@ module.exports = function(sails) { // Catch-all: // Log a message and try to use `res.send` to respond. try { - sails.log.verbose('A request did not match any routes, and no `res.notFound` handler is configured.'); + sails.log.verbose('A request (%s) did not match any routes, and no `res.notFound` handler is configured.', req.url); res.send(404); return; } From ccaf6e20ec6aa004c730577ae18a20cbb5796163 Mon Sep 17 00:00:00 2001 From: Mike McNeil Date: Wed, 17 Dec 2014 20:35:29 -0600 Subject: [PATCH 121/235] 0.11.0-rc1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3c20d0b6c3..3f86b2dc28 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "sails", "author": "Mike McNeil <@mikermcneil>", - "version": "0.11.0", + "version": "0.11.0-rc1", "description": "API-driven framework for building realtime apps, using MVC conventions (based on Express and Socket.io)", "homepage": "http://sailsjs.org", "keywords": [ From d922d338a72f7b2efc05270f9f8ad57f2d537d62 Mon Sep 17 00:00:00 2001 From: Mike McNeil Date: Wed, 17 Dec 2014 20:45:24 -0600 Subject: [PATCH 122/235] use ngen0.12 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3f86b2dc28..362557ea3c 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,7 @@ "ejs-locals": "~1.0.2", "glob": "~3.2.9", "i18n": "~0.5.0", - "sails-generate": "~0.11.2", + "sails-generate": "~0.12.0", "sails-build-dictionary": "~0.10.1", "grunt-cli": "~0.1.11", "ejs": "~0.8.4", From d67afb224fdff20c4e37931246dd790bf707c6a7 Mon Sep 17 00:00:00 2001 From: Konstantin Zolotarev Date: Thu, 18 Dec 2014 15:34:44 +0600 Subject: [PATCH 123/235] Added tests for blueprints `prefix` config option --- lib/hooks/blueprints/index.js | 7 +++ test/integration/router.APIScaffold.test.js | 47 +++++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/lib/hooks/blueprints/index.js b/lib/hooks/blueprints/index.js index 4a37770a39..7f037057a9 100644 --- a/lib/hooks/blueprints/index.js +++ b/lib/hooks/blueprints/index.js @@ -73,6 +73,13 @@ module.exports = function(sails) { // // e.g. 'get /api/v2/frog/:id?': 'FrogController.find' prefix: '', + + // Blueprint/REST-Route Modifiers + // Will work only for REST and will extend `prefix` option + // + // e.g. 'get /api/v2/frog/:id?': 'FrogController.find' + restPrefix: '', + // e.g. 'get /frogs': 'FrogController.find' pluralize: false, diff --git a/test/integration/router.APIScaffold.test.js b/test/integration/router.APIScaffold.test.js index 119b6016bb..6211c6257b 100644 --- a/test/integration/router.APIScaffold.test.js +++ b/test/integration/router.APIScaffold.test.js @@ -197,6 +197,53 @@ describe('router :: ', function() { }); }); }); + + describe('with `prefix` option set', function() { + + before(function() { + httpHelper.writeBlueprint({ + prefix: '/api' + }); + }); + + it('should not bind blueprint actions without prefix', function(done) { + httpHelper.testRoute('get', { + url: 'empty', + json: true + }, function(err, response) { + if (err) done(new Error(err)); + + assert(response.statusCode === 404); + done(); + }); + }); + + it('should return JSON for a newly created instance of the test model called with prefix', function(done) { + + httpHelper.testRoute('get', { + url: 'api/empty/create', + json: true + }, function (err, response, body) { + if (err) return done(new Error(err)); + + assert((response.statusCode === 201 || response.statusCode === 200)); + done(); + }); + }); + + it('should bind blueprint actions with given prefix', function(done) { + httpHelper.testRoute('get', { + url: 'api/empty', + json: true + }, function(err, response) { + if (err) done(new Error(err)); + + assert(response.body instanceof Array); + done(); + }); + }); + + }); }); }); From 86a0a57121914f45e349ffc89a5e07da6bf97010 Mon Sep 17 00:00:00 2001 From: Konstantin Zolotarev Date: Thu, 18 Dec 2014 19:39:34 +0600 Subject: [PATCH 124/235] Binding restfull routes --- lib/hooks/blueprints/index.js | 52 +++++++++-- test/integration/router.APIScaffold.test.js | 98 ++++++++++++++++++++- 2 files changed, 140 insertions(+), 10 deletions(-) diff --git a/lib/hooks/blueprints/index.js b/lib/hooks/blueprints/index.js index 7f037057a9..a5107261bd 100644 --- a/lib/hooks/blueprints/index.js +++ b/lib/hooks/blueprints/index.js @@ -203,6 +203,30 @@ module.exports = function(sails) { } } + // Validate blueprint config for this controller + if ( config.restPrefix ) { + if ( !_(config.restPrefix).isString() ) { + sails.after('lifted', function () { + sails.log.blank(); + sails.log.warn(util.format('Ignoring invalid blueprint rest prefix configured for controller `%s`.', globalId)); + sails.log.warn('`restPrefix` should be a string, e.g. "/api/v1".'); + STRINGFILE.logMoreInfoLink(STRINGFILE.get('links.docs.config.blueprints'), sails.log.warn); + }); + return; + } + if ( !config.restPrefix.match(/^\//) ) { + var originalRestPrefix = config.restPrefix; + sails.after('lifted', function () { + sails.log.blank(); + sails.log.warn(util.format('Invalid blueprint restPrefix ("%s") configured for controller `%s` (should start with a `/`).', originalRestPrefix, globalId)); + sails.log.warn(util.format('For now, assuming you meant: "%s".', config.restPrefix)); + STRINGFILE.logMoreInfoLink(STRINGFILE.get('links.docs.config.blueprints'), sails.log.warn); + }); + + config.restPrefix = '/' + config.restPrefix; + } + } + // Determine the names of the controller's user-defined actions // IMPORTANT: Use `sails.controllers` instead of `sails.middleware.controllers` // (since `sails.middleware.controllers` will have blueprints already mixed-in, @@ -213,6 +237,10 @@ module.exports = function(sails) { // Determine base route var baseRoute = config.prefix + '/' + controllerId; + + //@todo check if both prefixes are / / + var baseRestRoute = config.prefix + config.restPrefix + '/' + controllerId; + if (config.pluralize) { baseRoute = pluralize(baseRoute); } @@ -271,7 +299,13 @@ module.exports = function(sails) { var _getAction = _.partial(_getMiddlewareForShadowRoute, controllerId); // Returns a customized version of the route template as a string. - var _getRoute = _.partialRight(util.format,baseRoute); + var _getRoute = _.partialRight(util.format, baseRoute); + + var _getRestRoute = _getRoute; + if (config.restPrefix) { + // Returns a customized version of the route template as a string for REST + _getRestRoute = _.partialRight(util.format, baseRestRoute); + } // Mix in the known associations for this model to the route options. @@ -314,18 +348,18 @@ module.exports = function(sails) { if ( config.rest ) { sails.log.silly('Binding RESTful blueprint/shadow routes for model+controller:',controllerId); - _bindRoute(_getRoute('get %s'), 'find'); - _bindRoute(_getRoute('get %s/:id'), 'findOne'); - _bindRoute(_getRoute('post %s'), 'create'); - _bindRoute(_getRoute('put %s/:id'), 'update'); - _bindRoute(_getRoute('post %s/:id'), 'update'); - _bindRoute(_getRoute('delete %s/:id?'), 'destroy'); + _bindRoute(_getRestRoute('get %s'), 'find'); + _bindRoute(_getRestRoute('get %s/:id'), 'findOne'); + _bindRoute(_getRestRoute('post %s'), 'create'); + _bindRoute(_getRestRoute('put %s/:id'), 'update'); + _bindRoute(_getRestRoute('post %s/:id'), 'update'); + _bindRoute(_getRestRoute('delete %s/:id?'), 'destroy'); // Bind "rest" blueprint/shadow routes based on known associations in our model's schema // Bind add/remove for each `collection` associations _(Model.associations).where({type: 'collection'}).forEach(function (association) { var alias = association.alias; - var _getAssocRoute = _.partialRight(util.format, baseRoute, alias); + var _getAssocRoute = _.partialRight(util.format, baseRestRoute, alias); var opts = _.merge({ alias: alias }, routeOpts); sails.log.silly('Binding RESTful association blueprint `'+alias+'` for',controllerId); @@ -336,7 +370,7 @@ module.exports = function(sails) { // and populate for both `collection` and `model` associations _(Model.associations).forEach(function (association) { var alias = association.alias; - var _getAssocRoute = _.partialRight(util.format, baseRoute, alias); + var _getAssocRoute = _.partialRight(util.format, baseRestRoute, alias); var opts = _.merge({ alias: alias }, routeOpts); sails.log.silly('Binding RESTful association blueprint `'+alias+'` for',controllerId); diff --git a/test/integration/router.APIScaffold.test.js b/test/integration/router.APIScaffold.test.js index 6211c6257b..2132bc5426 100644 --- a/test/integration/router.APIScaffold.test.js +++ b/test/integration/router.APIScaffold.test.js @@ -198,7 +198,7 @@ describe('router :: ', function() { }); }); - describe('with `prefix` option set', function() { + describe('with `prefix` option set :: ', function() { before(function() { httpHelper.writeBlueprint({ @@ -244,6 +244,102 @@ describe('router :: ', function() { }); }); + + describe('with `restPrefix` option set :: ', function() { + + before(function() { + httpHelper.writeBlueprint({ + restPrefix: '/api' + }); + }); + + it('API should be accessible without restPrefix ', function(done) { + + httpHelper.testRoute('get', { + url: 'empty/create', + json: true + }, function (err, response, body) { + if (err) return done(new Error(err)); + + assert(response.statusCode === 201); + done(); + }); + }); + + + it('API should not be accessible with restPrefix ', function(done) { + httpHelper.testRoute('get', { + url: 'api/empty/create', + json: true + }, function (err, response, body) { + if (err) return done(new Error(err)); + + assert(response.statusCode === 404); + done(); + }); + }); + + it('REST actions should be accessible only with `restPrefix` set ', function(done) { + httpHelper.testRoute('get', { + url: 'api/empty', + json: true + }, function (err, response, body) { + if (err) return done(new Error(err)); + + assert(response.body instanceof Array); + done(); + }); + }); + + it('REST GET action could not be accessible without `restPrefix` ', function(done) { + httpHelper.testRoute('get', { + url: 'empty', + json: true + }, function (err, response, body) { + if (err) return done(new Error(err)); + + assert(response.statusCode === 404); + done(); + }); + }); + + }); + + describe('`prefix` and `restPrefix` config options set together :: ', function() { + + before(function() { + httpHelper.writeBlueprint({ + prefix: '/api', + restPrefix: '/rest' + }); + }); + + it('API should not be accessible with `restPrefix` only with `prefix` ', function(done) { + httpHelper.testRoute('get', { + url: 'api/empty/create', + json: true + }, function (err, response, body) { + if (err) return done(new Error(err)); + + assert(response.statusCode === 201); + done(); + }); + }); + + it('REST should be accessible via `prefix` + `restPrefix`', function(done) { + httpHelper.testRoute('get', { + url: 'api/rest/empty', + json: true + }, function (err, response, body) { + if (err) return done(new Error(err)); + + assert(response.body instanceof Array); + done(); + }); + }); + + }); + }); }); From 948970808de5da6fe4a54a38baa248269d30fd13 Mon Sep 17 00:00:00 2001 From: Scott Gress Date: Thu, 18 Dec 2014 14:41:20 -0600 Subject: [PATCH 125/235] Added "sails-hook-sockets" as a dep --- package.json | 63 ++++++++++++++++++++++++++-------------------------- 1 file changed, 32 insertions(+), 31 deletions(-) diff --git a/package.json b/package.json index 362557ea3c..093556f721 100644 --- a/package.json +++ b/package.json @@ -29,54 +29,55 @@ "lib": "lib" }, "dependencies": { - "waterline": "~0.10.11", - "express": "3.16.0", - "method-override": "~2.3.0", - "cookie": "0.1.2", - "cookie-signature": "1.0.4", - "rc": "~0.5.0", - "sails-stringfile": "~0.3.0", + "anchor": "~0.10.0", "async": "~0.2.9", - "lodash": "~2.4.1", "captains-log": "~0.11.8", - "reportback": "~0.1.4", - "sails-util": "~0.10.3", "colors": "~0.6.2", + "commander": "~2.1.0", "connect-flash": "~0.1.1", - "pluralize": "~0.0.5", - "node-uuid": "~1.4.0", - "ejs-locals": "~1.0.2", - "glob": "~3.2.9", - "i18n": "~0.5.0", - "sails-generate": "~0.12.0", - "sails-build-dictionary": "~0.10.1", - "grunt-cli": "~0.1.11", + "cookie": "0.1.2", + "cookie-signature": "1.0.4", "ejs": "~0.8.4", + "ejs-locals": "~1.0.2", + "express": "3.16.0", + "express-handlebars": "~1.0.1", "fs-extra": "~0.8.1", - "sails-disk": "~0.10.0", - "commander": "~2.1.0", + "glob": "~3.2.9", "grunt": "0.4.2", - "grunt-contrib-copy": "~0.5.0", + "grunt-cli": "~0.1.11", "grunt-contrib-clean": "~0.5.0", + "grunt-contrib-coffee": "~0.10.1", "grunt-contrib-concat": "~0.3.0", - "grunt-sails-linker": "~0.9.5", - "grunt-contrib-jst": "~0.6.0", - "grunt-contrib-watch": "~0.5.3", - "grunt-contrib-uglify": "~0.4.0", + "grunt-contrib-copy": "~0.5.0", "grunt-contrib-cssmin": "~0.9.0", + "grunt-contrib-jst": "~0.6.0", "grunt-contrib-less": "0.11.1", - "grunt-contrib-coffee": "~0.10.1", + "grunt-contrib-uglify": "~0.4.0", + "grunt-contrib-watch": "~0.5.3", + "grunt-sails-linker": "~0.9.5", "grunt-sync": "~0.0.4", - "express-handlebars": "~1.0.1", + "i18n": "~0.5.0", "include-all": "~0.1.3", - "skipper": "~0.5.3", + "lodash": "~2.4.1", "merge-defaults": "~0.1.0", - "anchor": "~0.10.0", + "method-override": "~2.3.0", "mock-req": "0.1.0", "mock-res": "0.1.0", - "semver": "~2.2.1", + "node-uuid": "~1.4.0", + "pluralize": "~0.0.5", "prompt": "~0.2.13", - "uid-safe": "^1.0.1" + "rc": "~0.5.0", + "reportback": "~0.1.4", + "sails-build-dictionary": "~0.10.1", + "sails-disk": "~0.10.0", + "sails-generate": "~0.12.0", + "sails-hook-sockets": "^0.11.2", + "sails-stringfile": "~0.3.0", + "sails-util": "~0.10.3", + "semver": "~2.2.1", + "skipper": "~0.5.3", + "uid-safe": "^1.0.1", + "waterline": "~0.10.11" }, "devDependencies": { "root-require": "~0.2.0", From 7c339ba7e9f4e7531e864a454c12877855af99fa Mon Sep 17 00:00:00 2001 From: Scott Gress Date: Thu, 18 Dec 2014 15:12:10 -0600 Subject: [PATCH 126/235] Turned off tests checking for conflicting hooks --- test/integration/hook.3rdparty.test.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/integration/hook.3rdparty.test.js b/test/integration/hook.3rdparty.test.js index 75beb2a1bb..958c63ac89 100644 --- a/test/integration/hook.3rdparty.test.js +++ b/test/integration/hook.3rdparty.test.js @@ -13,7 +13,7 @@ describe('hooks :: ', function() { var sailsprocess; - describe('installing a 3rd-party hook', function() { + describe.only('installing a 3rd-party hook', function() { var appName = 'testApp'; before(function() { @@ -162,7 +162,7 @@ describe('hooks :: ', function() { }); - describe('setting the hook name to `views` (an existing hook)', function(){ + xdescribe('setting the hook name to `views` (an existing hook)', function(){ it ('should throw an error', function(done) { appHelper.liftQuiet({installedHooks: {'sails-hook-shout': {name: 'views'}}}, function(err, _sails) { @@ -232,7 +232,7 @@ describe('hooks :: ', function() { }); - describe('into node_modules/sails-hook-views', function(){ + xdescribe('into node_modules/sails-hook-views', function(){ before(function(done) { this.timeout(5000); @@ -259,7 +259,7 @@ describe('hooks :: ', function() { }); - describe('into node_modules/views', function(){ + xdescribe('into node_modules/views', function(){ before(function(done) { this.timeout(5000); From ba784225027ff228a6a08a1e8a9b44afbe31f29b Mon Sep 17 00:00:00 2001 From: Scott Gress Date: Thu, 18 Dec 2014 23:51:04 -0600 Subject: [PATCH 127/235] Use parseSocket and remove backwards compat for 0.9 clients (stopped working) --- lib/hooks/pubsub/index.js | 73 +++++++++++++++++++-------------------- 1 file changed, 36 insertions(+), 37 deletions(-) diff --git a/lib/hooks/pubsub/index.js b/lib/hooks/pubsub/index.js index 6ff9f77dc6..a7faf62e19 100644 --- a/lib/hooks/pubsub/index.js +++ b/lib/hooks/pubsub/index.js @@ -390,9 +390,9 @@ module.exports = function(sails) { */ subscribe: function (req, records, contexts) { // If a request object was sent, get its socket, otherwise assume a socket was sent. - var socket = req.socket ? req.socket : req; + var socket = sails.sockets.parseSocket(req); - if (!socket.manager) { + if (!socket) { return sails.log.warn('`Model.subscribe()` called by a non-socket request. Only requests originating from a connected socket may be subscribed. Ignoring...'); } @@ -427,18 +427,17 @@ module.exports = function(sails) { // If the subscribing socket is using the legacy (v0.9.x) socket SDK (sails.io.js), // always subscribe the client to the `legacy_v0.9` context. - if (sails.config.sockets['backwardsCompatibilityFor0.9SocketClients'] && socket.handshake) { - var sdk = app.getSDKMetadata(socket.handshake); - var isLegacySocketClient = sdk.version === '0.9.0'; - if (isLegacySocketClient) { - contexts.push('legacy_v0.9'); - } - } + // if (sails.config.sockets['backwardsCompatibilityFor0.9SocketClients'] && socket.handshake) { + // var sdk = app.getSDKMetadata(socket.handshake); + // var isLegacySocketClient = sdk.version === '0.9.0'; + // if (isLegacySocketClient) { + // contexts.push('legacy_v0.9'); + // } + // } // Subscribe to model instances records = self.pluralize(records); var ids = _.pluck(records, this.primaryKey); - _.each(ids,function (id) { _.each(contexts, function(context) { sails.log.silly( @@ -461,9 +460,9 @@ module.exports = function(sails) { unsubscribe: function (req, records, contexts) { // If a request object was sent, get its socket, otherwise assume a socket was sent. - var socket = req.socket ? req.socket : req; + var socket = sails.sockets.parseSocket(req); - if (!socket.manager) { + if (!socket) { return sails.log.warn('`Model.unsubscribe()` called by a non-socket request. Only requests originating from a connected socket may be subscribed. Ignoring...'); } @@ -487,13 +486,13 @@ module.exports = function(sails) { contexts = this.getAllContexts(); } - if (sails.config.sockets['backwardsCompatibilityFor0.9SocketClients'] && socket.handshake) { - var sdk = app.getSDKMetadata(socket.handshake); - var isLegacySocketClient = sdk.version === '0.9.0'; - if (isLegacySocketClient) { - contexts.push('legacy_v0.9'); - } - } + // if (sails.config.sockets['backwardsCompatibilityFor0.9SocketClients'] && socket.handshake) { + // var sdk = app.getSDKMetadata(socket.handshake); + // var isLegacySocketClient = sdk.version === '0.9.0'; + // if (isLegacySocketClient) { + // contexts.push('legacy_v0.9'); + // } + // } records = self.pluralize(records); var ids = _.pluck(records, this.primaryKey); @@ -1150,22 +1149,22 @@ module.exports = function(sails) { */ watch: function ( req ) { - var socket = req.socket ? req.socket : req; + var socket = sails.sockets.parseSocket(req); - if (!socket.manager) { + if (!socket) { return sails.log.warn('`Model.watch()` called by a non-socket request. Only requests originating from a connected socket may be subscribed. Ignoring...'); } sails.sockets.join(socket, this._classRoom()); sails.log.silly("Subscribed socket ", sails.sockets.id(socket), "to", this._classRoom()); - if (sails.config.sockets['backwardsCompatibilityFor0.9SocketClients'] && socket.handshake) { - var sdk = app.getSDKMetadata(socket.handshake); - var isLegacySocketClient = sdk.version === '0.9.0'; - if (isLegacySocketClient) { - sails.sockets.join(socket, this._classRoom()+':legacy_v0.9'); - } - } + // if (sails.config.sockets['backwardsCompatibilityFor0.9SocketClients'] && socket.handshake) { + // var sdk = app.getSDKMetadata(socket.handshake); + // var isLegacySocketClient = sdk.version === '0.9.0'; + // if (isLegacySocketClient) { + // sails.sockets.join(socket, this._classRoom()+':legacy_v0.9'); + // } + // } }, @@ -1176,22 +1175,22 @@ module.exports = function(sails) { */ unwatch: function ( req ) { - var socket = req.socket ? req.socket : req; + var socket = sails.sockets.parseSocket(req); - if (!socket.manager) { + if (!socket) { return sails.log.warn('`Model.unwatch()` called by a non-socket request. Only requests originating from a connected socket may be subscribed. Ignoring...'); } sails.sockets.leave(socket, this._classRoom()); sails.log.silly("Unubscribed socket ", sails.sockets.id(socket), "from", this._classRoom()); - if (sails.config.sockets['backwardsCompatibilityFor0.9SocketClients'] && socket.handshake) { - var sdk = app.getSDKMetadata(socket.handshake); - var isLegacySocketClient = sdk.version === '0.9.0'; - if (isLegacySocketClient) { - sails.sockets.leave(socket, this._classRoom()+':legacy_v0.9'); - } - } + // if (sails.config.sockets['backwardsCompatibilityFor0.9SocketClients'] && socket.handshake) { + // var sdk = app.getSDKMetadata(socket.handshake); + // var isLegacySocketClient = sdk.version === '0.9.0'; + // if (isLegacySocketClient) { + // sails.sockets.leave(socket, this._classRoom()+':legacy_v0.9'); + // } + // } }, From b94f1d25e50e97b920566ba3ef71c7af469c6ddb Mon Sep 17 00:00:00 2001 From: Scott Gress Date: Thu, 18 Dec 2014 23:52:11 -0600 Subject: [PATCH 128/235] New method for lifting two sockets --- test/integration/helpers/appHelper.js | 40 +++++++++++++++------------ 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/test/integration/helpers/appHelper.js b/test/integration/helpers/appHelper.js index b1b97d5d86..206ae9a169 100644 --- a/test/integration/helpers/appHelper.js +++ b/test/integration/helpers/appHelper.js @@ -16,13 +16,12 @@ var path = require('path'); var sailsBin = path.resolve('./bin/sails.js'); var spawn = require('child_process').spawn; var Sails = require('../../../lib/app'); +var io = require('sails.io.js')(require('socket.io-client')); // Make existsSync not crash on older versions of Node fs.existsSync = fs.existsSync || path.existsSync; -// Set up `_ioClient` the first time this file is required -var _ioClient = require('sails.io.js')(require('socket.io-client')); // var _ioClient = require('./sails.io')(require('socket.io-client')); @@ -49,15 +48,18 @@ module.exports.build = function( /* [appName], done */ ) { wrench.rmdirSyncRecursive(path.resolve('./', appName)); } - exec(sailsBin + ' new ' + appName, function(err) { - if (err) return done(err); + fs.mkdirSync(path.resolve('./', appName)); + + process.chdir(appName); - var fixtures = wrench.readdirSyncRecursive('./test/integration/fixtures/sampleapp'); + exec(sailsBin + ' new', function(err) { + if (err) return done(err); + var fixtures = wrench.readdirSyncRecursive('../test/integration/fixtures/sampleapp'); if (fixtures.length < 1) return done(); // If fixtures copy them to the test app fixtures.forEach(function(file) { - var filePath = path.resolve('./test/integration/fixtures/sampleapp', file); + var filePath = path.resolve('../test/integration/fixtures/sampleapp', file); // Check if file is a directory var stat = fs.statSync(filePath); @@ -69,11 +71,11 @@ module.exports.build = function( /* [appName], done */ ) { var data = fs.readFileSync(filePath); // Create file and any missing parent directories in its path - fs.createFileSync(path.resolve('./', appName, file), data); - fs.writeFileSync(path.resolve('./', appName, file), data); + fs.createFileSync(path.resolve(file), data); + fs.writeFileSync(path.resolve(file), data); }); - process.chdir(appName); + // process.chdir(appName); return done(); }); }; @@ -151,16 +153,18 @@ module.exports.liftWithTwoSockets = function(options, callback) { } module.exports.lift(options, function(err, sails) { if (err) {return callback(err);} - // console.log('trying to connect socket1'); - var socket1 = _ioClient.connect('http://localhost:1342',{'force new connection': true}); + + var socket1 = io.sails.connect('http://localhost:1342', { + multiplex: false, + }); socket1.on('connect', function() { - // console.log('socket1 connected'); - // console.log('trying to connect socket2'); - var socket2 = _ioClient.connect('http://localhost:1342',{'force new connection': true}); - socket2.on('connect', function() { - callback(null, sails, socket1, socket2); - }); - }); + var socket2 = io.sails.connect('http://localhost:1342', { + multiplex: false, + }); + socket2.on('connect', function() { + return callback(null, sails, socket1, socket2); + }); + }); }); }; From dc8c1bcc83cd2739e26c979f008a56dbe9ba52bb Mon Sep 17 00:00:00 2001 From: Scott Gress Date: Thu, 18 Dec 2014 23:52:36 -0600 Subject: [PATCH 129/235] Clean up "before" and "after" blocks --- .../hook.pubsub.modelEvents.context.test.js | 74 ++++++++++--------- 1 file changed, 39 insertions(+), 35 deletions(-) diff --git a/test/integration/hook.pubsub.modelEvents.context.test.js b/test/integration/hook.pubsub.modelEvents.context.test.js index dc40164671..f24ad15afa 100644 --- a/test/integration/hook.pubsub.modelEvents.context.test.js +++ b/test/integration/hook.pubsub.modelEvents.context.test.js @@ -4,8 +4,10 @@ var assert = require('assert'); var socketHelper = require('./helpers/socketHelper.js'); var appHelper = require('./helpers/appHelper'); +var httpHelper = require('./helpers/httpHelper'); var util = require('util'); - +var io = require('sails.io.js')(require('socket.io-client')); +io.sails.environment = "production"; /** * Errors */ @@ -25,48 +27,52 @@ describe('pubsub :: ', function() { describe('Model events (i.e. not the firehose)', function() { - - describe('when a model no default autosubscribe contexts ', function() { - - before(function(done) { - this.timeout(5000); - appHelper.build(appName, function(err) { + before(function(done) { + this.timeout(5000); + appHelper.build(appName, function(err) { + if (err) { + throw new Error(err); + } + socketHelper.writeModelConfig(); + appHelper.liftWithTwoSockets({ + silly: false + }, function(err, sails, _socket1, _socket2) { if (err) { throw new Error(err); } - socketHelper.writeModelConfig(); - appHelper.liftWithTwoSockets({ - verbose: false - }, function(err, sails, _socket1, _socket2) { - if (err) { - throw new Error(err); - } - sailsprocess = sails; - - socket1 = _socket1; - socket2 = _socket2; - - socket2.get('/user/create?name=joe', function() { - socket2.get('/user/create?name=abby', function() { - done(); - }); + sailsprocess = sails; + socket1 = _socket1; + socket2 = _socket2; + + httpHelper.testRoute('get', 'user/create?name=joe', function(err) { + if (err) {return done(err);} + httpHelper.testRoute('get', 'user/create?name=abby', function(err) { + if (err) {return done(err);} + done(); }); }); + }); }); + }); + + after(function() { - after(function() { + if (sailsprocess) { + sailsprocess.kill(); + } + // console.log('before `chdir ../`' + ', cwd was :: ' + process.cwd()); + process.chdir('../'); + // console.log('after `chdir ../`' + ', cwd was :: ' + process.cwd()); + appHelper.teardown(); + }); + + describe('when a model no default autosubscribe contexts ', function() { + after(function(done) { socket1.disconnect(); socket2.disconnect(); - - if (sailsprocess) { - sailsprocess.kill(); - } - // console.log('before `chdir ../`' + ', cwd was :: ' + process.cwd()); - process.chdir('../'); - // console.log('after `chdir ../`' + ', cwd was :: ' + process.cwd()); - appHelper.teardown(); + done(); }); afterEach(function(done) { @@ -90,9 +96,7 @@ describe('pubsub :: ', function() { describe('after subscribing to the update context', function() { before(function(done) { - socket2.get('/user/subscribe?id=1&context=update', function() { - done(); - }); + socket2.get('/user/subscribe?id=1&context=update', done); }); it('updating an instance via put should result in the correct socket messages being received', function(done) { var TIME_TO_WAIT = 1500; From b0558b03944b9c0a27726c3fa0544bf002e723bc Mon Sep 17 00:00:00 2001 From: Scott Gress Date: Thu, 18 Dec 2014 23:52:48 -0600 Subject: [PATCH 130/235] Workaround for issue with sails-generate and sails.io.js --- test/integration/new.test.js | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/test/integration/new.test.js b/test/integration/new.test.js index 65a39199ba..fd49197ad1 100644 --- a/test/integration/new.test.js +++ b/test/integration/new.test.js @@ -46,7 +46,7 @@ describe('New app generator', function() { it('should create new, liftable app in new folder', function(done) { exec(sailsbin + ' new ' + appName, function(err) { if (err) { return done(new Error(err)); } - appHelper.lift({log:{level:'silent'}}, function(err, sailsprocess) { + appHelper.lift({log:{level:'silent'}}, function(err, sailsprocess) { if (err) {return done(err);} sailsprocess.once('hook:http:listening', function(){sailsprocess.kill(done);}); // sailsprocess.kill(done); @@ -71,8 +71,20 @@ describe('New app generator', function() { describe('sails generate new ', function() { + // Workaround for issue where sails-generate tries to load + // sails/node_modules/sails.io.js as the sails.io.js generator + before(function() { + fs.mkdirSync("_test"); + process.chdir("_test"); + }); + + after(function() { + process.chdir(".."); + wrench.rmdirSyncRecursive("_test"); + }); + it('should create new app', function(done) { - exec(sailsbin + ' generate new ' + appName, function(err) { + exec("../" + sailsbin + ' generate new ' + appName, function(err) { if (err) { return done(new Error(err)); } appHelper.lift({log:{level:'silent'}}, function(err, sailsprocess) { if (err) {return done(err);} @@ -86,7 +98,7 @@ describe('New app generator', function() { if (err) { return done(new Error(err)); } exec('touch '+appName+'/test', function(err) { if (err) { return done(new Error(err)); } - exec(sailsbin + ' generate new ' + appName, function(err, dumb, result) { + exec("../" + sailsbin + ' generate new ' + appName, function(err, dumb, result) { assert.notEqual(result.indexOf('error'), -1); done(); }); From b059ded55fbdfd0d11a92bb1b77c9206cf248a20 Mon Sep 17 00:00:00 2001 From: Scott Gress Date: Thu, 18 Dec 2014 23:52:55 -0600 Subject: [PATCH 131/235] Reactivate all tests --- test/integration/hook.3rdparty.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/hook.3rdparty.test.js b/test/integration/hook.3rdparty.test.js index 958c63ac89..a8f7fc9040 100644 --- a/test/integration/hook.3rdparty.test.js +++ b/test/integration/hook.3rdparty.test.js @@ -13,7 +13,7 @@ describe('hooks :: ', function() { var sailsprocess; - describe.only('installing a 3rd-party hook', function() { + describe('installing a 3rd-party hook', function() { var appName = 'testApp'; before(function() { From e277fac41076c976f63c805de77fe008fd9ec0eb Mon Sep 17 00:00:00 2001 From: Konstantin Zolotarev Date: Fri, 19 Dec 2014 14:06:27 +0600 Subject: [PATCH 132/235] Normalise rest path prefix. --- lib/hooks/blueprints/index.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/hooks/blueprints/index.js b/lib/hooks/blueprints/index.js index a5107261bd..a48141c944 100644 --- a/lib/hooks/blueprints/index.js +++ b/lib/hooks/blueprints/index.js @@ -4,6 +4,7 @@ var _ = require('lodash') , util = require('util') + , nodepath = require('path') , async = require('async') , pluralize = require('pluralize') , BlueprintController = { @@ -203,7 +204,7 @@ module.exports = function(sails) { } } - // Validate blueprint config for this controller + // Validate REST route blueprint config for this controller if ( config.restPrefix ) { if ( !_(config.restPrefix).isString() ) { sails.after('lifted', function () { @@ -237,12 +238,12 @@ module.exports = function(sails) { // Determine base route var baseRoute = config.prefix + '/' + controllerId; - - //@todo check if both prefixes are / / - var baseRestRoute = config.prefix + config.restPrefix + '/' + controllerId; + // Determine base route for RESTful service + var baseRestRoute = nodepath.normalize(config.prefix + config.restPrefix + '/' + controllerId); if (config.pluralize) { baseRoute = pluralize(baseRoute); + baseRestRoute = pluralize(baseRestRoute); } // Build route options for blueprint From 9f6a7daa6671561392d219e44f45815c75921745 Mon Sep 17 00:00:00 2001 From: Scott Gress Date: Fri, 19 Dec 2014 09:59:57 -0600 Subject: [PATCH 133/235] Removed socket.io.js dev dependency, added file directly This should put those pesky sails-generate issues in the tests to bed. --- package.json | 3 +- test/integration/helpers/appHelper.js | 3 +- test/integration/helpers/sails.io.js | 4378 +++-------------- .../hook.pubsub.modelEvents.context.test.js | 2 - 4 files changed, 648 insertions(+), 3738 deletions(-) diff --git a/package.json b/package.json index 093556f721..bec71788e2 100644 --- a/package.json +++ b/package.json @@ -94,8 +94,7 @@ "mocha": "~1.17.1", "portfinder": "~0.2.1", "coffee-script": "~1.7.1", - "socket.io-client": "^1.2.1", - "sails.io.js": "^0.11.0" + "socket.io-client": "^1.2.1" }, "repository": { "type": "git", diff --git a/test/integration/helpers/appHelper.js b/test/integration/helpers/appHelper.js index 206ae9a169..016a467247 100644 --- a/test/integration/helpers/appHelper.js +++ b/test/integration/helpers/appHelper.js @@ -16,7 +16,8 @@ var path = require('path'); var sailsBin = path.resolve('./bin/sails.js'); var spawn = require('child_process').spawn; var Sails = require('../../../lib/app'); -var io = require('sails.io.js')(require('socket.io-client')); +var io = require('./sails.io.js')(require('socket.io-client')); +io.sails.environment = "production"; // Make existsSync not crash on older versions of Node fs.existsSync = fs.existsSync || path.existsSync; diff --git a/test/integration/helpers/sails.io.js b/test/integration/helpers/sails.io.js index c672d02238..47f30b280a 100644 --- a/test/integration/helpers/sails.io.js +++ b/test/integration/helpers/sails.io.js @@ -1,3303 +1,3 @@ -/** - * Version 0.10.x of the sails.io.js JavaScript SDK. - * Here to test backwards compatibility. - */ - - - -/*! Socket.IO.js build:0.9.16, development. Copyright(c) 2011 LearnBoost MIT Licensed */ - -var io = ('undefined' === typeof module ? {} : module.exports); -(function() { - -/** - * socket.io - * Copyright(c) 2011 LearnBoost - * MIT Licensed - */ - -(function (exports, global) { - - /** - * IO namespace. - * - * @namespace - */ - - var io = exports; - - /** - * Socket.IO version - * - * @api public - */ - - io.version = '0.9.16'; - - /** - * Protocol implemented. - * - * @api public - */ - - io.protocol = 1; - - /** - * Available transports, these will be populated with the available transports - * - * @api public - */ - - io.transports = []; - - /** - * Keep track of jsonp callbacks. - * - * @api private - */ - - io.j = []; - - /** - * Keep track of our io.Sockets - * - * @api private - */ - io.sockets = {}; - - - /** - * Manages connections to hosts. - * - * @param {String} uri - * @Param {Boolean} force creation of new socket (defaults to false) - * @api public - */ - - io.connect = function (host, details) {}; - -})('object' === typeof module ? module.exports : (this.io = {}), this); -/** - * socket.io - * Copyright(c) 2011 LearnBoost - * MIT Licensed - */ - -(function (exports, global) { - - /** - * Utilities namespace. - * - * @namespace - */ - - var util = exports.util = {}; - - /** - * Parses an URI - * - * @author Steven Levithan (MIT license) - * @api public - */ - - var re = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/; - - var parts = ['source', 'protocol', 'authority', 'userInfo', 'user', 'password', - 'host', 'port', 'relative', 'path', 'directory', 'file', 'query', - 'anchor']; - - util.parseUri = function (str) { - var m = re.exec(str || '') - , uri = {} - , i = 14; - - while (i--) { - uri[parts[i]] = m[i] || ''; - } - - return uri; - }; - - /** - * Produces a unique url that identifies a Socket.IO connection. - * - * @param {Object} uri - * @api public - */ - - util.uniqueUri = function (uri) { - var protocol = uri.protocol - , host = uri.host - , port = uri.port; - - if ('document' in global) { - host = host || document.domain; - port = port || (protocol == 'https' - && document.location.protocol !== 'https:' ? 443 : document.location.port); - } else { - host = host || 'localhost'; - - if (!port && protocol == 'https') { - port = 443; - } - } - - return (protocol || 'http') + '://' + host + ':' + (port || 80); - }; - - /** - * Mergest 2 query strings in to once unique query string - * - * @param {String} base - * @param {String} addition - * @api public - */ - - util.query = function (base, addition) { - var query = util.chunkQuery(base || '') - , components = []; - - util.merge(query, util.chunkQuery(addition || '')); - for (var part in query) { - if (query.hasOwnProperty(part)) { - components.push(part + '=' + query[part]); - } - } - - return components.length ? '?' + components.join('&') : ''; - }; - - /** - * Transforms a querystring in to an object - * - * @param {String} qs - * @api public - */ - - util.chunkQuery = function (qs) { - var query = {} - , params = qs.split('&') - , i = 0 - , l = params.length - , kv; - - for (; i < l; ++i) { - kv = params[i].split('='); - if (kv[0]) { - query[kv[0]] = kv[1]; - } - } - - return query; - }; - - /** - * Executes the given function when the page is loaded. - * - * io.util.load(function () { console.log('page loaded'); }); - * - * @param {Function} fn - * @api public - */ - - var pageLoaded = false; - - util.load = function (fn) { - if ('document' in global && document.readyState === 'complete' || pageLoaded) { - return fn(); - } - - util.on(global, 'load', fn, false); - }; - - /** - * Adds an event. - * - * @api private - */ - - util.on = function (element, event, fn, capture) { - if (element.attachEvent) { - element.attachEvent('on' + event, fn); - } else if (element.addEventListener) { - element.addEventListener(event, fn, capture); - } - }; - - /** - * Generates the correct `XMLHttpRequest` for regular and cross domain requests. - * - * @param {Boolean} [xdomain] Create a request that can be used cross domain. - * @returns {XMLHttpRequest|false} If we can create a XMLHttpRequest. - * @api private - */ - - util.request = function (xdomain) { - console.log("HERE"); - if (xdomain && 'undefined' != typeof XDomainRequest && !util.ua.hasCORS) { - return new XDomainRequest(); - } - - if ('undefined' != typeof XMLHttpRequest && (!xdomain || util.ua.hasCORS)) { - return new XMLHttpRequest(); - } - - if (!xdomain) { - try { - return new window[(['Active'].concat('Object').join('X'))]('Microsoft.XMLHTTP'); - } catch(e) { } - } - - return null; - }; - - /** - * XHR based transport constructor. - * - * @constructor - * @api public - */ - - /** - * Change the internal pageLoaded value. - */ - - if ('undefined' != typeof window) { - util.load(function () { - pageLoaded = true; - }); - } - - /** - * Defers a function to ensure a spinner is not displayed by the browser - * - * @param {Function} fn - * @api public - */ - - util.defer = function (fn) { - if (!util.ua.webkit || 'undefined' != typeof importScripts) { - return fn(); - } - - util.load(function () { - setTimeout(fn, 100); - }); - }; - - /** - * Merges two objects. - * - * @api public - */ - - util.merge = function merge (target, additional, deep, lastseen) { - var seen = lastseen || [] - , depth = typeof deep == 'undefined' ? 2 : deep - , prop; - - for (prop in additional) { - if (additional.hasOwnProperty(prop) && util.indexOf(seen, prop) < 0) { - if (typeof target[prop] !== 'object' || !depth) { - target[prop] = additional[prop]; - seen.push(additional[prop]); - } else { - util.merge(target[prop], additional[prop], depth - 1, seen); - } - } - } - - return target; - }; - - /** - * Merges prototypes from objects - * - * @api public - */ - - util.mixin = function (ctor, ctor2) { - util.merge(ctor.prototype, ctor2.prototype); - }; - - /** - * Shortcut for prototypical and static inheritance. - * - * @api private - */ - - util.inherit = function (ctor, ctor2) { - function f() {}; - f.prototype = ctor2.prototype; - ctor.prototype = new f; - }; - - /** - * Checks if the given object is an Array. - * - * io.util.isArray([]); // true - * io.util.isArray({}); // false - * - * @param Object obj - * @api public - */ - - util.isArray = Array.isArray || function (obj) { - return Object.prototype.toString.call(obj) === '[object Array]'; - }; - - /** - * Intersects values of two arrays into a third - * - * @api public - */ - - util.intersect = function (arr, arr2) { - var ret = [] - , longest = arr.length > arr2.length ? arr : arr2 - , shortest = arr.length > arr2.length ? arr2 : arr; - - for (var i = 0, l = shortest.length; i < l; i++) { - if (~util.indexOf(longest, shortest[i])) - ret.push(shortest[i]); - } - - return ret; - }; - - /** - * Array indexOf compatibility. - * - * @see bit.ly/a5Dxa2 - * @api public - */ - - util.indexOf = function (arr, o, i) { - - for (var j = arr.length, i = i < 0 ? i + j < 0 ? 0 : i + j : i || 0; - i < j && arr[i] !== o; i++) {} - - return j <= i ? -1 : i; - }; - - /** - * Converts enumerables to array. - * - * @api public - */ - - util.toArray = function (enu) { - var arr = []; - - for (var i = 0, l = enu.length; i < l; i++) - arr.push(enu[i]); - - return arr; - }; - - /** - * UA / engines detection namespace. - * - * @namespace - */ - - util.ua = {}; - - /** - * Whether the UA supports CORS for XHR. - * - * @api public - */ - - util.ua.hasCORS = 'undefined' != typeof XMLHttpRequest && (function () { - try { - var a = new XMLHttpRequest(); - } catch (e) { - return false; - } - - return a.withCredentials != undefined; - })(); - - /** - * Detect webkit. - * - * @api public - */ - - util.ua.webkit = 'undefined' != typeof navigator - && /webkit/i.test(navigator.userAgent); - - /** - * Detect iPad/iPhone/iPod. - * - * @api public - */ - - util.ua.iDevice = 'undefined' != typeof navigator - && /iPad|iPhone|iPod/i.test(navigator.userAgent); - -})('undefined' != typeof io ? io : module.exports, this); -/** - * socket.io - * Copyright(c) 2011 LearnBoost - * MIT Licensed - */ - -(function (exports, io) { - - /** - * Expose constructor. - */ - - exports.EventEmitter = EventEmitter; - - /** - * Event emitter constructor. - * - * @api public. - */ - - function EventEmitter () {}; - - /** - * Adds a listener - * - * @api public - */ - - EventEmitter.prototype.on = function (name, fn) { - if (!this.$events) { - this.$events = {}; - } - - if (!this.$events[name]) { - this.$events[name] = fn; - } else if (io.util.isArray(this.$events[name])) { - this.$events[name].push(fn); - } else { - this.$events[name] = [this.$events[name], fn]; - } - - return this; - }; - - EventEmitter.prototype.addListener = EventEmitter.prototype.on; - - /** - * Adds a volatile listener. - * - * @api public - */ - - EventEmitter.prototype.once = function (name, fn) { - var self = this; - - function on () { - self.removeListener(name, on); - fn.apply(this, arguments); - }; - - on.listener = fn; - this.on(name, on); - - return this; - }; - - /** - * Removes a listener. - * - * @api public - */ - - EventEmitter.prototype.removeListener = function (name, fn) { - if (this.$events && this.$events[name]) { - var list = this.$events[name]; - - if (io.util.isArray(list)) { - var pos = -1; - - for (var i = 0, l = list.length; i < l; i++) { - if (list[i] === fn || (list[i].listener && list[i].listener === fn)) { - pos = i; - break; - } - } - - if (pos < 0) { - return this; - } - - list.splice(pos, 1); - - if (!list.length) { - delete this.$events[name]; - } - } else if (list === fn || (list.listener && list.listener === fn)) { - delete this.$events[name]; - } - } - - return this; - }; - - /** - * Removes all listeners for an event. - * - * @api public - */ - - EventEmitter.prototype.removeAllListeners = function (name) { - if (name === undefined) { - this.$events = {}; - return this; - } - - if (this.$events && this.$events[name]) { - this.$events[name] = null; - } - - return this; - }; - - /** - * Gets all listeners for a certain event. - * - * @api publci - */ - - EventEmitter.prototype.listeners = function (name) { - if (!this.$events) { - this.$events = {}; - } - - if (!this.$events[name]) { - this.$events[name] = []; - } - - if (!io.util.isArray(this.$events[name])) { - this.$events[name] = [this.$events[name]]; - } - - return this.$events[name]; - }; - - /** - * Emits an event. - * - * @api public - */ - - EventEmitter.prototype.emit = function (name) { - if (!this.$events) { - return false; - } - - var handler = this.$events[name]; - - if (!handler) { - return false; - } - - var args = Array.prototype.slice.call(arguments, 1); - - if ('function' == typeof handler) { - handler.apply(this, args); - } else if (io.util.isArray(handler)) { - var listeners = handler.slice(); - - for (var i = 0, l = listeners.length; i < l; i++) { - listeners[i].apply(this, args); - } - } else { - return false; - } - - return true; - }; - -})( - 'undefined' != typeof io ? io : module.exports - , 'undefined' != typeof io ? io : module.parent.exports -); - -/** - * socket.io - * Copyright(c) 2011 LearnBoost - * MIT Licensed - */ - -/** - * Based on JSON2 (http://www.JSON.org/js.html). - */ - -(function (exports, nativeJSON) { - "use strict"; - - // use native JSON if it's available - if (nativeJSON && nativeJSON.parse){ - return exports.JSON = { - parse: nativeJSON.parse - , stringify: nativeJSON.stringify - }; - } - - var JSON = exports.JSON = {}; - - function f(n) { - // Format integers to have at least two digits. - return n < 10 ? '0' + n : n; - } - - function date(d, key) { - return isFinite(d.valueOf()) ? - d.getUTCFullYear() + '-' + - f(d.getUTCMonth() + 1) + '-' + - f(d.getUTCDate()) + 'T' + - f(d.getUTCHours()) + ':' + - f(d.getUTCMinutes()) + ':' + - f(d.getUTCSeconds()) + 'Z' : null; - }; - - var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, - escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, - gap, - indent, - meta = { // table of character substitutions - '\b': '\\b', - '\t': '\\t', - '\n': '\\n', - '\f': '\\f', - '\r': '\\r', - '"' : '\\"', - '\\': '\\\\' - }, - rep; - - - function quote(string) { - -// If the string contains no control characters, no quote characters, and no -// backslash characters, then we can safely slap some quotes around it. -// Otherwise we must also replace the offending characters with safe escape -// sequences. - - escapable.lastIndex = 0; - return escapable.test(string) ? '"' + string.replace(escapable, function (a) { - var c = meta[a]; - return typeof c === 'string' ? c : - '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); - }) + '"' : '"' + string + '"'; - } - - - function str(key, holder) { - -// Produce a string from holder[key]. - - var i, // The loop counter. - k, // The member key. - v, // The member value. - length, - mind = gap, - partial, - value = holder[key]; - -// If the value has a toJSON method, call it to obtain a replacement value. - - if (value instanceof Date) { - value = date(key); - } - -// If we were called with a replacer function, then call the replacer to -// obtain a replacement value. - - if (typeof rep === 'function') { - value = rep.call(holder, key, value); - } - -// What happens next depends on the value's type. - - switch (typeof value) { - case 'string': - return quote(value); - - case 'number': - -// JSON numbers must be finite. Encode non-finite numbers as null. - - return isFinite(value) ? String(value) : 'null'; - - case 'boolean': - case 'null': - -// If the value is a boolean or null, convert it to a string. Note: -// typeof null does not produce 'null'. The case is included here in -// the remote chance that this gets fixed someday. - - return String(value); - -// If the type is 'object', we might be dealing with an object or an array or -// null. - - case 'object': - -// Due to a specification blunder in ECMAScript, typeof null is 'object', -// so watch out for that case. - - if (!value) { - return 'null'; - } - -// Make an array to hold the partial results of stringifying this object value. - - gap += indent; - partial = []; - -// Is the value an array? - - if (Object.prototype.toString.apply(value) === '[object Array]') { - -// The value is an array. Stringify every element. Use null as a placeholder -// for non-JSON values. - - length = value.length; - for (i = 0; i < length; i += 1) { - partial[i] = str(i, value) || 'null'; - } - -// Join all of the elements together, separated with commas, and wrap them in -// brackets. - - v = partial.length === 0 ? '[]' : gap ? - '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' : - '[' + partial.join(',') + ']'; - gap = mind; - return v; - } - -// If the replacer is an array, use it to select the members to be stringified. - - if (rep && typeof rep === 'object') { - length = rep.length; - for (i = 0; i < length; i += 1) { - if (typeof rep[i] === 'string') { - k = rep[i]; - v = str(k, value); - if (v) { - partial.push(quote(k) + (gap ? ': ' : ':') + v); - } - } - } - } else { - -// Otherwise, iterate through all of the keys in the object. - - for (k in value) { - if (Object.prototype.hasOwnProperty.call(value, k)) { - v = str(k, value); - if (v) { - partial.push(quote(k) + (gap ? ': ' : ':') + v); - } - } - } - } - -// Join all of the member texts together, separated with commas, -// and wrap them in braces. - - v = partial.length === 0 ? '{}' : gap ? - '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' : - '{' + partial.join(',') + '}'; - gap = mind; - return v; - } - } - -// If the JSON object does not yet have a stringify method, give it one. - - JSON.stringify = function (value, replacer, space) { - -// The stringify method takes a value and an optional replacer, and an optional -// space parameter, and returns a JSON text. The replacer can be a function -// that can replace values, or an array of strings that will select the keys. -// A default replacer method can be provided. Use of the space parameter can -// produce text that is more easily readable. - - var i; - gap = ''; - indent = ''; - -// If the space parameter is a number, make an indent string containing that -// many spaces. - - if (typeof space === 'number') { - for (i = 0; i < space; i += 1) { - indent += ' '; - } - -// If the space parameter is a string, it will be used as the indent string. - - } else if (typeof space === 'string') { - indent = space; - } - -// If there is a replacer, it must be a function or an array. -// Otherwise, throw an error. - - rep = replacer; - if (replacer && typeof replacer !== 'function' && - (typeof replacer !== 'object' || - typeof replacer.length !== 'number')) { - throw new Error('JSON.stringify'); - } - -// Make a fake root object containing our value under the key of ''. -// Return the result of stringifying the value. - - return str('', {'': value}); - }; - -// If the JSON object does not yet have a parse method, give it one. - - JSON.parse = function (text, reviver) { - // The parse method takes a text and an optional reviver function, and returns - // a JavaScript value if the text is a valid JSON text. - - var j; - - function walk(holder, key) { - - // The walk method is used to recursively walk the resulting structure so - // that modifications can be made. - - var k, v, value = holder[key]; - if (value && typeof value === 'object') { - for (k in value) { - if (Object.prototype.hasOwnProperty.call(value, k)) { - v = walk(value, k); - if (v !== undefined) { - value[k] = v; - } else { - delete value[k]; - } - } - } - } - return reviver.call(holder, key, value); - } - - - // Parsing happens in four stages. In the first stage, we replace certain - // Unicode characters with escape sequences. JavaScript handles many characters - // incorrectly, either silently deleting them, or treating them as line endings. - - text = String(text); - cx.lastIndex = 0; - if (cx.test(text)) { - text = text.replace(cx, function (a) { - return '\\u' + - ('0000' + a.charCodeAt(0).toString(16)).slice(-4); - }); - } - - // In the second stage, we run the text against regular expressions that look - // for non-JSON patterns. We are especially concerned with '()' and 'new' - // because they can cause invocation, and '=' because it can cause mutation. - // But just to be safe, we want to reject all unexpected forms. - - // We split the second stage into 4 regexp operations in order to work around - // crippling inefficiencies in IE's and Safari's regexp engines. First we - // replace the JSON backslash pairs with '@' (a non-JSON character). Second, we - // replace all simple value tokens with ']' characters. Third, we delete all - // open brackets that follow a colon or comma or that begin the text. Finally, - // we look to see that the remaining characters are only whitespace or ']' or - // ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. - - if (/^[\],:{}\s]*$/ - .test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@') - .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']') - .replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { - - // In the third stage we use the eval function to compile the text into a - // JavaScript structure. The '{' operator is subject to a syntactic ambiguity - // in JavaScript: it can begin a block or an object literal. We wrap the text - // in parens to eliminate the ambiguity. - - j = eval('(' + text + ')'); - - // In the optional fourth stage, we recursively walk the new structure, passing - // each name/value pair to a reviver function for possible transformation. - - return typeof reviver === 'function' ? - walk({'': j}, '') : j; - } - - // If the text is not JSON parseable, then a SyntaxError is thrown. - - throw new SyntaxError('JSON.parse'); - }; - -})( - 'undefined' != typeof io ? io : module.exports - , typeof JSON !== 'undefined' ? JSON : undefined -); - -/** - * socket.io - * Copyright(c) 2011 LearnBoost - * MIT Licensed - */ - -(function (exports, io) { - - /** - * Parser namespace. - * - * @namespace - */ - - var parser = exports.parser = {}; - - /** - * Packet types. - */ - - var packets = parser.packets = [ - 'disconnect' - , 'connect' - , 'heartbeat' - , 'message' - , 'json' - , 'event' - , 'ack' - , 'error' - , 'noop' - ]; - - /** - * Errors reasons. - */ - - var reasons = parser.reasons = [ - 'transport not supported' - , 'client not handshaken' - , 'unauthorized' - ]; - - /** - * Errors advice. - */ - - var advice = parser.advice = [ - 'reconnect' - ]; - - /** - * Shortcuts. - */ - - var JSON = io.JSON - , indexOf = io.util.indexOf; - - /** - * Encodes a packet. - * - * @api private - */ - - parser.encodePacket = function (packet) { - var type = indexOf(packets, packet.type) - , id = packet.id || '' - , endpoint = packet.endpoint || '' - , ack = packet.ack - , data = null; - - switch (packet.type) { - case 'error': - var reason = packet.reason ? indexOf(reasons, packet.reason) : '' - , adv = packet.advice ? indexOf(advice, packet.advice) : ''; - - if (reason !== '' || adv !== '') - data = reason + (adv !== '' ? ('+' + adv) : ''); - - break; - - case 'message': - if (packet.data !== '') - data = packet.data; - break; - - case 'event': - var ev = { name: packet.name }; - - if (packet.args && packet.args.length) { - ev.args = packet.args; - } - - data = JSON.stringify(ev); - break; - - case 'json': - data = JSON.stringify(packet.data); - break; - - case 'connect': - if (packet.qs) - data = packet.qs; - break; - - case 'ack': - data = packet.ackId - + (packet.args && packet.args.length - ? '+' + JSON.stringify(packet.args) : ''); - break; - } - - // construct packet with required fragments - var encoded = [ - type - , id + (ack == 'data' ? '+' : '') - , endpoint - ]; - - // data fragment is optional - if (data !== null && data !== undefined) - encoded.push(data); - - return encoded.join(':'); - }; - - /** - * Encodes multiple messages (payload). - * - * @param {Array} messages - * @api private - */ - - parser.encodePayload = function (packets) { - var decoded = ''; - - if (packets.length == 1) - return packets[0]; - - for (var i = 0, l = packets.length; i < l; i++) { - var packet = packets[i]; - decoded += '\ufffd' + packet.length + '\ufffd' + packets[i]; - } - - return decoded; - }; - - /** - * Decodes a packet - * - * @api private - */ - - var regexp = /([^:]+):([0-9]+)?(\+)?:([^:]+)?:?([\s\S]*)?/; - - parser.decodePacket = function (data) { - var pieces = data.match(regexp); - - if (!pieces) return {}; - - var id = pieces[2] || '' - , data = pieces[5] || '' - , packet = { - type: packets[pieces[1]] - , endpoint: pieces[4] || '' - }; - - // whether we need to acknowledge the packet - if (id) { - packet.id = id; - if (pieces[3]) - packet.ack = 'data'; - else - packet.ack = true; - } - - // handle different packet types - switch (packet.type) { - case 'error': - var pieces = data.split('+'); - packet.reason = reasons[pieces[0]] || ''; - packet.advice = advice[pieces[1]] || ''; - break; - - case 'message': - packet.data = data || ''; - break; - - case 'event': - try { - var opts = JSON.parse(data); - packet.name = opts.name; - packet.args = opts.args; - } catch (e) { } - - packet.args = packet.args || []; - break; - - case 'json': - try { - packet.data = JSON.parse(data); - } catch (e) { } - break; - - case 'connect': - packet.qs = data || ''; - break; - - case 'ack': - var pieces = data.match(/^([0-9]+)(\+)?(.*)/); - if (pieces) { - packet.ackId = pieces[1]; - packet.args = []; - - if (pieces[3]) { - try { - packet.args = pieces[3] ? JSON.parse(pieces[3]) : []; - } catch (e) { } - } - } - break; - - case 'disconnect': - case 'heartbeat': - break; - }; - - return packet; - }; - - /** - * Decodes data payload. Detects multiple messages - * - * @return {Array} messages - * @api public - */ - - parser.decodePayload = function (data) { - // IE doesn't like data[i] for unicode chars, charAt works fine - if (data.charAt(0) == '\ufffd') { - var ret = []; - - for (var i = 1, length = ''; i < data.length; i++) { - if (data.charAt(i) == '\ufffd') { - ret.push(parser.decodePacket(data.substr(i + 1).substr(0, length))); - i += Number(length) + 1; - length = ''; - } else { - length += data.charAt(i); - } - } - - return ret; - } else { - return [parser.decodePacket(data)]; - } - }; - -})( - 'undefined' != typeof io ? io : module.exports - , 'undefined' != typeof io ? io : module.parent.exports -); -/** - * socket.io - * Copyright(c) 2011 LearnBoost - * MIT Licensed - */ - -(function (exports, io) { - - /** - * Expose constructor. - */ - - exports.Transport = Transport; - - /** - * This is the transport template for all supported transport methods. - * - * @constructor - * @api public - */ - - function Transport (socket, sessid) { - this.socket = socket; - this.sessid = sessid; - }; - - /** - * Apply EventEmitter mixin. - */ - - io.util.mixin(Transport, io.EventEmitter); - - - /** - * Indicates whether heartbeats is enabled for this transport - * - * @api private - */ - - Transport.prototype.heartbeats = function () { - return true; - }; - - /** - * Handles the response from the server. When a new response is received - * it will automatically update the timeout, decode the message and - * forwards the response to the onMessage function for further processing. - * - * @param {String} data Response from the server. - * @api private - */ - - Transport.prototype.onData = function (data) { - this.clearCloseTimeout(); - - // If the connection in currently open (or in a reopening state) reset the close - // timeout since we have just received data. This check is necessary so - // that we don't reset the timeout on an explicitly disconnected connection. - if (this.socket.connected || this.socket.connecting || this.socket.reconnecting) { - this.setCloseTimeout(); - } - - if (data !== '') { - // todo: we should only do decodePayload for xhr transports - var msgs = io.parser.decodePayload(data); - - if (msgs && msgs.length) { - for (var i = 0, l = msgs.length; i < l; i++) { - this.onPacket(msgs[i]); - } - } - } - - return this; - }; - - /** - * Handles packets. - * - * @api private - */ - - Transport.prototype.onPacket = function (packet) { - this.socket.setHeartbeatTimeout(); - - if (packet.type == 'heartbeat') { - return this.onHeartbeat(); - } - - if (packet.type == 'connect' && packet.endpoint == '') { - this.onConnect(); - } - - if (packet.type == 'error' && packet.advice == 'reconnect') { - this.isOpen = false; - } - - this.socket.onPacket(packet); - - return this; - }; - - /** - * Sets close timeout - * - * @api private - */ - - Transport.prototype.setCloseTimeout = function () { - if (!this.closeTimeout) { - var self = this; - - this.closeTimeout = setTimeout(function () { - self.onDisconnect(); - }, this.socket.closeTimeout); - } - }; - - /** - * Called when transport disconnects. - * - * @api private - */ - - Transport.prototype.onDisconnect = function () { - if (this.isOpen) this.close(); - this.clearTimeouts(); - this.socket.onDisconnect(); - return this; - }; - - /** - * Called when transport connects - * - * @api private - */ - - Transport.prototype.onConnect = function () { - this.socket.onConnect(); - return this; - }; - - /** - * Clears close timeout - * - * @api private - */ - - Transport.prototype.clearCloseTimeout = function () { - if (this.closeTimeout) { - clearTimeout(this.closeTimeout); - this.closeTimeout = null; - } - }; - - /** - * Clear timeouts - * - * @api private - */ - - Transport.prototype.clearTimeouts = function () { - this.clearCloseTimeout(); - - if (this.reopenTimeout) { - clearTimeout(this.reopenTimeout); - } - }; - - /** - * Sends a packet - * - * @param {Object} packet object. - * @api private - */ - - Transport.prototype.packet = function (packet) { - this.send(io.parser.encodePacket(packet)); - }; - - /** - * Send the received heartbeat message back to server. So the server - * knows we are still connected. - * - * @param {String} heartbeat Heartbeat response from the server. - * @api private - */ - - Transport.prototype.onHeartbeat = function (heartbeat) { - this.packet({ type: 'heartbeat' }); - }; - - /** - * Called when the transport opens. - * - * @api private - */ - - Transport.prototype.onOpen = function () { - this.isOpen = true; - this.clearCloseTimeout(); - this.socket.onOpen(); - }; - - /** - * Notifies the base when the connection with the Socket.IO server - * has been disconnected. - * - * @api private - */ - - Transport.prototype.onClose = function () { - var self = this; - - /* FIXME: reopen delay causing a infinit loop - this.reopenTimeout = setTimeout(function () { - self.open(); - }, this.socket.options['reopen delay']);*/ - - this.isOpen = false; - this.socket.onClose(); - this.onDisconnect(); - }; - - /** - * Generates a connection url based on the Socket.IO URL Protocol. - * See for more details. - * - * @returns {String} Connection url - * @api private - */ - - Transport.prototype.prepareUrl = function () { - var options = this.socket.options; - - return this.scheme() + '://' - + options.host + ':' + options.port + '/' - + options.resource + '/' + io.protocol - + '/' + this.name + '/' + this.sessid; - }; - - /** - * Checks if the transport is ready to start a connection. - * - * @param {Socket} socket The socket instance that needs a transport - * @param {Function} fn The callback - * @api private - */ - - Transport.prototype.ready = function (socket, fn) { - fn.call(this); - }; -})( - 'undefined' != typeof io ? io : module.exports - , 'undefined' != typeof io ? io : module.parent.exports -); -/** - * socket.io - * Copyright(c) 2011 LearnBoost - * MIT Licensed - */ - -(function (exports, io, global) { - - /** - * Expose constructor. - */ - - exports.Socket = Socket; - - /** - * Create a new `Socket.IO client` which can establish a persistent - * connection with a Socket.IO enabled server. - * - * @api public - */ - - function Socket (options) { - console.log("NEW SOCKET"); - this.options = { - port: 80 - , secure: false - , document: 'document' in global ? document : false - , resource: 'socket.io' - , transports: io.transports - , 'connect timeout': 10000 - , 'try multiple transports': true - , 'reconnect': true - , 'reconnection delay': 500 - , 'reconnection limit': Infinity - , 'reopen delay': 3000 - , 'max reconnection attempts': 10 - , 'sync disconnect on unload': false - , 'auto connect': true - , 'flash policy port': 10843 - , 'manualFlush': false - }; - - io.util.merge(this.options, options); - - this.connected = false; - this.open = false; - this.connecting = false; - this.reconnecting = false; - this.namespaces = {}; - this.buffer = []; - this.doBuffer = false; - - if (this.options['sync disconnect on unload'] && - (!this.isXDomain() || io.util.ua.hasCORS)) { - var self = this; - io.util.on(global, 'beforeunload', function () { - self.disconnectSync(); - }, false); - } - - if (this.options['auto connect']) { - this.connect(); - } -}; - - /** - * Apply EventEmitter mixin. - */ - - io.util.mixin(Socket, io.EventEmitter); - - /** - * Returns a namespace listener/emitter for this socket - * - * @api public - */ - - Socket.prototype.of = function (name) { - if (!this.namespaces[name]) { - this.namespaces[name] = new io.SocketNamespace(this, name); - - if (name !== '') { - this.namespaces[name].packet({ type: 'connect' }); - } - } - - return this.namespaces[name]; - }; - - /** - * Emits the given event to the Socket and all namespaces - * - * @api private - */ - - Socket.prototype.publish = function () { - this.emit.apply(this, arguments); - - var nsp; - - for (var i in this.namespaces) { - if (this.namespaces.hasOwnProperty(i)) { - nsp = this.of(i); - nsp.$emit.apply(nsp, arguments); - } - } - }; - - /** - * Performs the handshake - * - * @api private - */ - - function empty () { }; - - Socket.prototype.handshake = function (fn) { - var self = this - , options = this.options; - - function complete (data) { - if (data instanceof Error) { - self.connecting = false; - self.onError(data.message); - } else { - fn.apply(null, data.split(':')); - } - }; - - var url = [ - 'http' + (options.secure ? 's' : '') + ':/' - , options.host + ':' + options.port - , options.resource - , io.protocol - , io.util.query(this.options.query, 't=' + +new Date) - ].join('/'); - - if (this.isXDomain() && !io.util.ua.hasCORS) { - var insertAt = document.getElementsByTagName('script')[0] - , script = document.createElement('script'); - - script.src = url + '&jsonp=' + io.j.length; - insertAt.parentNode.insertBefore(script, insertAt); - - io.j.push(function (data) { - complete(data); - script.parentNode.removeChild(script); - }); - } else { - var xhr = io.util.request(); - - xhr.open('GET', url, true); - if (this.isXDomain()) { - xhr.withCredentials = true; - } - xhr.onreadystatechange = function () { - if (xhr.readyState == 4) { - xhr.onreadystatechange = empty; - - if (xhr.status == 200) { - complete(xhr.responseText); - } else if (xhr.status == 403) { - self.onError(xhr.responseText); - } else { - self.connecting = false; - !self.reconnecting && self.onError(xhr.responseText); - } - } - }; - xhr.send(null); - } - }; - - /** - * Find an available transport based on the options supplied in the constructor. - * - * @api private - */ - - Socket.prototype.getTransport = function (override) { - var transports = override || this.transports, match; - - for (var i = 0, transport; transport = transports[i]; i++) { - if (io.Transport[transport] - && io.Transport[transport].check(this) - && (!this.isXDomain() || io.Transport[transport].xdomainCheck(this))) { - return new io.Transport[transport](this, this.sessionid); - } - } - - return null; - }; - - /** - * Connects to the server. - * - * @param {Function} [fn] Callback. - * @returns {io.Socket} - * @api public - */ - - Socket.prototype.connect = function (fn) { - if (this.connecting) { - return this; - } - - var self = this; - self.connecting = true; - - this.handshake(function (sid, heartbeat, close, transports) { - self.sessionid = sid; - self.closeTimeout = close * 1000; - self.heartbeatTimeout = heartbeat * 1000; - if(!self.transports) - self.transports = self.origTransports = (transports ? io.util.intersect( - transports.split(',') - , self.options.transports - ) : self.options.transports); - - self.setHeartbeatTimeout(); - - function connect (transports){ - if (self.transport) self.transport.clearTimeouts(); - - self.transport = self.getTransport(transports); - if (!self.transport) return self.publish('connect_failed'); - - // once the transport is ready - self.transport.ready(self, function () { - self.connecting = true; - self.publish('connecting', self.transport.name); - self.transport.open(); - - if (self.options['connect timeout']) { - self.connectTimeoutTimer = setTimeout(function () { - if (!self.connected) { - self.connecting = false; - - if (self.options['try multiple transports']) { - var remaining = self.transports; - - while (remaining.length > 0 && remaining.splice(0,1)[0] != - self.transport.name) {} - - if (remaining.length){ - connect(remaining); - } else { - self.publish('connect_failed'); - } - } - } - }, self.options['connect timeout']); - } - }); - } - - connect(self.transports); - - self.once('connect', function (){ - clearTimeout(self.connectTimeoutTimer); - - fn && typeof fn == 'function' && fn(); - }); - }); - - return this; - }; - - /** - * Clears and sets a new heartbeat timeout using the value given by the - * server during the handshake. - * - * @api private - */ - - Socket.prototype.setHeartbeatTimeout = function () { - clearTimeout(this.heartbeatTimeoutTimer); - if(this.transport && !this.transport.heartbeats()) return; - - var self = this; - this.heartbeatTimeoutTimer = setTimeout(function () { - self.transport.onClose(); - }, this.heartbeatTimeout); - }; - - /** - * Sends a message. - * - * @param {Object} data packet. - * @returns {io.Socket} - * @api public - */ - - Socket.prototype.packet = function (data) { - if (this.connected && !this.doBuffer) { - this.transport.packet(data); - } else { - this.buffer.push(data); - } - - return this; - }; - - /** - * Sets buffer state - * - * @api private - */ - - Socket.prototype.setBuffer = function (v) { - this.doBuffer = v; - - if (!v && this.connected && this.buffer.length) { - if (!this.options['manualFlush']) { - this.flushBuffer(); - } - } - }; - - /** - * Flushes the buffer data over the wire. - * To be invoked manually when 'manualFlush' is set to true. - * - * @api public - */ - - Socket.prototype.flushBuffer = function() { - this.transport.payload(this.buffer); - this.buffer = []; - }; - - - /** - * Disconnect the established connect. - * - * @returns {io.Socket} - * @api public - */ - - Socket.prototype.disconnect = function () { - if (this.connected || this.connecting) { - if (this.open) { - this.of('').packet({ type: 'disconnect' }); - } - - // handle disconnection immediately - this.onDisconnect('booted'); - } - - return this; - }; - - /** - * Disconnects the socket with a sync XHR. - * - * @api private - */ - - Socket.prototype.disconnectSync = function () { - // ensure disconnection - var xhr = io.util.request(); - var uri = [ - 'http' + (this.options.secure ? 's' : '') + ':/' - , this.options.host + ':' + this.options.port - , this.options.resource - , io.protocol - , '' - , this.sessionid - ].join('/') + '/?disconnect=1'; - - xhr.open('GET', uri, false); - xhr.send(null); - - // handle disconnection immediately - this.onDisconnect('booted'); - }; - - /** - * Check if we need to use cross domain enabled transports. Cross domain would - * be a different port or different domain name. - * - * @returns {Boolean} - * @api private - */ - - Socket.prototype.isXDomain = function () { - - var port = global.location.port || - ('https:' == global.location.protocol ? 443 : 80); - - return this.options.host !== global.location.hostname - || this.options.port != port; - }; - - /** - * Called upon handshake. - * - * @api private - */ - - Socket.prototype.onConnect = function () { - if (!this.connected) { - this.connected = true; - this.connecting = false; - if (!this.doBuffer) { - // make sure to flush the buffer - this.setBuffer(false); - } - this.emit('connect'); - } - }; - - /** - * Called when the transport opens - * - * @api private - */ - - Socket.prototype.onOpen = function () { - this.open = true; - }; - - /** - * Called when the transport closes. - * - * @api private - */ - - Socket.prototype.onClose = function () { - this.open = false; - clearTimeout(this.heartbeatTimeoutTimer); - }; - - /** - * Called when the transport first opens a connection - * - * @param text - */ - - Socket.prototype.onPacket = function (packet) { - this.of(packet.endpoint).onPacket(packet); - }; - - /** - * Handles an error. - * - * @api private - */ - - Socket.prototype.onError = function (err) { - if (err && err.advice) { - if (err.advice === 'reconnect' && (this.connected || this.connecting)) { - this.disconnect(); - if (this.options.reconnect) { - this.reconnect(); - } - } - } - - this.publish('error', err && err.reason ? err.reason : err); - }; - - /** - * Called when the transport disconnects. - * - * @api private - */ - - Socket.prototype.onDisconnect = function (reason) { - var wasConnected = this.connected - , wasConnecting = this.connecting; - - this.connected = false; - this.connecting = false; - this.open = false; - - if (wasConnected || wasConnecting) { - this.transport.close(); - this.transport.clearTimeouts(); - if (wasConnected) { - this.publish('disconnect', reason); - - if ('booted' != reason && this.options.reconnect && !this.reconnecting) { - this.reconnect(); - } - } - } - }; - - /** - * Called upon reconnection. - * - * @api private - */ - - Socket.prototype.reconnect = function () { - this.reconnecting = true; - this.reconnectionAttempts = 0; - this.reconnectionDelay = this.options['reconnection delay']; - - var self = this - , maxAttempts = this.options['max reconnection attempts'] - , tryMultiple = this.options['try multiple transports'] - , limit = this.options['reconnection limit']; - - function reset () { - if (self.connected) { - for (var i in self.namespaces) { - if (self.namespaces.hasOwnProperty(i) && '' !== i) { - self.namespaces[i].packet({ type: 'connect' }); - } - } - self.publish('reconnect', self.transport.name, self.reconnectionAttempts); - } - - clearTimeout(self.reconnectionTimer); - - self.removeListener('connect_failed', maybeReconnect); - self.removeListener('connect', maybeReconnect); - - self.reconnecting = false; - - delete self.reconnectionAttempts; - delete self.reconnectionDelay; - delete self.reconnectionTimer; - delete self.redoTransports; - - self.options['try multiple transports'] = tryMultiple; - }; - - function maybeReconnect () { - if (!self.reconnecting) { - return; - } - - if (self.connected) { - return reset(); - }; - - if (self.connecting && self.reconnecting) { - return self.reconnectionTimer = setTimeout(maybeReconnect, 1000); - } - - if (self.reconnectionAttempts++ >= maxAttempts) { - if (!self.redoTransports) { - self.on('connect_failed', maybeReconnect); - self.options['try multiple transports'] = true; - self.transports = self.origTransports; - self.transport = self.getTransport(); - self.redoTransports = true; - self.connect(); - } else { - self.publish('reconnect_failed'); - reset(); - } - } else { - if (self.reconnectionDelay < limit) { - self.reconnectionDelay *= 2; // exponential back off - } - - self.connect(); - self.publish('reconnecting', self.reconnectionDelay, self.reconnectionAttempts); - self.reconnectionTimer = setTimeout(maybeReconnect, self.reconnectionDelay); - } - }; - - this.options['try multiple transports'] = false; - this.reconnectionTimer = setTimeout(maybeReconnect, this.reconnectionDelay); - - this.on('connect', maybeReconnect); - }; - -})( - 'undefined' != typeof io ? io : module.exports - , 'undefined' != typeof io ? io : module.parent.exports - , this -); -/** - * socket.io - * Copyright(c) 2011 LearnBoost - * MIT Licensed - */ - -(function (exports, io) { - - /** - * Expose constructor. - */ - - exports.SocketNamespace = SocketNamespace; - - /** - * Socket namespace constructor. - * - * @constructor - * @api public - */ - - function SocketNamespace (socket, name) { - this.socket = socket; - this.name = name || ''; - this.flags = {}; - this.json = new Flag(this, 'json'); - this.ackPackets = 0; - this.acks = {}; - }; - - /** - * Apply EventEmitter mixin. - */ - - io.util.mixin(SocketNamespace, io.EventEmitter); - - /** - * Copies emit since we override it - * - * @api private - */ - - SocketNamespace.prototype.$emit = io.EventEmitter.prototype.emit; - - /** - * Creates a new namespace, by proxying the request to the socket. This - * allows us to use the synax as we do on the server. - * - * @api public - */ - - SocketNamespace.prototype.of = function () { - return this.socket.of.apply(this.socket, arguments); - }; - - /** - * Sends a packet. - * - * @api private - */ - - SocketNamespace.prototype.packet = function (packet) { - packet.endpoint = this.name; - this.socket.packet(packet); - this.flags = {}; - return this; - }; - - /** - * Sends a message - * - * @api public - */ - - SocketNamespace.prototype.send = function (data, fn) { - var packet = { - type: this.flags.json ? 'json' : 'message' - , data: data - }; - - if ('function' == typeof fn) { - packet.id = ++this.ackPackets; - packet.ack = true; - this.acks[packet.id] = fn; - } - - return this.packet(packet); - }; - - /** - * Emits an event - * - * @api public - */ - - SocketNamespace.prototype.emit = function (name) { - var args = Array.prototype.slice.call(arguments, 1) - , lastArg = args[args.length - 1] - , packet = { - type: 'event' - , name: name - }; - - if ('function' == typeof lastArg) { - packet.id = ++this.ackPackets; - packet.ack = 'data'; - this.acks[packet.id] = lastArg; - args = args.slice(0, args.length - 1); - } - - packet.args = args; - - return this.packet(packet); - }; - - /** - * Disconnects the namespace - * - * @api private - */ - - SocketNamespace.prototype.disconnect = function () { - if (this.name === '') { - this.socket.disconnect(); - } else { - this.packet({ type: 'disconnect' }); - this.$emit('disconnect'); - } - - return this; - }; - - /** - * Handles a packet - * - * @api private - */ - - SocketNamespace.prototype.onPacket = function (packet) { - var self = this; - - function ack () { - self.packet({ - type: 'ack' - , args: io.util.toArray(arguments) - , ackId: packet.id - }); - }; - - switch (packet.type) { - case 'connect': - this.$emit('connect'); - break; - - case 'disconnect': - if (this.name === '') { - this.socket.onDisconnect(packet.reason || 'booted'); - } else { - this.$emit('disconnect', packet.reason); - } - break; - - case 'message': - case 'json': - var params = ['message', packet.data]; - - if (packet.ack == 'data') { - params.push(ack); - } else if (packet.ack) { - this.packet({ type: 'ack', ackId: packet.id }); - } - - this.$emit.apply(this, params); - break; - - case 'event': - var params = [packet.name].concat(packet.args); - - if (packet.ack == 'data') - params.push(ack); - - this.$emit.apply(this, params); - break; - - case 'ack': - if (this.acks[packet.ackId]) { - this.acks[packet.ackId].apply(this, packet.args); - delete this.acks[packet.ackId]; - } - break; - - case 'error': - if (packet.advice){ - this.socket.onError(packet); - } else { - if (packet.reason == 'unauthorized') { - this.$emit('connect_failed', packet.reason); - } else { - this.$emit('error', packet.reason); - } - } - break; - } - }; - - /** - * Flag interface. - * - * @api private - */ - - function Flag (nsp, name) { - this.namespace = nsp; - this.name = name; - }; - - /** - * Send a message - * - * @api public - */ - - Flag.prototype.send = function () { - this.namespace.flags[this.name] = true; - this.namespace.send.apply(this.namespace, arguments); - }; - - /** - * Emit an event - * - * @api public - */ - - Flag.prototype.emit = function () { - this.namespace.flags[this.name] = true; - this.namespace.emit.apply(this.namespace, arguments); - }; - -})( - 'undefined' != typeof io ? io : module.exports - , 'undefined' != typeof io ? io : module.parent.exports -); - -/** - * socket.io - * Copyright(c) 2011 LearnBoost - * MIT Licensed - */ - -(function (exports, io, global) { - - /** - * Expose constructor. - */ - - exports.websocket = WS; - - /** - * The WebSocket transport uses the HTML5 WebSocket API to establish an - * persistent connection with the Socket.IO server. This transport will also - * be inherited by the FlashSocket fallback as it provides a API compatible - * polyfill for the WebSockets. - * - * @constructor - * @extends {io.Transport} - * @api public - */ - - function WS (socket) { - io.Transport.apply(this, arguments); - }; - - /** - * Inherits from Transport. - */ - - io.util.inherit(WS, io.Transport); - - /** - * Transport name - * - * @api public - */ - - WS.prototype.name = 'websocket'; - - /** - * Initializes a new `WebSocket` connection with the Socket.IO server. We attach - * all the appropriate listeners to handle the responses from the server. - * - * @returns {Transport} - * @api public - */ - - WS.prototype.open = function () { - var query = io.util.query(this.socket.options.query) - , self = this - , Socket - - - if (!Socket) { - Socket = global.MozWebSocket || global.WebSocket; - } - - this.websocket = new Socket(this.prepareUrl() + query); - - this.websocket.onopen = function () { - self.onOpen(); - self.socket.setBuffer(false); - }; - this.websocket.onmessage = function (ev) { - self.onData(ev.data); - }; - this.websocket.onclose = function () { - self.onClose(); - self.socket.setBuffer(true); - }; - this.websocket.onerror = function (e) { - self.onError(e); - }; - - return this; - }; - - /** - * Send a message to the Socket.IO server. The message will automatically be - * encoded in the correct message format. - * - * @returns {Transport} - * @api public - */ - - // Do to a bug in the current IDevices browser, we need to wrap the send in a - // setTimeout, when they resume from sleeping the browser will crash if - // we don't allow the browser time to detect the socket has been closed - if (io.util.ua.iDevice) { - WS.prototype.send = function (data) { - var self = this; - setTimeout(function() { - self.websocket.send(data); - },0); - return this; - }; - } else { - WS.prototype.send = function (data) { - this.websocket.send(data); - return this; - }; - } - - /** - * Payload - * - * @api private - */ - - WS.prototype.payload = function (arr) { - for (var i = 0, l = arr.length; i < l; i++) { - this.packet(arr[i]); - } - return this; - }; - - /** - * Disconnect the established `WebSocket` connection. - * - * @returns {Transport} - * @api public - */ - - WS.prototype.close = function () { - this.websocket.close(); - return this; - }; - - /** - * Handle the errors that `WebSocket` might be giving when we - * are attempting to connect or send messages. - * - * @param {Error} e The error. - * @api private - */ - - WS.prototype.onError = function (e) { - this.socket.onError(e); - }; - - /** - * Returns the appropriate scheme for the URI generation. - * - * @api private - */ - WS.prototype.scheme = function () { - return this.socket.options.secure ? 'wss' : 'ws'; - }; - - /** - * Checks if the browser has support for native `WebSockets` and that - * it's not the polyfill created for the FlashSocket transport. - * - * @return {Boolean} - * @api public - */ - - WS.check = function () { - return ('WebSocket' in global && !('__addTask' in WebSocket)) - || 'MozWebSocket' in global; - }; - - /** - * Check if the `WebSocket` transport support cross domain communications. - * - * @returns {Boolean} - * @api public - */ - - WS.xdomainCheck = function () { - return true; - }; - - /** - * Add the transport to your public io.transports array. - * - * @api private - */ - - io.transports.push('websocket'); - -})( - 'undefined' != typeof io ? io.Transport : module.exports - , 'undefined' != typeof io ? io : module.parent.exports - , this -); - -/** - * socket.io - * Copyright(c) 2011 LearnBoost - * MIT Licensed - */ - -(function (exports, io, global) { - - /** - * Expose constructor. - * - * @api public - */ - - exports.XHR = XHR; - - /** - * XHR constructor - * - * @costructor - * @api public - */ - - function XHR (socket) { - if (!socket) return; - - io.Transport.apply(this, arguments); - this.sendBuffer = []; - }; - - /** - * Inherits from Transport. - */ - - io.util.inherit(XHR, io.Transport); - - /** - * Establish a connection - * - * @returns {Transport} - * @api public - */ - - XHR.prototype.open = function () { - this.socket.setBuffer(false); - this.onOpen(); - this.get(); - - // we need to make sure the request succeeds since we have no indication - // whether the request opened or not until it succeeded. - this.setCloseTimeout(); - - return this; - }; - - /** - * Check if we need to send data to the Socket.IO server, if we have data in our - * buffer we encode it and forward it to the `post` method. - * - * @api private - */ - - XHR.prototype.payload = function (payload) { - var msgs = []; - - for (var i = 0, l = payload.length; i < l; i++) { - msgs.push(io.parser.encodePacket(payload[i])); - } - - this.send(io.parser.encodePayload(msgs)); - }; - - /** - * Send data to the Socket.IO server. - * - * @param data The message - * @returns {Transport} - * @api public - */ - - XHR.prototype.send = function (data) { - this.post(data); - return this; - }; - - /** - * Posts a encoded message to the Socket.IO server. - * - * @param {String} data A encoded message. - * @api private - */ - - function empty () { }; - - XHR.prototype.post = function (data) { - var self = this; - this.socket.setBuffer(true); - - function stateChange () { - if (this.readyState == 4) { - this.onreadystatechange = empty; - self.posting = false; - - if (this.status == 200){ - self.socket.setBuffer(false); - } else { - self.onClose(); - } - } - } - - function onload () { - this.onload = empty; - self.socket.setBuffer(false); - }; - - this.sendXHR = this.request('POST'); - - if (global.XDomainRequest && this.sendXHR instanceof XDomainRequest) { - this.sendXHR.onload = this.sendXHR.onerror = onload; - } else { - this.sendXHR.onreadystatechange = stateChange; - } - - this.sendXHR.send(data); - }; - - /** - * Disconnects the established `XHR` connection. - * - * @returns {Transport} - * @api public - */ - - XHR.prototype.close = function () { - this.onClose(); - return this; - }; - - /** - * Generates a configured XHR request - * - * @param {String} url The url that needs to be requested. - * @param {String} method The method the request should use. - * @returns {XMLHttpRequest} - * @api private - */ - - XHR.prototype.request = function (method) { - var req = io.util.request(this.socket.isXDomain()) - , query = io.util.query(this.socket.options.query, 't=' + +new Date); - - req.open(method || 'GET', this.prepareUrl() + query, true); - - if (method == 'POST') { - try { - if (req.setRequestHeader) { - req.setRequestHeader('Content-type', 'text/plain;charset=UTF-8'); - } else { - // XDomainRequest - req.contentType = 'text/plain'; - } - } catch (e) {} - } - - return req; - }; - - /** - * Returns the scheme to use for the transport URLs. - * - * @api private - */ - - XHR.prototype.scheme = function () { - return this.socket.options.secure ? 'https' : 'http'; - }; - - /** - * Check if the XHR transports are supported - * - * @param {Boolean} xdomain Check if we support cross domain requests. - * @returns {Boolean} - * @api public - */ - - XHR.check = function (socket, xdomain) { - try { - var request = io.util.request(xdomain), - usesXDomReq = (global.XDomainRequest && request instanceof XDomainRequest), - socketProtocol = (socket && socket.options && socket.options.secure ? 'https:' : 'http:'), - isXProtocol = (global.location && socketProtocol != global.location.protocol); - if (request && !(usesXDomReq && isXProtocol)) { - return true; - } - } catch(e) {} - - return false; - }; - - /** - * Check if the XHR transport supports cross domain requests. - * - * @returns {Boolean} - * @api public - */ - - XHR.xdomainCheck = function (socket) { - return XHR.check(socket, true); - }; - -})( - 'undefined' != typeof io ? io.Transport : module.exports - , 'undefined' != typeof io ? io : module.parent.exports - , this -); -/** - * socket.io - * Copyright(c) 2011 LearnBoost - * MIT Licensed - */ - -(function (exports, io) { - - /** - * Expose constructor. - */ - - exports.htmlfile = HTMLFile; - - /** - * The HTMLFile transport creates a `forever iframe` based transport - * for Internet Explorer. Regular forever iframe implementations will - * continuously trigger the browsers buzy indicators. If the forever iframe - * is created inside a `htmlfile` these indicators will not be trigged. - * - * @constructor - * @extends {io.Transport.XHR} - * @api public - */ - - function HTMLFile (socket) { - io.Transport.XHR.apply(this, arguments); - }; - - /** - * Inherits from XHR transport. - */ - - io.util.inherit(HTMLFile, io.Transport.XHR); - - /** - * Transport name - * - * @api public - */ - - HTMLFile.prototype.name = 'htmlfile'; - - /** - * Creates a new Ac...eX `htmlfile` with a forever loading iframe - * that can be used to listen to messages. Inside the generated - * `htmlfile` a reference will be made to the HTMLFile transport. - * - * @api private - */ - - HTMLFile.prototype.get = function () { - this.doc = new window[(['Active'].concat('Object').join('X'))]('htmlfile'); - this.doc.open(); - this.doc.write(''); - this.doc.close(); - this.doc.parentWindow.s = this; - - var iframeC = this.doc.createElement('div'); - iframeC.className = 'socketio'; - - this.doc.body.appendChild(iframeC); - this.iframe = this.doc.createElement('iframe'); - - iframeC.appendChild(this.iframe); - - var self = this - , query = io.util.query(this.socket.options.query, 't='+ +new Date); - - this.iframe.src = this.prepareUrl() + query; - - io.util.on(window, 'unload', function () { - self.destroy(); - }); - }; - - /** - * The Socket.IO server will write script tags inside the forever - * iframe, this function will be used as callback for the incoming - * information. - * - * @param {String} data The message - * @param {document} doc Reference to the context - * @api private - */ - - HTMLFile.prototype._ = function (data, doc) { - // unescape all forward slashes. see GH-1251 - data = data.replace(/\\\//g, '/'); - this.onData(data); - try { - var script = doc.getElementsByTagName('script')[0]; - script.parentNode.removeChild(script); - } catch (e) { } - }; - - /** - * Destroy the established connection, iframe and `htmlfile`. - * And calls the `CollectGarbage` function of Internet Explorer - * to release the memory. - * - * @api private - */ - - HTMLFile.prototype.destroy = function () { - if (this.iframe){ - try { - this.iframe.src = 'about:blank'; - } catch(e){} - - this.doc = null; - this.iframe.parentNode.removeChild(this.iframe); - this.iframe = null; - - CollectGarbage(); - } - }; - - /** - * Disconnects the established connection. - * - * @returns {Transport} Chaining. - * @api public - */ - - HTMLFile.prototype.close = function () { - this.destroy(); - return io.Transport.XHR.prototype.close.call(this); - }; - - /** - * Checks if the browser supports this transport. The browser - * must have an `Ac...eXObject` implementation. - * - * @return {Boolean} - * @api public - */ - - HTMLFile.check = function (socket) { - if (typeof window != "undefined" && (['Active'].concat('Object').join('X')) in window){ - try { - var a = new window[(['Active'].concat('Object').join('X'))]('htmlfile'); - return a && io.Transport.XHR.check(socket); - } catch(e){} - } - return false; - }; - - /** - * Check if cross domain requests are supported. - * - * @returns {Boolean} - * @api public - */ - - HTMLFile.xdomainCheck = function () { - // we can probably do handling for sub-domains, we should - // test that it's cross domain but a subdomain here - return false; - }; - - /** - * Add the transport to your public io.transports array. - * - * @api private - */ - - io.transports.push('htmlfile'); - -})( - 'undefined' != typeof io ? io.Transport : module.exports - , 'undefined' != typeof io ? io : module.parent.exports -); - -/** - * socket.io - * Copyright(c) 2011 LearnBoost - * MIT Licensed - */ - -(function (exports, io, global) { - - /** - * Expose constructor. - */ - - exports['xhr-polling'] = XHRPolling; - - /** - * The XHR-polling transport uses long polling XHR requests to create a - * "persistent" connection with the server. - * - * @constructor - * @api public - */ - - function XHRPolling () { - io.Transport.XHR.apply(this, arguments); - }; - - /** - * Inherits from XHR transport. - */ - - io.util.inherit(XHRPolling, io.Transport.XHR); - - /** - * Merge the properties from XHR transport - */ - - io.util.merge(XHRPolling, io.Transport.XHR); - - /** - * Transport name - * - * @api public - */ - - XHRPolling.prototype.name = 'xhr-polling'; - - /** - * Indicates whether heartbeats is enabled for this transport - * - * @api private - */ - - XHRPolling.prototype.heartbeats = function () { - return false; - }; - - /** - * Establish a connection, for iPhone and Android this will be done once the page - * is loaded. - * - * @returns {Transport} Chaining. - * @api public - */ - - XHRPolling.prototype.open = function () { - var self = this; - - io.Transport.XHR.prototype.open.call(self); - return false; - }; - - /** - * Starts a XHR request to wait for incoming messages. - * - * @api private - */ - - function empty () {}; - - XHRPolling.prototype.get = function () { - if (!this.isOpen) return; - - var self = this; - - function stateChange () { - if (this.readyState == 4) { - this.onreadystatechange = empty; - - if (this.status == 200) { - self.onData(this.responseText); - self.get(); - } else { - self.onClose(); - } - } - }; - - function onload () { - this.onload = empty; - this.onerror = empty; - self.retryCounter = 1; - self.onData(this.responseText); - self.get(); - }; - - function onerror () { - self.retryCounter ++; - if(!self.retryCounter || self.retryCounter > 3) { - self.onClose(); - } else { - self.get(); - } - }; - - this.xhr = this.request(); - - if (global.XDomainRequest && this.xhr instanceof XDomainRequest) { - this.xhr.onload = onload; - this.xhr.onerror = onerror; - } else { - this.xhr.onreadystatechange = stateChange; - } - - this.xhr.send(null); - }; - - /** - * Handle the unclean close behavior. - * - * @api private - */ - - XHRPolling.prototype.onClose = function () { - io.Transport.XHR.prototype.onClose.call(this); - - if (this.xhr) { - this.xhr.onreadystatechange = this.xhr.onload = this.xhr.onerror = empty; - try { - this.xhr.abort(); - } catch(e){} - this.xhr = null; - } - }; - - /** - * Webkit based browsers show a infinit spinner when you start a XHR request - * before the browsers onload event is called so we need to defer opening of - * the transport until the onload event is called. Wrapping the cb in our - * defer method solve this. - * - * @param {Socket} socket The socket instance that needs a transport - * @param {Function} fn The callback - * @api private - */ - - XHRPolling.prototype.ready = function (socket, fn) { - var self = this; - - io.util.defer(function () { - fn.call(self); - }); - }; - - /** - * Add the transport to your public io.transports array. - * - * @api private - */ - - io.transports.push('xhr-polling'); - -})( - 'undefined' != typeof io ? io.Transport : module.exports - , 'undefined' != typeof io ? io : module.parent.exports - , this -); - -/** - * socket.io - * Copyright(c) 2011 LearnBoost - * MIT Licensed - */ - -(function (exports, io, global) { - /** - * There is a way to hide the loading indicator in Firefox. If you create and - * remove a iframe it will stop showing the current loading indicator. - * Unfortunately we can't feature detect that and UA sniffing is evil. - * - * @api private - */ - - var indicator = global.document && "MozAppearance" in - global.document.documentElement.style; - - /** - * Expose constructor. - */ - - exports['jsonp-polling'] = JSONPPolling; - - /** - * The JSONP transport creates an persistent connection by dynamically - * inserting a script tag in the page. This script tag will receive the - * information of the Socket.IO server. When new information is received - * it creates a new script tag for the new data stream. - * - * @constructor - * @extends {io.Transport.xhr-polling} - * @api public - */ - - function JSONPPolling (socket) { - io.Transport['xhr-polling'].apply(this, arguments); - - this.index = io.j.length; - - var self = this; - - io.j.push(function (msg) { - self._(msg); - }); - }; - - /** - * Inherits from XHR polling transport. - */ - - io.util.inherit(JSONPPolling, io.Transport['xhr-polling']); - - /** - * Transport name - * - * @api public - */ - - JSONPPolling.prototype.name = 'jsonp-polling'; - - /** - * Posts a encoded message to the Socket.IO server using an iframe. - * The iframe is used because script tags can create POST based requests. - * The iframe is positioned outside of the view so the user does not - * notice it's existence. - * - * @param {String} data A encoded message. - * @api private - */ - - JSONPPolling.prototype.post = function (data) { - var self = this - , query = io.util.query( - this.socket.options.query - , 't='+ (+new Date) + '&i=' + this.index - ); - - if (!this.form) { - var form = document.createElement('form') - , area = document.createElement('textarea') - , id = this.iframeId = 'socketio_iframe_' + this.index - , iframe; - - form.className = 'socketio'; - form.style.position = 'absolute'; - form.style.top = '0px'; - form.style.left = '0px'; - form.style.display = 'none'; - form.target = id; - form.method = 'POST'; - form.setAttribute('accept-charset', 'utf-8'); - area.name = 'd'; - form.appendChild(area); - document.body.appendChild(form); - - this.form = form; - this.area = area; - } - - this.form.action = this.prepareUrl() + query; - - function complete () { - initIframe(); - self.socket.setBuffer(false); - }; - - function initIframe () { - if (self.iframe) { - self.form.removeChild(self.iframe); - } - - try { - // ie6 dynamic iframes with target="" support (thanks Chris Lambacher) - iframe = document.createElement('