Permalink
Browse files

Merge branch 'master' of https://github.com/dtex/spas

Conflicts:
	.gitignore
  • Loading branch information...
John Lafitte John Lafitte
John Lafitte authored and John Lafitte committed Oct 28, 2013
2 parents 85d2ed1 + fd125af commit 516c6f8653a26ba132fa0138fd98fe39cdbc59b4
Showing with 181 additions and 85 deletions.
  1. +10 −2 .gitignore
  2. +19 −0 LICENSE
  3. +4 −0 README.md
  4. +5 −0 lib/config.js
  5. +44 −15 lib/engine.js
  6. +28 −18 lib/oauth.js
  7. +31 −0 lib/streaming.js
  8. +18 −17 package.json
  9. +22 −33 spas.js
View
@@ -6,11 +6,19 @@ lib-cov
*.out
*.pid
*.gz
+
pids
results
node_modules
-bundles
-logs
+
+bundles/
+bundles/*
+!bundles/sample.js
+
+
+
config.json
+
npm-debug.log
.DS_Store
+config.json
View
19 LICENSE
@@ -0,0 +1,19 @@
+The MIT License (MIT)
+Copyright (c) 2012 Donovan Buck
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this
+software and associated documentation files (the "Software"), to deal in the Software
+without restriction, including without limitation the rights to use, copy, modify,
+merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be included in all copies
+or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
+OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
View
@@ -9,4 +9,8 @@ Every web site that hits multiple API's from the client should be doing this...
## Documentation
[http://dtex.github.com/spas](http://dtex.github.com/spas)
+## Stinkin' Badges
+
+[![NPM version](https://badge.fury.io/js/spas.png)](http://badge.fury.io/js/spas)
+[![Dependencies](https://david-dm.org/dtex/spas.png)](https://david-dm.org/dtex/spas.png)
[![Build Status](https://travis-ci.org/dtex/spas.png)](https://travis-ci.org/dtex/spas)
View
@@ -74,6 +74,11 @@ config.args = {
'spawned': nconf.get('spawned') ? true : false
};
+// We use this object to store various unique objects. For example, we store
+// the value inserted into the users cookie when authenticating against an
+// oauth provider, so we know what bundle part they are authenticating
+config.guids = {};
+
// If spas.js exists in the CWD, then this is probably a local install
config.isLocal = fs.existsSync(process.cwd() + '/spas.js');
View
@@ -153,22 +153,25 @@ exports.refresh = function(api, key, bid, bundle) {
}
+
//
// ## Retrieve the requested bundle
//
exports.fulfill = function ( myRes, ip, bid, callback, gzip, override ) {
winston.info('exports.fulfill: ' + bid);
- var bundle = GLOBAL.bundles[bid];
-
+ var bundle = GLOBAL.bundles[bid],
+ now = new Date();
+
// If the user requested a bundle that is not defined
if (typeof bundle === 'undefined') {
myRes.writeHead(404);
myRes.end();
return false;
}
+ // If a callback was not passed, and we have a default callback name in the bundle
if (!callback && bundle.callback) {
callback = bundle.callback;
}
@@ -186,6 +189,11 @@ exports.fulfill = function ( myRes, ip, bid, callback, gzip, override ) {
if(_.has(bundle, 'callback')) {
queriesInThisBundle--;
}
+
+ // expiration is not an API request
+ if(_.has(bundle, 'expiration')) {
+ queriesInThisBundle--;
+ }
// If override was not passed
if( _.isUndefined( override )) {
@@ -197,7 +205,9 @@ exports.fulfill = function ( myRes, ip, bid, callback, gzip, override ) {
// There was an error so force refresh on bundle
exports.fulfill( myRes, ip, bid, callback, gzip, true );
} else {
+ winston.debug('bid'+bid+':' + doc);
jDoc = JSON.parse( doc );
+ GLOBAL.bundles[bid].expiration = new Date(jDoc.expires);
jDoc.fromcache = true;
sendResponse(jDoc, myRes, ip, bid, callback, gzip);
}
@@ -227,7 +237,7 @@ exports.fulfill = function ( myRes, ip, bid, callback, gzip, override ) {
doc = JSON.parse( doc );
doc.expires = new Date(doc.expires);
if ( ('expires' in doc) && _.isDate(doc.expires) ) {
- var secleft = doc.expires.getSecondsBetween( new Date() ) * -1;
+ var secleft = doc.expires.getSecondsBetween( now ) * -1;
}
if (secleft < 0) {
self.finished = true;
@@ -310,7 +320,7 @@ exports.fulfill = function ( myRes, ip, bid, callback, gzip, override ) {
if ( err ) {
- api.expires = ( new Date() );
+ api.expires = ( now );
tout = {};
tout.cname = key;
tout.expires = api.expires;
@@ -334,7 +344,7 @@ exports.fulfill = function ( myRes, ip, bid, callback, gzip, override ) {
}
// Build the stored response
- api.expires = ( new Date() ).addSeconds( api.cacheduration );
+ api.expires = ( now ).addSeconds( api.cacheduration );
bundle[key] = api;
//client.set('bundle'+bid, JSON.stringify(bundle));
var tout = {
@@ -360,7 +370,12 @@ exports.fulfill = function ( myRes, ip, bid, callback, gzip, override ) {
queriesInThisBundle--;
- if (_.has(apiResponse, 'redirect')) thisResponse.redirect = apiResponse.redirect;
+ if (_.has(apiResponse, 'redirect')) {
+ thisResponse["redirect"] = apiResponse.redirect;
+ thisResponse["guid"] = apiResponse.guid || '';
+ thisResponse["authBundle"] = bid;
+ thisResponse["authPart"] = apiResponse.cname;
+ }
thisResponse[apiResponse.cname] = apiResponse;
if (queriesInThisBundle === 0) {
@@ -378,10 +393,15 @@ exports.fulfill = function ( myRes, ip, bid, callback, gzip, override ) {
// Update the expiration date on the bundle
var tout = {
expires: _.min( thisResponse, function( val ) { return val.expires } ).expires,
- lastModified: new Date()
+ lastModified: now
};
- if (_.has( thisResponse, 'redirect')) tout.redirect = thisResponse.redirect;
+ if (_.has( thisResponse, 'redirect')) {
+ tout.redirect = thisResponse.redirect,
+ tout.guid = thisResponse.guid,
+ tout.authBundle = thisResponse.authBundle,
+ tout.authPart = thisResponse.authPart
+ };
// Insert api responses into bundle
_.each( thisResponse, function( val, idx ) {
@@ -398,7 +418,7 @@ exports.fulfill = function ( myRes, ip, bid, callback, gzip, override ) {
// Determine the seconds left before expiry
if ( 'expires' in tout && _.isDate(tout.expires) ) {
- tout.secleft = tout.expires.getSecondsBetween( new Date() ) * -1;
+ tout.secleft = tout.expires.getSecondsBetween( now ) * -1;
} else {
tout.secleft = 3600;
}
@@ -413,12 +433,21 @@ exports.fulfill = function ( myRes, ip, bid, callback, gzip, override ) {
work: function(doc) {
winston.info('manager:sendResponse');
-
+
if (_.has(doc, 'redirect')) {
- myRes.statusCode = 302;
- myRes.setHeader("Location", doc.redirect);
- myRes.end();
- this.finished = true;
+
+ if (_.has(doc, 'guid')) {
+ GLOBAL.config.guids[doc.guid] = doc.authBundle+','+doc.authPart;
+ myRes.setHeader("Set-Cookie", "authCode="+doc.guid);
+ myRes.statusCode = 200;
+ myRes.end('<p>Please authorize spas at <a href="'+doc.redirect+'">'+doc.redirect+'</a></p>');
+ this.finished = true;
+ } else {
+ myRes.statusCode = 302;
+ myRes.setHeader("Location", doc.redirect);
+ myRes.end();
+ this.finished = true;
+ }
} else {
// Send the results
sendResponse(doc, myRes, ip, bid, callback, gzip);
@@ -431,7 +460,7 @@ exports.fulfill = function ( myRes, ip, bid, callback, gzip, override ) {
work:function() {
var parts = [];
_.each( bundle, function( api, key ) {
- if (key !== 'cleanup' && key !== 'callback') {
+ if (['cleanup', 'callback', 'expiration'].indexOf(key) === -1) {
if (_.isUndefined(api, 'credentials')) {
api.credentials = {};
}
View
@@ -3,6 +3,7 @@ var redis = require("redis")
, neuron = require('neuron')
, winston = require('./logging').winston
, oauth = require('oauth').OAuth
+ , uuid = require("node-uuid")
;
require('date-utils');
@@ -24,25 +25,29 @@ var manager = new neuron.JobManager();
manager.addJob('finishAuth', {
work: function( tout, cb, authParams ) {
cb(tout, authParams);
+ this.finished = true;
}
});
manager.addJob('getTemporaryCredentials', {
work: function( api, bid, key, cb ) {
+ winston.info ('getTemporaryCredentials called for ' + bid + ', ' + key);
+
var oaData = GLOBAL.config.authentication[api.auth.provider];
var oa = new oauth(oaData.requestTemporaryCredentials,
oaData.requestAccessToken,
oaData.oauth_consumer_key,
oaData.client_secret,
oaData.version,
- oaData.authorize,
+ oaData.authorize_callback,
oaData.encryption),
self = this;
oa.getOAuthRequestToken(function(error, oauth_token, oauth_token_secret, results){
if(error) {
- manager.enqueue('finishAuth', false, cb);
+ winston.error(error);
+ manager.enqueue('finishAuth', error, cb);
self.finished = true;
} else {
api.credentials = {
@@ -54,11 +59,12 @@ manager.addJob('getTemporaryCredentials', {
client.set(bid+key+'oauth', JSON.stringify(api.credentials));
var tout = {
expires: new Date(),
- redirect: oaData.authorize+
- "?oauth_token="+api.credentials.oauth_token+"&oauth_nonce="+bid+","+key,
+ redirect: oaData.authorize+"?oauth_token="+api.credentials.oauth_token,
err: {errnum:1, errtxt:"Authentication provider requires code."},
- cname: key
+ cname: key,
+ guid: uuid.v4()
};
+ winston.debug('Authorize redirect response:' + JSON.stringify(tout));
manager.enqueue('finishAuth', tout, cb);
self.finished = true;
}
@@ -67,8 +73,9 @@ manager.addJob('getTemporaryCredentials', {
});
manager.addJob('getAccessToken', {
- work: function(api, bid, key, oauth_token, cb) {
+ work: function(api, bid, key, oauth_token, oauth_verifier, cb) {
+ winston.info('Running getAccessToken for ' + bid + ', ' + key);
var oaData = GLOBAL.config.authentication[api.auth.provider],
self = this;
@@ -77,15 +84,18 @@ manager.addJob('getAccessToken', {
oaData.oauth_consumer_key,
oaData.client_secret,
oaData.version,
- oaData.authorize,
+ oaData.authorize_callback,
oaData.encryption);
-
- oa.getOAuthAccessToken(oauth_token, api.credentials.oauth_token_secret, function(error, oauth_access_token, oauth_access_token_secret, results2) {
+
+ winston.debug('oauth_token:' + oauth_token + ' api.credentials.oauth_token_secret:' + api.credentials.oauth_token_secret);
+
+ oa.getOAuthAccessToken(oauth_token, api.credentials.oauth_token_secret, oauth_verifier, function(error, oauth_access_token, oauth_access_token_secret, results2) {
if(error) {
// handle error
- manager.enqueue('finishAuth', false, cb);
- self.finsihed = true;
+ winston.error(error);
+ manager.enqueue('finishAuth', error, cb);
+ self.finished = true;
} else {
api.credentials.oauth_access_token = oauth_access_token;
api.credentials.oauth_token_secret = oauth_access_token_secret;
@@ -106,20 +116,21 @@ manager.addJob('getAccessToken', {
"oauth_timestamp": Math.floor( (new Date()).getTime() / 1000 ),
"oauth_token": api.credentials.oauth_access_token
});
- self.finsihed = true;
+ self.finished = true;
}
});
}
})
exports.authorize = function( api, bid, key, cb ) {
+ winston.info('oauth authorize called for ' + bid +', ' + key);
+
if (!_.has(GLOBAL.config, 'authentication') || !_.has(GLOBAL.config.authentication, api.auth.provider)) {
winston.error('Authentication provider ' + api.auth.provider + ' not defined');
return false;
}
- // We need to handle when provider is not defined in the config file
var oaData = GLOBAL.config.authentication[api.auth.provider];
// See if we have an oauth record in the database
@@ -150,20 +161,19 @@ exports.authorize = function( api, bid, key, cb ) {
});
}
-exports.saveOauthToken = function( api, nonce, oauth_token, cb) {
+exports.saveOauthToken = function( api, oauth_token, oauth_verifier, bid, key, cb) {
- var nonce = nonce.split(","),
- bid = nonce[0],
- key = nonce[1];
+ winston.info('Running saveOauthToken for ' + bid + ', ' + key);
client.get(bid+key+'oauth', function(err, doc) {
if (err || doc === null) {
// handle error here
} else {
api.credentials = JSON.parse(doc);
+ if (oauth_verifier) api.credentials.oauth_verifier = oauth_verifier;
- manager.enqueue('getAccessToken', api, bid, key, oauth_token, cb)
+ manager.enqueue('getAccessToken', api, bid, key, oauth_token, oauth_verifier, cb)
}
View
@@ -0,0 +1,31 @@
+var redis = require("redis")
+ , redisRStream = require('redis-rstream')
+ , zlib = require('zlib')
+ , winston = require('./logging').winston
+;
+
+// Connect to redis
+var client = redis.createClient(GLOBAL.config.redis.port, GLOBAL.config.redis.address);
+if (GLOBAL.config.redis.auth) {
+ client.auth(GLOBAL.config.redis.auth, function (err) {
+ if (err) {
+ // handle err;
+ }
+ });
+}
+
+//
+// ## The cached bundle is current so respond in the most efficient way possible
+//
+exports.response = function( bid, gzip, myRes, callback ) {
+
+ winston.info('streaming.response: ' + bid);
+
+ var responseType = callback ? 'application/javascript' : 'application/json';
+
+ var responseHeaders = {'Content-Type': responseType, 'vary': 'Accept-Encoding'};//, 'max-age': jDoc.secleft, 'cache-control': 'public, max-age='+jDoc.secleft+', no-transform', "Expires": jDoc.expires, "Last-Modified": jDoc.lastModified};
+
+ //myRes.writeHead(200, responseHeaders);
+ redisRStream(client, 'bid'+bid).pipe(myRes);
+ //myRes.end();
+}
Oops, something went wrong.

0 comments on commit 516c6f8

Please sign in to comment.