160 lines
		
	
	
		
			4.2 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			160 lines
		
	
	
		
			4.2 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| 'use strict';
 | |
| 
 | |
| // TODO handle static app urls?
 | |
| // NOTE rejecting non-api urls should happen before this
 | |
| module.exports.create = function (conf, deps/*, Services*/) {
 | |
|   var PromiseA = deps.Promise;
 | |
|   var app = deps.app;
 | |
|   var express = deps.express;
 | |
|   var escapeStringRegexp = require('escape-string-regexp');
 | |
|   var vhostsMap = conf.vhostsMap;
 | |
| 
 | |
|   function getApi(route) {
 | |
|     // TODO don't modify route, modify some other variable instead
 | |
| 
 | |
|     var path = require('path');
 | |
|     // TODO needs some version stuff (which would also allow hot-loading of updates)
 | |
|     // TODO version could be tied to sha256sum
 | |
|     var pkgpath = path.join(conf.apipath, (route.api.package || route.api.id), (route.api.version || ''));
 | |
| 
 | |
|     return new PromiseA(function (resolve, reject) {
 | |
|       var myApp;
 | |
|       var ursa;
 | |
| 
 | |
|       try {
 | |
|         // 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 = express();
 | |
|         myApp.disable('x-powered-by');
 | |
|         if (app.get('trust proxy')) {
 | |
|           myApp.set('trust proxy', app.get('trust proxy'));
 | |
|         }
 | |
|         if (!conf.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');
 | |
|           conf.keypair = ursa.createPrivateKey(conf.privkey, 'ascii');
 | |
|           conf.pubkey = ursa.createPublicKey(conf.pubkey, 'ascii'); //conf.keypair.toPublicKey();
 | |
|         }
 | |
|         // TODO give pub/priv pair for app and all public keys
 | |
|         route.route = require(pkgpath).create(conf, deps, myApp);
 | |
|       } catch(e) {
 | |
|         reject(e);
 | |
|         return;
 | |
|       }
 | |
| 
 | |
|       resolve(route.route);
 | |
|     });
 | |
|   }
 | |
| 
 | |
|   function api(req, res, next) {
 | |
|     var apps; 
 | |
| 
 | |
|     if (!vhostsMap[req.hostname]) {
 | |
|       // TODO keep track of match-only vhosts, such as '*.example.com',
 | |
|       // separate from exact matches
 | |
|       next(new Error("this domain is not registered"));
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     vhostsMap[req.hostname].pathnames.some(function (route) {
 | |
|       var pathname = route.pathname;
 | |
|       if ('/' === pathname) {
 | |
|         pathname = '/api';
 | |
|       }
 | |
|       if (-1 === pathname.indexOf('/api')) {
 | |
|         // TODO needs namespace for current api
 | |
|         pathname = '/api' + pathname;
 | |
|       }
 | |
|       // pathname += '.local';
 | |
| 
 | |
|       if (!route.re) {
 | |
|         route.re = new RegExp(escapeStringRegexp(pathname) + '(#|\\/|\\?|$)');
 | |
|       }
 | |
|       // re.test("/api")
 | |
|       // re.test("/api?")
 | |
|       // re.test("/api/")
 | |
|       // re.test("/api/foo")
 | |
|       // re.test("/apifoo") // false
 | |
|       if (route.re.test(req.url)) {
 | |
|         // make a copy
 | |
|         apps = route.apps.slice(0);
 | |
|         return true;
 | |
|       }
 | |
|     });
 | |
| 
 | |
|     if (!apps) {
 | |
|       next();
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     function nextify(err) {
 | |
|       var route;
 | |
| 
 | |
|       if (err) {
 | |
|         next(err);
 | |
|         return;
 | |
|       }
 | |
|       
 | |
|       // shortest to longest
 | |
|       //route = apps.pop();
 | |
|       // longest to shortest
 | |
|       route = apps.shift();
 | |
|       if (!route) {
 | |
|         next();
 | |
|         return;
 | |
|       }
 | |
| 
 | |
|       if (route.route) {
 | |
|         if (route.route.then) {
 | |
|           route.route.then(function (expressApp) {
 | |
|             expressApp(req, res, nextify);
 | |
|           });
 | |
|           return;
 | |
|         }
 | |
|         route.route(req, res, nextify);
 | |
|         return;
 | |
|       }
 | |
| 
 | |
|       if (route._errored) {
 | |
|         nextify(new Error("couldn't load api"));
 | |
|         return;
 | |
|       }
 | |
| 
 | |
|       if (!route.api) {
 | |
|         console.error('missing route:', req.url);
 | |
|         nextify(new Error("no api available for this route"));
 | |
|         return;
 | |
|       }
 | |
| 
 | |
|       return getApi(route).then(function (expressApp) {
 | |
|         try {
 | |
|           expressApp(req, res, nextify);
 | |
|           route.route = expressApp;
 | |
|         } catch(e) {
 | |
|           route._errored = true;
 | |
|           console.error('[App Load Error]');
 | |
|           nextify(new Error("couldn't load api"));
 | |
|         }
 | |
| 
 | |
|         return expressApp;
 | |
|       }, function (err) {
 | |
|         console.error('[App Promise Error]');
 | |
|         nextify(err);
 | |
|       });
 | |
|     }
 | |
| 
 | |
|     nextify();
 | |
|   }
 | |
| 
 | |
|   return {
 | |
|     api: api
 | |
|   };
 | |
| };
 |