| 
									
										
										
										
											2015-11-21 12:36:22 +00:00
										 |  |  | 'use strict'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var escapeStringRegexp = require('escape-string-regexp'); | 
					
						
							|  |  |  | var staticHandlers = {}; | 
					
						
							|  |  |  | //var apiHandlers = {};
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function compileVhosts(vhostsMap) { | 
					
						
							|  |  |  |   var results = { | 
					
						
							|  |  |  |     patterns: [] | 
					
						
							|  |  |  |   , conflictsMap: {} | 
					
						
							|  |  |  |   , matchesMap: {} | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // compli
 | 
					
						
							|  |  |  |   Object.keys(vhostsMap).forEach(function (key) { | 
					
						
							|  |  |  |     var vhost = vhostsMap[key]; | 
					
						
							|  |  |  |     var bare; | 
					
						
							|  |  |  |     var www; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if ('.' === vhost.hostname[0]) { | 
					
						
							|  |  |  |       // for consistency
 | 
					
						
							|  |  |  |       // TODO this should happen at the database level
 | 
					
						
							|  |  |  |       vhost.hostname = '*' + vhost.hostname; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if ('*' === vhost.hostname[0]) { | 
					
						
							|  |  |  |       // TODO check that we are not trying to redirect a tld (.com, .co.uk, .org, etc)
 | 
					
						
							|  |  |  |       // tlds should follow the global policy
 | 
					
						
							|  |  |  |       if (vhost.hostname[1] && '.' !== vhost.hostname[1]) { | 
					
						
							|  |  |  |         // this is not a good place to throw as the consequences of a bug would be
 | 
					
						
							|  |  |  |         // very bad, but errors should never be silent, so we'll compromise
 | 
					
						
							|  |  |  |         console.warn("[NON-FATAL ERROR]: ignoring pattern '" + vhost.hostname + "'"); | 
					
						
							|  |  |  |         results.conflictsMap[vhost.hostname] = vhost; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       // nix the '*' for easier matching
 | 
					
						
							|  |  |  |       vhost.hostname = vhost.hostname.slice(1); | 
					
						
							|  |  |  |       // except the default
 | 
					
						
							|  |  |  |       if (!vhost.hostname) { | 
					
						
							|  |  |  |         vhost.hostname = '*'; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       if (results.conflictsMap[vhost.hostname]) { | 
					
						
							|  |  |  |         console.warn("[NON-FATAL ERROR]: duplicate entry for pattern '" + vhost.hostname + "'"); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       results.conflictsMap[vhost.hostname] = vhost; | 
					
						
							|  |  |  |       results.patterns.push(vhost); | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     bare = vhost.hostname.replace(/^www\./i, ''); | 
					
						
							|  |  |  |     www = vhost.hostname.replace(/^(www\.)?/i, 'www.'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     results.matchesMap[bare] = vhost; | 
					
						
							|  |  |  |     results.matchesMap[www] = vhost; | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   results.patterns.sort(function (a, b) { | 
					
						
							|  |  |  |     return b.id.length - a.id.length; | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return results; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-02 12:42:23 +00:00
										 |  |  | function loadPages(pkgConf, packagedPage, req, res, next) { | 
					
						
							| 
									
										
										
										
											2015-11-21 12:36:22 +00:00
										 |  |  |   var PromiseA = require('bluebird'); | 
					
						
							|  |  |  |   var fs = require('fs'); | 
					
						
							|  |  |  |   var path = require('path'); | 
					
						
							| 
									
										
										
										
											2015-12-02 12:42:23 +00:00
										 |  |  |   var pkgpath = path.join(pkgConf.pagespath, (packagedPage.package || packagedPage.id), (packagedPage.version || '')); | 
					
						
							| 
									
										
										
										
											2015-11-21 12:36:22 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |   // TODO special cases for /.well_known/ and similar (oauth3.html, oauth3.json, webfinger, etc)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   function handlePromise(p) { | 
					
						
							|  |  |  |     p.then(function (app) { | 
					
						
							|  |  |  |       app(req, res, next); | 
					
						
							| 
									
										
										
										
											2015-12-02 12:42:23 +00:00
										 |  |  |       packagedPage._page = app; | 
					
						
							| 
									
										
										
										
											2015-11-21 12:36:22 +00:00
										 |  |  |     }, function (err) { | 
					
						
							|  |  |  |       console.error('[App Promise Error]'); | 
					
						
							|  |  |  |       next(err); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (staticHandlers[pkgpath]) { | 
					
						
							| 
									
										
										
										
											2015-12-02 12:42:23 +00:00
										 |  |  |     packagedPage._page = staticHandlers[pkgpath]; | 
					
						
							|  |  |  |     packagedPage._page(req, res, next); | 
					
						
							| 
									
										
										
										
											2015-11-21 12:36:22 +00:00
										 |  |  |     return; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-02 12:42:23 +00:00
										 |  |  |   if (!packagedPage._promise_page) { | 
					
						
							|  |  |  |     packagedPage._promise_page = new PromiseA(function (resolve, reject) { | 
					
						
							| 
									
										
										
										
											2015-11-21 12:36:22 +00:00
										 |  |  |       fs.exists(pkgpath, function (exists) { | 
					
						
							| 
									
										
										
										
											2015-12-11 05:54:30 +00:00
										 |  |  |         var staticServer; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-11-21 12:36:22 +00:00
										 |  |  |         if (!exists) { | 
					
						
							| 
									
										
										
										
											2015-12-02 12:33:42 +00:00
										 |  |  |           reject(new Error("package '" + pkgpath + "' is registered but does not exist")); | 
					
						
							| 
									
										
										
										
											2015-11-21 12:36:22 +00:00
										 |  |  |           return; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         //console.log('[static mount]', pkgpath);
 | 
					
						
							| 
									
										
										
										
											2015-12-11 05:54:30 +00:00
										 |  |  |         // https://github.com/expressjs/serve-static/issues/54
 | 
					
						
							| 
									
										
										
										
											2015-12-12 03:43:31 +00:00
										 |  |  |         // https://github.com/pillarjs/send/issues/91
 | 
					
						
							| 
									
										
										
										
											2015-12-11 05:54:30 +00:00
										 |  |  |         // https://example.com/.well-known/acme-challenge/xxxxxxxxxxxxxxx
 | 
					
						
							| 
									
										
										
										
											2015-12-12 03:43:31 +00:00
										 |  |  |         staticServer = require('serve-static')(pkgpath, { dotfiles: undefined }); | 
					
						
							| 
									
										
										
										
											2015-12-11 05:54:30 +00:00
										 |  |  |         resolve(staticServer); | 
					
						
							| 
									
										
										
										
											2015-11-21 12:36:22 +00:00
										 |  |  |       }); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-02 12:42:23 +00:00
										 |  |  |   handlePromise(packagedPage._promise_page); | 
					
						
							| 
									
										
										
										
											2015-11-21 12:36:22 +00:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-08 00:51:48 +00:00
										 |  |  | function getApi(conf, pkgConf, pkgDeps, packagedApi) { | 
					
						
							| 
									
										
										
										
											2015-11-21 12:36:22 +00:00
										 |  |  |   var PromiseA = require('bluebird'); | 
					
						
							|  |  |  |   var path = require('path'); | 
					
						
							| 
									
										
										
										
											2015-12-02 12:33:42 +00:00
										 |  |  |   var pkgpath = path.join(pkgConf.apipath, packagedApi.id/*, (packagedApi.api.version || '')*/); | 
					
						
							| 
									
										
										
										
											2015-11-21 12:36:22 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |   // TODO needs some version stuff (which would also allow hot-loading of updates)
 | 
					
						
							|  |  |  |   // TODO version could be tied to sha256sum
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return new PromiseA(function (resolve, reject) { | 
					
						
							|  |  |  |     var myApp; | 
					
						
							|  |  |  |     var ursa; | 
					
						
							|  |  |  |     var promise; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // TODO dynamic requires are a no-no
 | 
					
						
							|  |  |  |     // can we statically generate a require-er? on each install?
 | 
					
						
							|  |  |  |     // module.exports = { {{pkgpath}}: function () { return require({{pkgpath}}) } }
 | 
					
						
							|  |  |  |     // requirer[pkgpath]()
 | 
					
						
							|  |  |  |     myApp = pkgDeps.express(); | 
					
						
							|  |  |  |     myApp.disable('x-powered-by'); | 
					
						
							|  |  |  |     if (pkgDeps.app.get('trust proxy')) { | 
					
						
							|  |  |  |       myApp.set('trust proxy', pkgDeps.app.get('trust proxy')); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     if (!pkgConf.pubkey) { | 
					
						
							|  |  |  |       /* | 
					
						
							|  |  |  |         return ursa.createPrivateKey(pem, password, encoding); | 
					
						
							|  |  |  |         var pem = myKey.toPrivatePem(); | 
					
						
							|  |  |  |         return jwt.verifyAsync(token, myKey.toPublicPem(), { ignoreExpiration: false && true }).then(function (decoded) { | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |       */ | 
					
						
							|  |  |  |       ursa = require('ursa'); | 
					
						
							|  |  |  |       pkgConf.keypair = ursa.createPrivateKey(pkgConf.privkey, 'ascii'); | 
					
						
							|  |  |  |       pkgConf.pubkey = ursa.createPublicKey(pkgConf.pubkey, 'ascii'); //conf.keypair.toPublicKey();
 | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     try { | 
					
						
							| 
									
										
										
										
											2015-12-02 12:33:42 +00:00
										 |  |  |       packagedApi._apipkg = require(path.join(pkgpath, 'package.json')); | 
					
						
							|  |  |  |       packagedApi._apiname = packagedApi._apipkg.name; | 
					
						
							|  |  |  |       if (packagedApi._apipkg.walnut) { | 
					
						
							|  |  |  |         pkgpath += '/' + packagedApi._apipkg.walnut; | 
					
						
							| 
									
										
										
										
											2015-11-23 08:42:55 +00:00
										 |  |  |       } | 
					
						
							| 
									
										
										
										
											2015-12-04 07:00:30 +00:00
										 |  |  |       promise = PromiseA.resolve(require(pkgpath).create(pkgConf, pkgDeps, myApp)); | 
					
						
							| 
									
										
										
										
											2015-11-21 12:36:22 +00:00
										 |  |  |     } catch(e) { | 
					
						
							|  |  |  |       reject(e); | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     promise.then(function () { | 
					
						
							|  |  |  |       // TODO give pub/priv pair for app and all public keys
 | 
					
						
							| 
									
										
										
										
											2015-12-02 12:33:42 +00:00
										 |  |  |       // packagedApi._api = require(pkgpath).create(pkgConf, pkgDeps, myApp);
 | 
					
						
							| 
									
										
										
										
											2015-12-04 07:00:30 +00:00
										 |  |  |       packagedApi._api = require('express-lazy')(); | 
					
						
							| 
									
										
										
										
											2015-12-02 12:33:42 +00:00
										 |  |  |       packagedApi._api_app = myApp; | 
					
						
							| 
									
										
										
										
											2015-12-04 07:00:30 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-12 04:55:47 +00:00
										 |  |  |       //require('./oauth3-auth').inject(conf, packagedApi._api, pkgConf, pkgDeps);
 | 
					
						
							| 
									
										
										
										
											2015-12-04 07:00:30 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |       // DEBUG
 | 
					
						
							| 
									
										
										
										
											2015-12-04 08:46:11 +00:00
										 |  |  |       //
 | 
					
						
							| 
									
										
										
										
											2015-12-04 08:11:34 +00:00
										 |  |  |       /* | 
					
						
							| 
									
										
										
										
											2015-12-04 07:00:30 +00:00
										 |  |  |       packagedApi._api.use('/', function (req, res, next) { | 
					
						
							| 
									
										
										
										
											2015-12-04 08:46:11 +00:00
										 |  |  |         console.log('[DEBUG pkgApiApp]', req.method, req.hostname, req.url); | 
					
						
							| 
									
										
										
										
											2015-12-04 07:00:30 +00:00
										 |  |  |         next(); | 
					
						
							|  |  |  |       }); | 
					
						
							| 
									
										
										
										
											2015-12-04 08:46:11 +00:00
										 |  |  |       //*/
 | 
					
						
							| 
									
										
										
										
											2015-12-04 07:00:30 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-11-21 12:36:22 +00:00
										 |  |  |       // TODO fix backwards compat
 | 
					
						
							| 
									
										
										
										
											2015-12-04 07:00:30 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-11-21 12:36:22 +00:00
										 |  |  |       // /api/com.example.foo (no change)
 | 
					
						
							| 
									
										
										
										
											2015-12-02 12:33:42 +00:00
										 |  |  |       packagedApi._api.use('/', packagedApi._api_app); | 
					
						
							| 
									
										
										
										
											2015-12-04 07:00:30 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-11-21 12:36:22 +00:00
										 |  |  |       // /api/com.example.foo => /api
 | 
					
						
							| 
									
										
										
										
											2015-12-02 12:33:42 +00:00
										 |  |  |       packagedApi._api.use('/', function (req, res, next) { | 
					
						
							| 
									
										
										
										
											2015-12-04 07:00:30 +00:00
										 |  |  |         var priorUrl = req.url; | 
					
						
							| 
									
										
										
										
											2015-12-02 12:33:42 +00:00
										 |  |  |         req.url = '/api' + req.url.slice(('/api/' + packagedApi.id).length); | 
					
						
							| 
									
										
										
										
											2015-12-04 09:57:52 +00:00
										 |  |  |         // console.log('api mangle 3:', req.url);
 | 
					
						
							| 
									
										
										
										
											2015-12-04 07:00:30 +00:00
										 |  |  |         packagedApi._api_app(req, res, function (err) { | 
					
						
							|  |  |  |           req.url = priorUrl; | 
					
						
							|  |  |  |           next(err); | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       // /api/com.example.foo => /
 | 
					
						
							|  |  |  |       packagedApi._api.use('/api/' + packagedApi.id, function (req, res, next) { | 
					
						
							|  |  |  |         // console.log('api mangle 2:', '/api/' + packagedApi.id, req.url);
 | 
					
						
							| 
									
										
										
										
											2015-12-04 09:57:52 +00:00
										 |  |  |         // console.log(packagedApi._api_app.toString());
 | 
					
						
							| 
									
										
										
										
											2015-12-02 12:33:42 +00:00
										 |  |  |         packagedApi._api_app(req, res, next); | 
					
						
							| 
									
										
										
										
											2015-11-21 12:36:22 +00:00
										 |  |  |       }); | 
					
						
							| 
									
										
										
										
											2015-12-04 07:00:30 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-02 12:33:42 +00:00
										 |  |  |       resolve(packagedApi._api); | 
					
						
							| 
									
										
										
										
											2015-11-21 12:36:22 +00:00
										 |  |  |     }, reject); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-08 00:51:48 +00:00
										 |  |  | function loadApi(conf, pkgConf, pkgDeps, packagedApi) { | 
					
						
							| 
									
										
										
										
											2015-11-21 12:36:22 +00:00
										 |  |  |   function handlePromise(p) { | 
					
						
							|  |  |  |     return p.then(function (api) { | 
					
						
							| 
									
										
										
										
											2015-12-02 12:33:42 +00:00
										 |  |  |       packagedApi._api = api; | 
					
						
							| 
									
										
										
										
											2015-11-21 12:36:22 +00:00
										 |  |  |       return api; | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-02 12:33:42 +00:00
										 |  |  |   if (!packagedApi._promise_api) { | 
					
						
							| 
									
										
										
										
											2015-12-08 00:51:48 +00:00
										 |  |  |     packagedApi._promise_api = getApi(conf, pkgConf, pkgDeps, packagedApi); | 
					
						
							| 
									
										
										
										
											2015-11-21 12:36:22 +00:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-02 12:33:42 +00:00
										 |  |  |   return handlePromise(packagedApi._promise_api); | 
					
						
							| 
									
										
										
										
											2015-11-21 12:36:22 +00:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function layerItUp(pkgConf, router, req, res, next) { | 
					
						
							|  |  |  |   var nexti = -1; | 
					
						
							|  |  |  |   // Layers exist so that static apps can use them like a virtual filesystem
 | 
					
						
							|  |  |  |   // i.e. oauth3.html isn't in *your* app but you may use it and want it mounted at /.well-known/oauth3.html
 | 
					
						
							|  |  |  |   // or perhaps some dynamic content (like application cache)
 | 
					
						
							|  |  |  |   function nextify(err) { | 
					
						
							| 
									
										
										
										
											2015-12-02 12:42:23 +00:00
										 |  |  |     var packagedPage; | 
					
						
							| 
									
										
										
										
											2015-11-21 12:36:22 +00:00
										 |  |  |     nexti += 1; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (err) { | 
					
						
							|  |  |  |       next(err); | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // shortest to longest
 | 
					
						
							|  |  |  |     //route = packages.pop();
 | 
					
						
							|  |  |  |     // longest to shortest
 | 
					
						
							| 
									
										
										
										
											2015-12-02 12:42:23 +00:00
										 |  |  |     packagedPage = router.packagedPages[nexti]; | 
					
						
							|  |  |  |     if (!packagedPage) { | 
					
						
							| 
									
										
										
										
											2015-11-21 12:36:22 +00:00
										 |  |  |       next(); | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-02 12:42:23 +00:00
										 |  |  |     if (packagedPage._page) { | 
					
						
							|  |  |  |       packagedPage._page(req, res, nextify); | 
					
						
							| 
									
										
										
										
											2015-11-21 12:36:22 +00:00
										 |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // could attach to req.{ pkgConf, pkgDeps, Services}
 | 
					
						
							| 
									
										
										
										
											2015-12-02 12:42:23 +00:00
										 |  |  |     loadPages(pkgConf, packagedPage, req, res, next); | 
					
						
							| 
									
										
										
										
											2015-11-21 12:36:22 +00:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   nextify(); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function runApi(opts, router, req, res, next) { | 
					
						
							|  |  |  |   var pkgConf = opts.config; | 
					
						
							|  |  |  |   var pkgDeps = opts.deps; | 
					
						
							|  |  |  |   //var Services = opts.Services;
 | 
					
						
							| 
									
										
										
										
											2015-12-02 12:33:42 +00:00
										 |  |  |   var packagedApi; | 
					
						
							| 
									
										
										
										
											2015-11-21 12:36:22 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |   // TODO compile packagesMap
 | 
					
						
							|  |  |  |   // TODO people may want to use the framework in a non-framework way (i.e. to conceal the module name)
 | 
					
						
							| 
									
										
										
										
											2015-12-02 12:33:42 +00:00
										 |  |  |   router.packagedApis.some(function (_packagedApi) { | 
					
						
							| 
									
										
										
										
											2015-12-04 08:11:34 +00:00
										 |  |  |     // console.log('[DEBUG _packagedApi.id]', _packagedApi.id);
 | 
					
						
							| 
									
										
										
										
											2015-11-21 12:36:22 +00:00
										 |  |  |     var pathname = router.pathname; | 
					
						
							|  |  |  |     if ('/' === pathname) { | 
					
						
							|  |  |  |       pathname = ''; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     // TODO allow for special apis that do not follow convention (.well_known, webfinger, oauth3.html, etc)
 | 
					
						
							| 
									
										
										
										
											2015-12-02 12:33:42 +00:00
										 |  |  |     if (!_packagedApi._api_re) { | 
					
						
							|  |  |  |       _packagedApi._api_re = new RegExp(escapeStringRegexp(pathname + '/api/' + _packagedApi.id) + '\/([\\w\\.\\-]+)(\\/|\\?|$)'); | 
					
						
							|  |  |  |       //console.log('[api re 2]', _packagedApi._api_re);
 | 
					
						
							| 
									
										
										
										
											2015-11-21 12:36:22 +00:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2015-12-02 12:33:42 +00:00
										 |  |  |     if (_packagedApi._api_re.test(req.url)) { | 
					
						
							|  |  |  |       packagedApi = _packagedApi; | 
					
						
							| 
									
										
										
										
											2015-11-21 12:36:22 +00:00
										 |  |  |       return true; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-02 12:33:42 +00:00
										 |  |  |   if (!packagedApi) { | 
					
						
							|  |  |  |     console.log("[ODD] no api for '" + req.url + "'"); | 
					
						
							| 
									
										
										
										
											2015-11-21 12:36:22 +00:00
										 |  |  |     next(); | 
					
						
							|  |  |  |     return; | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2015-12-08 00:51:48 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |   // Reaching this point means that there are APIs for this pathname
 | 
					
						
							|  |  |  |   // it is important to identify this host + pathname (example.com/foo) as the app
 | 
					
						
							|  |  |  |   Object.defineProperty(req, 'experienceId', { | 
					
						
							|  |  |  |     enumerable: true | 
					
						
							|  |  |  |   , configurable: false | 
					
						
							|  |  |  |   , writable: false | 
					
						
							|  |  |  |     // TODO this identifier may need to be non-deterministic as to transfer if a domain name changes but is still the "same" app
 | 
					
						
							|  |  |  |     // (i.e. a company name change. maybe auto vs manual register - just like oauth3?)
 | 
					
						
							|  |  |  |     // NOTE: probably best to alias the name logically
 | 
					
						
							|  |  |  |   , value: (req.hostname + req.pathname).replace(/\/$/, '') | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  |   Object.defineProperty(req, 'escapedExperienceId', { | 
					
						
							|  |  |  |     enumerable: true | 
					
						
							|  |  |  |   , configurable: false | 
					
						
							|  |  |  |   , writable: false | 
					
						
							|  |  |  |     // TODO this identifier may need to be non-deterministic as to transfer if a domain name changes but is still the "same" app
 | 
					
						
							|  |  |  |     // (i.e. a company name change. maybe auto vs manual register - just like oauth3?)
 | 
					
						
							|  |  |  |     // NOTE: probably best to alias the name logically
 | 
					
						
							|  |  |  |   , value: req.experienceId.replace(/\//g, ':') | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  |   // packageId should mean hash(api.id + host + path) - also called "api"
 | 
					
						
							|  |  |  |   Object.defineProperty(req, 'packageId', { | 
					
						
							| 
									
										
										
										
											2015-11-21 12:36:22 +00:00
										 |  |  |     enumerable: true | 
					
						
							|  |  |  |   , configurable: false | 
					
						
							|  |  |  |   , writable: false | 
					
						
							|  |  |  |     // TODO this identifier may need to be non-deterministic as to transfer if a domain name changes but is still the "same" app
 | 
					
						
							|  |  |  |     // (i.e. a company name change. maybe auto vs manual register - just like oauth3?)
 | 
					
						
							| 
									
										
										
										
											2015-12-08 00:51:48 +00:00
										 |  |  |     // NOTE: probably best to alias the name logically
 | 
					
						
							| 
									
										
										
										
											2015-12-02 12:33:42 +00:00
										 |  |  |   , value: packagedApi.domain.id | 
					
						
							| 
									
										
										
										
											2015-11-21 12:36:22 +00:00
										 |  |  |   }); | 
					
						
							|  |  |  |   Object.defineProperty(req, 'appConfig', { | 
					
						
							|  |  |  |     enumerable: true | 
					
						
							|  |  |  |   , configurable: false | 
					
						
							|  |  |  |   , writable: false | 
					
						
							|  |  |  |   , value: {}       // TODO just the app-scoped config
 | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  |   Object.defineProperty(req, 'appDeps', { | 
					
						
							|  |  |  |     enumerable: true | 
					
						
							|  |  |  |   , configurable: false | 
					
						
							|  |  |  |   , writable: false | 
					
						
							|  |  |  |   , value: {}       // TODO app-scoped deps
 | 
					
						
							|  |  |  |                     // i.e. when we need to use things such as stripe id
 | 
					
						
							|  |  |  |                     // without exposing them to the app
 | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   //
 | 
					
						
							|  |  |  |   // TODO user authentication should go right about here
 | 
					
						
							|  |  |  |   //
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   //
 | 
					
						
							|  |  |  |   // TODO freeze objects for passing them into app
 | 
					
						
							|  |  |  |   //
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-02 12:33:42 +00:00
										 |  |  |   if (packagedApi._api) { | 
					
						
							|  |  |  |     packagedApi._api(req, res, next); | 
					
						
							| 
									
										
										
										
											2015-11-21 12:36:22 +00:00
										 |  |  |     return; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-04 08:11:34 +00:00
										 |  |  |   // console.log("[DEBUG pkgpath]", pkgConf.apipath, packagedApi.id);
 | 
					
						
							| 
									
										
										
										
											2015-12-08 00:51:48 +00:00
										 |  |  |   loadApi(opts.conf, pkgConf, pkgDeps, packagedApi).then(function (api) { | 
					
						
							| 
									
										
										
										
											2015-11-21 12:36:22 +00:00
										 |  |  |     api(req, res, next); | 
					
						
							|  |  |  |   }, function (err) { | 
					
						
							|  |  |  |     console.error('[App Promise Error]'); | 
					
						
							|  |  |  |     next(err); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function mapToApp(opts, req, res, next) { | 
					
						
							|  |  |  |   // opts = { config, deps, services }
 | 
					
						
							|  |  |  |   var vhost; | 
					
						
							|  |  |  |   var router; | 
					
						
							|  |  |  |   var pkgConf = opts.config; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (!pkgConf.vhostConf) { | 
					
						
							|  |  |  |     pkgConf.vhostConf = compileVhosts(pkgConf.vhostsMap); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   //console.log('req.hostname');
 | 
					
						
							|  |  |  |   //console.log(req.hostname);
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   //console.log(Object.keys(pkgConf.vhostConf.matchesMap));
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // TODO www vs no-www?
 | 
					
						
							|  |  |  |   vhost = pkgConf.vhostConf.matchesMap[req.hostname]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (!vhost) { | 
					
						
							|  |  |  |     pkgConf.vhostConf.patterns.some(function (pkg) { | 
					
						
							| 
									
										
										
										
											2015-11-23 10:22:04 +00:00
										 |  |  |       // TODO this should be done in the compile phase
 | 
					
						
							|  |  |  |       if ('*' === pkg.id[0] && '.' === pkg.id[1]) { | 
					
						
							|  |  |  |         pkg.id = pkg.id.slice(1); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       if (pkg.id === req.hostname.slice(req.hostname.length - pkg.id.length)) { | 
					
						
							| 
									
										
										
										
											2015-11-21 12:36:22 +00:00
										 |  |  |         vhost = pkg; | 
					
						
							|  |  |  |         return true; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (!vhost) { | 
					
						
							|  |  |  |     next(); | 
					
						
							|  |  |  |     return; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // TODO don't modify route here (or in subloaders), modify some other variable instead
 | 
					
						
							|  |  |  |   // TODO precompile RegExps and pre-sort app vs api
 | 
					
						
							|  |  |  |   vhost.pathnames.some(function (routes) { | 
					
						
							|  |  |  |     var pathname = routes.pathname; | 
					
						
							|  |  |  |     if ('/' === pathname) { | 
					
						
							|  |  |  |       pathname = ''; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (!routes._re_app) { | 
					
						
							|  |  |  |       routes._re_app = new RegExp(escapeStringRegexp(pathname) + '(#|\\/|\\?|$)'); | 
					
						
							|  |  |  |       //console.log('[static re]', routes._re_app);
 | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (!routes._re_api) { | 
					
						
							|  |  |  |       // TODO allow for special apis that do not follow convention (.well_known, webfinger, oauth3.html, etc)
 | 
					
						
							|  |  |  |       routes._re_api = new RegExp(escapeStringRegexp(pathname + '/api/') + '([\\w\\.\\-]+)(\\/|\\?|$)'); | 
					
						
							|  |  |  |       //console.log('[api re]', routes._re_api);
 | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (routes._re_app.test(req.url)) { | 
					
						
							|  |  |  |       router = routes; | 
					
						
							|  |  |  |       return true; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // no need to test for api yet as it is a postfix
 | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (!router) { | 
					
						
							|  |  |  |     //console.log('[no router for]', req.url);
 | 
					
						
							|  |  |  |     next(); | 
					
						
							|  |  |  |     return; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-11 05:54:30 +00:00
										 |  |  |   // TODO .well-known can be an API (webfinger, letsencrypt, oauth3)
 | 
					
						
							|  |  |  |   // or static (...???)
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-11-21 12:36:22 +00:00
										 |  |  |   if (!router._re_api.test(req.url)) { | 
					
						
							|  |  |  |     //console.log('[static router]');
 | 
					
						
							|  |  |  |     //console.log(router._re_api, req.url);
 | 
					
						
							| 
									
										
										
										
											2015-12-11 05:54:30 +00:00
										 |  |  |     layerItUp(pkgConf, router, req, res, function (err) { | 
					
						
							|  |  |  |       if (err) { | 
					
						
							|  |  |  |         next(err); | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       if (/\/\.well-known([\/?]|$)/.test(req.url)) { | 
					
						
							|  |  |  |         console.log('[TODO] handle .well-known as API'); | 
					
						
							|  |  |  |         // rewrite api as /api/org.ietf/.well-known ?
 | 
					
						
							|  |  |  |         // pass through simply as /.well-known ?
 | 
					
						
							|  |  |  |         // runApi(opts, router, req, res, next)
 | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       next(); | 
					
						
							|  |  |  |     }); | 
					
						
							| 
									
										
										
										
											2015-11-21 12:36:22 +00:00
										 |  |  |     return; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   //console.log('[api router]', req.url);
 | 
					
						
							|  |  |  |   return runApi(opts, router, req, res, next); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | module.exports.runApi = runApi; | 
					
						
							|  |  |  | module.exports.compileVhosts = compileVhosts; | 
					
						
							|  |  |  | module.exports.mapToApp = mapToApp; |