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;
|