484 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			484 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| 'use strict';
 | |
| 
 | |
| module.exports = function (myDeps, conf, overrideHttp) {
 | |
|   var express = require('express');
 | |
|   //var finalhandler = require('finalhandler');
 | |
|   var serveStatic = require('serve-static');
 | |
|   var serveIndex = require('serve-index');
 | |
|   //var assetServer = serveStatic(opts.assetsPath);
 | |
|   var path = require('path');
 | |
|   //var wellKnownServer = serveStatic(path.join(opts.assetsPath, 'well-known'));
 | |
| 
 | |
|   var serveStaticMap = {};
 | |
|   var serveIndexMap = {};
 | |
|   var content = conf.content;
 | |
|   //var server;
 | |
|   var serveInit;
 | |
|   var app;
 | |
|   var request;
 | |
| 
 | |
|   /*
 | |
|   function _reloadWrite(data, enc, cb) {
 | |
|     // /*jshint validthis: true */ /*
 | |
|     if (this.headersSent) {
 | |
|       this.__write(data, enc, cb);
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     if (!/html/i.test(this.getHeader('Content-Type'))) {
 | |
|       this.__write(data, enc, cb);
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     if (this.getHeader('Content-Length')) {
 | |
|       this.setHeader('Content-Length', this.getHeader('Content-Length') + this.__my_addLen);
 | |
|     }
 | |
| 
 | |
|     this.__write(this.__my_livereload);
 | |
|     this.__write(data, enc, cb);
 | |
|   }
 | |
|   */
 | |
| 
 | |
| 
 | |
|   function createServeInit() {
 | |
|     var PromiseA = require('bluebird');
 | |
|     var OAUTH3 = require('../packages/assets/org.oauth3');
 | |
|     require('../packages/assets/org.oauth3/oauth3.domains.js');
 | |
|     require('../packages/assets/org.oauth3/oauth3.dns.js');
 | |
|     require('../packages/assets/org.oauth3/oauth3.tunnel.js');
 | |
|     OAUTH3._hooks = require('../packages/assets/org.oauth3/oauth3.node.storage.js');
 | |
|     var fs = PromiseA.promisifyAll(require('fs'));
 | |
|     var ownersPath = path.join(__dirname, '..', 'var', 'owners.json');
 | |
| 
 | |
|     var scmp = require('scmp');
 | |
|     request = request || PromiseA.promisify(require('request'));
 | |
| 
 | |
|     var owners = {
 | |
|       all: function () {
 | |
|         var owners;
 | |
|         try {
 | |
|           owners = require(ownersPath);
 | |
|         } catch(e) {
 | |
|           owners = {};
 | |
|         }
 | |
| 
 | |
|         return PromiseA.resolve(Object.keys(owners).map(function (key) {
 | |
|           var owner = owners[key];
 | |
|           owner.id = key;
 | |
|           return owner;
 | |
|         }));
 | |
|       }
 | |
|     , get: function (id) {
 | |
|         var me = this;
 | |
| 
 | |
|         return me.all().then(function (owners) {
 | |
|           return owners.filter(function (owner) {
 | |
|             return scmp(id, owner.id);
 | |
|           })[0];
 | |
|         });
 | |
|       }
 | |
|     , exists: function (id) {
 | |
|         var me = this;
 | |
| 
 | |
|         return me.get(id).then(function (owner) {
 | |
|           return !!owner;
 | |
|         });
 | |
|       }
 | |
|     , set: function (id, obj) {
 | |
|         var owners;
 | |
|         try {
 | |
|           owners = require(ownersPath);
 | |
|         } catch(e) {
 | |
|           owners = {};
 | |
|         }
 | |
|         obj.id = id;
 | |
|         owners[id] = obj;
 | |
| 
 | |
|         return fs.mkdirAsync(path.dirname(ownersPath)).catch(function (err) {
 | |
|           if (err.code !== 'EEXIST') {
 | |
|             console.error('failed to mkdir', path.dirname(ownersPath), err.toString());
 | |
|           }
 | |
|         }).then(function () {
 | |
|           return fs.writeFileAsync(ownersPath, JSON.stringify(owners), 'utf8');
 | |
|         });
 | |
|       }
 | |
|     };
 | |
| 
 | |
|     myDeps.PromiseA = PromiseA;
 | |
|     myDeps.OAUTH3 = OAUTH3;
 | |
|     myDeps.storage = { owners: owners };
 | |
|     myDeps.recase = require('recase').create({});
 | |
|     myDeps.request = request;
 | |
|     myDeps.api = {
 | |
|       // TODO move loopback to oauth3.api('tunnel:loopback')
 | |
|       loopback: function (deps, session, opts2) {
 | |
|         var crypto = require('crypto');
 | |
|         var token = crypto.randomBytes(16).toString('hex');
 | |
|         var keyAuthorization = crypto.randomBytes(16).toString('hex');
 | |
|         var nonce = crypto.randomBytes(16).toString('hex');
 | |
| 
 | |
|         // TODO set token and keyAuthorization to /.well-known/cloud-challenge/:token
 | |
|         return request({
 | |
|           method: 'POST'
 | |
|         , url: 'https://oauth3.org/api/org.oauth3.tunnel/loopback'
 | |
|         , json: {
 | |
|             address: opts2.address
 | |
|           , port: opts2.port
 | |
|           , token: token
 | |
|           , keyAuthorization: keyAuthorization
 | |
|           , servername: opts2.servername
 | |
|           , nonce: nonce
 | |
|           , scheme: 'https'
 | |
|           , iat: Date.now()
 | |
|           }
 | |
|         }).then(function (result) {
 | |
|           // TODO this will always fail at the moment
 | |
|           console.log('loopback result:');
 | |
|           return result;
 | |
|         });
 | |
|       }
 | |
|     , tunnel: function (deps, session) {
 | |
|         // TODO save session to config and turn tunnel on
 | |
|         var OAUTH3 = deps.OAUTH3;
 | |
|         var url = require('url');
 | |
|         var providerUri = session.token.aud;
 | |
|         var urlObj = url.parse(OAUTH3.url.normalize(session.token.azp));
 | |
|         var oauth3 = OAUTH3.create(urlObj, {
 | |
|           providerUri: providerUri
 | |
|         , session: session
 | |
|         });
 | |
| 
 | |
|         return oauth3.setProvider(providerUri).then(function () {
 | |
|           /*
 | |
|           return oauth3.api('domains.list').then(function (domains) {
 | |
|             var domainsMap = {};
 | |
|             domains.forEach(function (d) {
 | |
|               if (!d.device) {
 | |
|                 return;
 | |
|               }
 | |
|               if (d.device !== conf.device.hostname) {
 | |
|                 return;
 | |
|               }
 | |
|               domainsMap[d.name] = true;
 | |
|             });
 | |
|           */
 | |
| 
 | |
|             //console.log('domains matching hostname', Object.keys(domainsMap));
 | |
|             //console.log('device', conf.device);
 | |
|             return oauth3.api('tunnel.token', {
 | |
|               data: {
 | |
|                 // filter to all domains that are on this device
 | |
|                 //domains: Object.keys(domainsMap)
 | |
|                 device: {
 | |
|                   hostname: conf.device.hostname
 | |
|                 , id: conf.device.uid || conf.device.id
 | |
|                 }
 | |
|               }
 | |
|             }).then(function (result) {
 | |
|               console.log('got a token from the tunnel server?');
 | |
|               result.owner = session.id;
 | |
|               return deps.tunneler.add(result);
 | |
|             });
 | |
|           /*
 | |
|           });
 | |
|           */
 | |
|         });
 | |
|       }
 | |
|     };
 | |
| 
 | |
|     return require('../packages/apis/com.daplie.goldilocks').create(myDeps, conf);
 | |
|   }
 | |
| 
 | |
|   app = express();
 | |
| 
 | |
|   var Sites = {
 | |
|     add: function (sitesMap, site) {
 | |
|       if (!sitesMap[site.$id]) {
 | |
|         sitesMap[site.$id] = site;
 | |
|       }
 | |
| 
 | |
|       if (!site.paths) {
 | |
|         site.paths = [];
 | |
|       }
 | |
|       if (!site.paths._map) {
 | |
|         site.paths._map = {};
 | |
|       }
 | |
|       site.paths.forEach(function (path) {
 | |
| 
 | |
|         site.paths._map[path.$id] = path;
 | |
| 
 | |
|         if (!path.modules) {
 | |
|           path.modules = [];
 | |
|         }
 | |
|         if (!path.modules._map) {
 | |
|           path.modules._map = {};
 | |
|         }
 | |
|         path.modules.forEach(function (module) {
 | |
| 
 | |
|           path.modules._map[module.$id] = module;
 | |
|         });
 | |
|       });
 | |
|     }
 | |
|   };
 | |
| 
 | |
|   var opts = overrideHttp || conf.http;
 | |
|   if (!opts.defaults) {
 | |
|     opts.defaults = {};
 | |
|   }
 | |
|   if (!opts.global) {
 | |
|     opts.global = {};
 | |
|   }
 | |
|   if (!opts.sites) {
 | |
|     opts.sites = [];
 | |
|   }
 | |
|   opts.sites._map = {};
 | |
|   opts.sites.forEach(function (site) {
 | |
| 
 | |
|     Sites.add(opts.sites._map, site);
 | |
|   });
 | |
| 
 | |
|   function mapMap(el, i, arr) {
 | |
|     arr._map[el.$id] = el;
 | |
|   }
 | |
|   opts.global.modules._map = {};
 | |
|   opts.global.modules.forEach(mapMap);
 | |
|   opts.global.paths._map = {};
 | |
|   opts.global.paths.forEach(function (path, i, arr) {
 | |
|     mapMap(path, i, arr);
 | |
|     //opts.global.paths._map[path.$id] = path;
 | |
|     path.modules._map = {};
 | |
|     path.modules.forEach(mapMap);
 | |
|   });
 | |
|   opts.sites.forEach(function (site) {
 | |
|     site.paths._map = {};
 | |
|     site.paths.forEach(function (path, i, arr) {
 | |
|       mapMap(path, i, arr);
 | |
|       //site.paths._map[path.$id] = path;
 | |
|       path.modules._map = {};
 | |
|       path.modules.forEach(mapMap);
 | |
|     });
 | |
|   });
 | |
|   opts.defaults.modules._map = {};
 | |
|   opts.defaults.modules.forEach(mapMap);
 | |
|   opts.defaults.paths._map = {};
 | |
|   opts.defaults.paths.forEach(function (path, i, arr) {
 | |
|     mapMap(path, i, arr);
 | |
|     //opts.global.paths._map[path.$id] = path;
 | |
|     path.modules._map = {};
 | |
|     path.modules.forEach(mapMap);
 | |
|   });
 | |
| 
 | |
|   return app.use('/', function (req, res, next) {
 | |
|     if (!req.headers.host) {
 | |
|       next(new Error('missing HTTP Host header'));
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     if (0 === req.url.indexOf('/api/com.daplie.goldilocks/')) {
 | |
|       if (!serveInit) {
 | |
|         serveInit = createServeInit();
 | |
|       }
 | |
|     }
 | |
|     if ('/api/com.daplie.goldilocks/init' === req.url) {
 | |
|       serveInit.init(req, res);
 | |
|       return;
 | |
|     }
 | |
|     if ('/api/com.daplie.goldilocks/tunnel' === req.url) {
 | |
|       serveInit.tunnel(req, res);
 | |
|       return;
 | |
|     }
 | |
|     if ('/api/com.daplie.goldilocks/config' === req.url) {
 | |
|       serveInit.config(req, res);
 | |
|       return;
 | |
|     }
 | |
|     if ('/api/com.daplie.goldilocks/request' === req.url) {
 | |
|       serveInit.request(req, res);
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     if (content && '/' === req.url) {
 | |
|       // res.setHeader('Content-Type', 'application/octet-stream');
 | |
|       res.end(content);
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     //var done = finalhandler(req, res);
 | |
|     var host = req.headers.host;
 | |
|     var hostname = (host||'').split(':')[0].toLowerCase();
 | |
| 
 | |
|     console.log('opts.global', opts.global);
 | |
|     var sites = [ opts.global || null, opts.sites._map[hostname] || null, opts.defaults || null ];
 | |
|     var loadables = {
 | |
|       serve: function (config, hostname, pathname, req, res, next) {
 | |
|         var originalUrl = req.url;
 | |
|         var dirpaths = config.paths.slice(0);
 | |
| 
 | |
|         function nextServe() {
 | |
|           var dirname = dirpaths.pop();
 | |
|           if (!dirname) {
 | |
|             req.url = originalUrl;
 | |
|             next();
 | |
|             return;
 | |
|           }
 | |
| 
 | |
|           console.log('[serve]', req.url, hostname, pathname, dirname);
 | |
|           dirname = path.resolve(conf.cwd, dirname.replace(/:hostname/, hostname));
 | |
|           if (!serveStaticMap[dirname]) {
 | |
|             serveStaticMap[dirname] = serveStatic(dirname);
 | |
|           }
 | |
| 
 | |
|           serveStaticMap[dirname](req, res, nextServe);
 | |
|         }
 | |
| 
 | |
|         req.url = req.url.substr(pathname.length - 1);
 | |
|         nextServe();
 | |
|       }
 | |
|     , indexes: function (config, hostname, pathname, req, res, next) {
 | |
|         var originalUrl = req.url;
 | |
|         var dirpaths = config.paths.slice(0);
 | |
| 
 | |
|         function nextIndex() {
 | |
|           var dirname = dirpaths.pop();
 | |
|           if (!dirname) {
 | |
|             req.url = originalUrl;
 | |
|             next();
 | |
|             return;
 | |
|           }
 | |
| 
 | |
|           console.log('[indexes]', req.url, hostname, pathname, dirname);
 | |
|           dirname = path.resolve(conf.cwd, dirname.replace(/:hostname/, hostname));
 | |
|           if (!serveStaticMap[dirname]) {
 | |
|             serveIndexMap[dirname] = serveIndex(dirname);
 | |
|           }
 | |
|           serveIndexMap[dirname](req, res, nextIndex);
 | |
|         }
 | |
| 
 | |
|         req.url = req.url.substr(pathname.length - 1);
 | |
|         nextIndex();
 | |
|       }
 | |
|     , app: function (config, hostname, pathname, req, res, next) {
 | |
|         //var appfile = path.resolve(/*process.cwd(), */config.path.replace(/:hostname/, hostname));
 | |
|         var appfile = config.path.replace(/:hostname/, hostname);
 | |
|         var app = require(appfile);
 | |
|         app(req, res, next);
 | |
|       }
 | |
|     };
 | |
| 
 | |
|     function runModule(module, hostname, pathname, modulename, req, res, next) {
 | |
|       if (!loadables[modulename]) {
 | |
|         next(new Error("no module '" + modulename + "' found"));
 | |
|         return;
 | |
|       }
 | |
|       loadables[modulename](module, hostname, pathname, req, res, next);
 | |
|     }
 | |
| 
 | |
|     function iterModules(modules, hostname, pathname, req, res, next) {
 | |
|       console.log('modules');
 | |
|       console.log(modules);
 | |
|       var modulenames = Object.keys(modules._map);
 | |
| 
 | |
|       function nextModule() {
 | |
|         var modulename = modulenames.pop();
 | |
|         if (!modulename) {
 | |
|           next();
 | |
|           return;
 | |
|         }
 | |
| 
 | |
|         console.log('modules', modules);
 | |
|         runModule(modules._map[modulename], hostname, pathname, modulename, req, res, nextModule);
 | |
|       }
 | |
| 
 | |
|       nextModule();
 | |
|     }
 | |
| 
 | |
|     function iterPaths(site, hostname, req, res, next) {
 | |
|       console.log('site', hostname);
 | |
|       console.log(site);
 | |
|       var pathnames = Object.keys(site.paths._map);
 | |
|       console.log('pathnames', pathnames);
 | |
|       pathnames = pathnames.filter(function (pathname) {
 | |
|         // TODO ensure that pathname has trailing /
 | |
|         return (0 === req.url.indexOf(pathname));
 | |
|         //return req.url.match(pathname);
 | |
|       });
 | |
|       pathnames.sort(function (a, b) {
 | |
|         return b.length - a.length;
 | |
|       });
 | |
|       console.log('pathnames', pathnames);
 | |
| 
 | |
|       function nextPath() {
 | |
|         var pathname = pathnames.shift();
 | |
|         if (!pathname) {
 | |
|           next();
 | |
|           return;
 | |
|         }
 | |
| 
 | |
|         console.log('iterPaths', hostname, pathname, req.url);
 | |
|         iterModules(site.paths._map[pathname].modules, hostname, pathname, req, res, nextPath);
 | |
|       }
 | |
| 
 | |
|       nextPath();
 | |
|     }
 | |
| 
 | |
|     function nextSite() {
 | |
|       console.log('hostname', hostname, sites);
 | |
|       var site;
 | |
|       if (!sites.length) {
 | |
|         next(); // 404
 | |
|         return;
 | |
|       }
 | |
|       site = sites.shift();
 | |
|       if (!site) {
 | |
|         nextSite();
 | |
|         return;
 | |
|       }
 | |
|       iterPaths(site, hostname, req, res, nextSite);
 | |
|     }
 | |
| 
 | |
|     nextSite();
 | |
| 
 | |
|     /*
 | |
|     function serveStaticly(server) {
 | |
|       function serveTheStatic() {
 | |
|         server.serve(req, res, function (err) {
 | |
|           if (err) { return done(err); }
 | |
|           server.index(req, res, function (err) {
 | |
|             if (err) { return done(err); }
 | |
|             req.url = req.url.replace(/\/assets/, '');
 | |
|             assetServer(req, res, function  () {
 | |
|               if (err) { return done(err); }
 | |
|               req.url = req.url.replace(/\/\.well-known/, '');
 | |
|               wellKnownServer(req, res, done);
 | |
|             });
 | |
|           });
 | |
|         });
 | |
|       }
 | |
| 
 | |
|       if (server.expressApp) {
 | |
|         server.expressApp(req, res, serveTheStatic);
 | |
|         return;
 | |
|       }
 | |
| 
 | |
|       serveTheStatic();
 | |
|     }
 | |
| 
 | |
|     if (opts.livereload) {
 | |
|       res.__my_livereload = '<script src="//'
 | |
|         + (host || opts.sites[0].name).split(':')[0]
 | |
|         + ':35729/livereload.js?snipver=1"></script>';
 | |
|       res.__my_addLen = res.__my_livereload.length;
 | |
| 
 | |
|       // TODO modify prototype instead of each instance?
 | |
|       res.__write = res.write;
 | |
|       res.write = _reloadWrite;
 | |
|     }
 | |
| 
 | |
|     console.log('hostname:', hostname, opts.sites[0].paths);
 | |
| 
 | |
|     addServer(hostname);
 | |
|     server = hostsMap[hostname] || hostsMap[opts.sites[0].name];
 | |
|     serveStaticly(server);
 | |
|     */
 | |
|   });
 | |
| };
 |