From 9d157c12a2143609c958c8243aa0651e810ec83a Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Fri, 19 May 2017 07:40:20 +0000 Subject: [PATCH] add shortcut to loading any static app --- lib/main.js | 87 +++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 77 insertions(+), 10 deletions(-) diff --git a/lib/main.js b/lib/main.js index eb9dcfd..495a556 100644 --- a/lib/main.js +++ b/lib/main.js @@ -69,6 +69,8 @@ module.exports.create = function (app, xconfx, apiFactories, apiDeps) { function loadHandler(name) { return function handler(req, res, next) { + // path.join('packages/pages', 'com.daplie.hello') // package name (used as file-link) + // path.join('packages/pages', 'domain.tld#hello') // dynamic exact url match var packagepath = path.join(xconfx.staticpath, name); return fs.lstatAsync(packagepath).then(function (stat) { @@ -84,8 +86,18 @@ module.exports.create = function (app, xconfx, apiFactories, apiDeps) { return disallowNonFiles; } + // path.join('packages/pages', 'domain.tld#hello') // a file (not folder) which contains a list of roots + // may look like this: + // + // com.daplie.hello + // tld.domain.app + // + // this is basically a 'recursive mount' to signify that 'com.daplie.hello' should be tried first + // and if no file matches that 'tld.domain.app' may be tried next, and so on + // + // this may well become a .htaccess type of situation allowing for redirects and such return fs.readFileAsync(packagepath, 'utf8').then(function (text) { - // TODO allow cascading + // TODO allow cascading multiple lines text = text.trim().split(/\n/)[0]; // TODO rerun the above, disallowing link-style (or count or memoize to prevent infinite loop) @@ -95,6 +107,8 @@ module.exports.create = function (app, xconfx, apiFactories, apiDeps) { return securityError; } + // instead of actually creating new instances of express.static + // this same effect could be managed by internally re-writing the url (and restoring it) return express.static(packagepath); }); }, function (/*err*/) { @@ -109,22 +123,27 @@ module.exports.create = function (app, xconfx, apiFactories, apiDeps) { } function staticHelper(appId, opts) { + console.log('[staticHelper]', appId); // TODO inter-process cache expirey // TODO add to xconfx.staticpath xconfx.staticpath = path.join(__dirname, '..', '..', 'packages', 'pages'); + + // Reads in each of the static apps as 'nodes' return fs.readdirAsync(xconfx.staticpath).then(function (nodes) { if (opts && opts.clear) { localCache.statics = {}; } - // longest to shortest + // Order from longest (index length - 1) to shortest (index 0) function shortToLong(a, b) { return b.length - a.length; } nodes.sort(shortToLong); nodes.forEach(function (name) { + console.log('[all apps]', name); if (!localCache.statics[name]) { + console.log('[load this app]', name); localCache.statics[name] = { handler: loadHandler(name), createdAt: Date.now() }; } }); @@ -151,10 +170,7 @@ module.exports.create = function (app, xconfx, apiFactories, apiDeps) { }); } - function serveStatic(req, res, next) { - // If we get this far we can be pretty confident that - // the domain was already set up because it's encrypted - var appId = req.hostname + req.url.replace(/\/+/g, '#').replace(/#$/, ''); + function serveStaticHelper(appId, opts, req, res, next) { var appIdParts = appId.split('#'); var appIdPart; @@ -168,13 +184,14 @@ module.exports.create = function (app, xconfx, apiFactories, apiDeps) { require('./no-www').scrubTheDub(req, res); return; } + /* if (!redirectives && config.redirects) { redirectives = require('./hostname-redirects').compile(config.redirects); } */ - // TODO assets.example.com/sub/assets/com.example.xyz/ + // If this looks like an API, we shouldn't be here if (/^api\./.test(req.hostname) && /\/api(\/|$)/.test(req.url)) { // supports api.example.com/sub/app/api/com.example.xyz/ if (!apiApp) { @@ -199,8 +216,18 @@ module.exports.create = function (app, xconfx, apiFactories, apiDeps) { return; } + /* + // TODO assets.example.com/sub/assets/com.example.xyz/ + if (/^assets\./.test(req.hostname) && /\/assets(\/|$)/.test(req.url)) { + ... + } + */ + + // There may be some app folders named 'apple.com', 'apple.com#foo', and 'apple.com#foo#bar' + // Here we're sorting an appId broken into parts like [ 'apple.com', 'foo', 'bar' ] + // and wer're checking to see if this is perhaps '/' of 'apple.com/foo/bar' or '/foo/bar' of 'apple.com', etc while (appIdParts.length) { - // TODO needs IPC to expire cache + // TODO needs IPC to expire cache when an API says the app mounts have been updated appIdPart = appIdParts.join('#'); if (localCache.statics[appIdPart]) { break; @@ -211,18 +238,58 @@ module.exports.create = function (app, xconfx, apiFactories, apiDeps) { } if (!appIdPart || !localCache.statics[appIdPart]) { - return staticHelper(appId).then(function () { - localCache.statics[appId].handler(req, res, next); + console.log('[serveStaticHelper] appId', appId); + return staticHelper(appId).then(function (webapp) { + //localCache.statics[appId].handler(req, res, next); + webapp.handler(req, res, next); }); } + console.log('[serveStaticHelper] appIdPart', appIdPart); + if (opts && opts.rewrite && -1 !== req.url.indexOf(appIdPart.replace(/#/, '/').replace(/\/$/, ''))) { + console.log('[staticHelper ReWrite]', req.url.slice(req.url.indexOf(appIdPart.replace(/#/, '/').replace(/\/$/, '')) + appIdPart.replace(/\/$/, '').length)); + req.url = req.url.slice(req.url.indexOf(appIdPart.replace(/#/, '/').replace(/\/$/, '')) + appIdPart.replace(/\/$/, '').length); + if (0 !== req.url.indexOf('/')) { + req.url = '/' + req.url; + } + } localCache.statics[appIdPart].handler(req, res, next); if (Date.now() - localCache.statics[appIdPart].createdAt > (5 * 60 * 1000)) { staticHelper(appId, { clear: true }); } } + function serveStatic(req, res, next) { + // We convert the URL that was sent in the browser bar from + // 'https://domain.tld/foo/bar' to 'domain.tld#foo#bar' + var appId = req.hostname + req.url.replace(/\/+/g, '#').replace(/#$/, ''); + serveStaticHelper(appId, null, req, res, next); + } + + function serveApps(req, res, next) { + var appId = req.url.slice(1).replace(/\/+/g, '#').replace(/#$/, ''); + + if (/^apps\./.test(req.hostname)) { + appId = appId.replace(/^apps#/, ''); + } else if (/\bapps#/.test(appId)) { + appId = appId.replace(/.*\bapps#/, ''); + } else { + next(); + return; + } + + console.log('[serveApps] appId', appId); + serveStaticHelper(appId, { rewrite: true }, req, res, next); + } + + // TODO set header 'X-ExperienceId: domain.tld/sub/path' + // This would let an app know whether its app is 'domain.tld' with a path of '/sub/path' + // or if its app is 'domain.tld/sub' with a path of '/path' + + // TODO handle assets.example.com/sub/assets/com.example.xyz/ + app.use('/', serveStatic); + app.use('/', serveApps); return PromiseA.resolve(); };