forked from coolaj86/walnut.js
		
	
		
			
				
	
	
		
			204 lines
		
	
	
		
			6.8 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			204 lines
		
	
	
		
			6.8 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| 'use strict';
 | |
| 
 | |
| var escapeStringRegexp = require('escape-string-regexp');
 | |
| //var apiHandlers = {};
 | |
| 
 | |
| function getApi(conf, pkgConf, pkgDeps, packagedApi) {
 | |
|   var PromiseA = pkgDeps.Promise;
 | |
|   var path = require('path');
 | |
|   var pkgpath = path.join(pkgConf.apipath, packagedApi.id/*, (packagedApi.api.version || '')*/);
 | |
| 
 | |
|   // 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 {
 | |
|       packagedApi._apipkg = require(path.join(pkgpath, 'package.json'));
 | |
|       packagedApi._apiname = packagedApi._apipkg.name;
 | |
|       if (packagedApi._apipkg.walnut) {
 | |
|         pkgpath += '/' + packagedApi._apipkg.walnut;
 | |
|       }
 | |
|       promise = PromiseA.resolve(require(pkgpath).create(pkgConf, pkgDeps, myApp));
 | |
|     } catch(e) {
 | |
|       reject(e);
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     promise.then(function () {
 | |
|       // TODO give pub/priv pair for app and all public keys
 | |
|       // packagedApi._api = require(pkgpath).create(pkgConf, pkgDeps, myApp);
 | |
|       packagedApi._api = require('express-lazy')();
 | |
|       packagedApi._api_app = myApp;
 | |
| 
 | |
|       packagedApi._api.use('/', require('./oauth3').attachOauth3);
 | |
| 
 | |
|       // TODO fix backwards compat
 | |
| 
 | |
|       // /api/com.example.foo (no change)
 | |
|       packagedApi._api.use('/', packagedApi._api_app);
 | |
| 
 | |
|       // /api/com.example.foo => /api
 | |
|       packagedApi._api.use('/', function (req, res, next) {
 | |
|         var priorUrl = req.url;
 | |
|         req.url = '/api' + req.url.slice(('/api/' + packagedApi.id).length);
 | |
|         // console.log('api mangle 3:', req.url);
 | |
|         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);
 | |
|         // console.log(packagedApi._api_app.toString());
 | |
|         packagedApi._api_app(req, res, next);
 | |
|       });
 | |
| 
 | |
|       resolve(packagedApi._api);
 | |
|     }, reject);
 | |
|   });
 | |
| }
 | |
| 
 | |
| function loadApi(conf, pkgConf, pkgDeps, packagedApi) {
 | |
|   function handlePromise(p) {
 | |
|     return p.then(function (api) {
 | |
|       packagedApi._api = api;
 | |
|       return api;
 | |
|     });
 | |
|   }
 | |
| 
 | |
|   if (!packagedApi._promise_api) {
 | |
|     packagedApi._promise_api = getApi(conf, pkgConf, pkgDeps, packagedApi);
 | |
|   }
 | |
| 
 | |
|   return handlePromise(packagedApi._promise_api);
 | |
| }
 | |
| 
 | |
| function runApi(opts, router, req, res, next) {
 | |
|   var path = require('path');
 | |
|   var pkgConf = opts.config;
 | |
|   var pkgDeps = opts.deps;
 | |
|   //var Services = opts.Services;
 | |
|   var packagedApi;
 | |
|   var pathname;
 | |
| 
 | |
|   // TODO compile packagesMap
 | |
|   // TODO people may want to use the framework in a non-framework way (i.e. to conceal the module name)
 | |
|   router.packagedApis.some(function (_packagedApi) {
 | |
|     // console.log('[DEBUG _packagedApi.id]', _packagedApi.id);
 | |
|     pathname = router.pathname;
 | |
|     if ('/' === pathname) {
 | |
|       pathname = '';
 | |
|     }
 | |
|     // TODO allow for special apis that do not follow convention (.well_known, webfinger, oauth3.html, etc)
 | |
|     if (!_packagedApi._api_re) {
 | |
|       _packagedApi._api_re = new RegExp(escapeStringRegexp(pathname + '/api/' + _packagedApi.id) + '\/([\\w\\.\\-]+)(\\/|\\?|$)');
 | |
|       //console.log('[api re 2]', _packagedApi._api_re);
 | |
|     }
 | |
|     if (_packagedApi._api_re.test(req.url)) {
 | |
|       packagedApi = _packagedApi;
 | |
|       return true;
 | |
|     }
 | |
|   });
 | |
| 
 | |
|   if (!packagedApi) {
 | |
|     console.log("[ODD] no api for '" + req.url + "'");
 | |
|     next();
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // 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: (path.join(req.hostname, 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', {
 | |
|     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: packagedApi.domain.id
 | |
|   });
 | |
|   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
 | |
|   //
 | |
| 
 | |
|   if (packagedApi._api) {
 | |
|     packagedApi._api(req, res, next);
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // console.log("[DEBUG pkgpath]", pkgConf.apipath, packagedApi.id);
 | |
|   loadApi(opts.conf, pkgConf, pkgDeps, packagedApi).then(function (api) {
 | |
|     api(req, res, next);
 | |
|   }, function (err) {
 | |
|     console.error('[App Promise Error]');
 | |
|     next(err);
 | |
|   });
 | |
| }
 | |
| 
 | |
| module.exports.runApi = runApi;
 |