walnut.js/lib/api-server.js

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