grid-apps-cmms/js/web-server.js

1123 lines
28 KiB
JavaScript
Raw Normal View History

/** Copyright Stewart Allen -- All Rights Reserved */
2017-04-18 17:09:33 -04:00
Array.prototype.contains = function(v) {
return this.indexOf(v) >= 0;
};
Array.prototype.appendAll = function(a) {
this.push.apply(this,a);
return this;
};
2020-01-21 10:24:56 -05:00
const helper = {
log: function() {
console.log(
moment().format('YYMMDD.HHmmss'),
[...arguments]
.map(v => util.inspect(v, {
maxArrayLength: null,
breakLength: Infinity,
colors: debug,
compact: true,
sorted: true,
depth: null
}))
.join(' ')
);
2020-01-21 10:24:56 -05:00
}
};
2017-04-18 17:09:33 -04:00
function log(o) {
helper.log(o);
2017-04-18 17:09:33 -04:00
}
function promise(resolve, reject) {
return new Promise(resolve, reject);
}
function rval() {
return (Math.round(Math.random()*0xffffffff)).toString(36);
}
function guid() {
return time().toString(36)+rval()+rval()+rval();
}
function time() {
return new Date().getTime();
}
/**
* @param {String[]} path
*/
function mkdirs(path) {
2020-02-09 18:00:02 -05:00
let root = "";
2017-04-18 17:09:33 -04:00
path.forEach(seg => {
if (root) {
root = root + "/" + seg;
} else {
root = seg;
}
lastmod(root) || fs.mkdirSync(root);
});
}
/**
* @param {String} o
*/
function obj2string(o) {
return JSON.stringify(o);
}
function string2obj(s) {
return JSON.parse(s);
}
/**
* sync return mtime for file or 0 for no such file
* @param {String} path
* @returns {number}
*/
function lastmod(path) {
try {
return fs.statSync(path).mtime.getTime();
} catch (e) {
return 0;
}
}
/**
* @param {String} filePath
* @param {Function} fn
* @returns {*}
*/
2020-02-09 18:00:02 -05:00
function getCachedFile(filePath, cpath, fn) {
let cachePath = ".cache/" + cpath.replace(/\//g,'_'),
2017-04-18 17:09:33 -04:00
cached = fileCache[filePath],
now = time();
if (cached) {
if (now - cached.lastcheck > 60000) {
2020-02-09 18:00:02 -05:00
let smod = lastmod(filePath),
2017-04-18 17:09:33 -04:00
cmod = cached.mtime;
if (!smod) throw "missing source file";
if (smod > cmod) cached = null;
cached.lastcheck = now;
}
}
if (!cached) {
2020-02-09 18:00:02 -05:00
let smod = lastmod(filePath),
2017-04-18 17:09:33 -04:00
cmod = lastmod(cachePath),
cacheData;
if (cmod >= smod) {
cacheData = fs.readFileSync(cachePath);
} else {
2020-01-21 10:24:56 -05:00
helper.log({update_cache:filePath});
2017-04-18 17:09:33 -04:00
cacheData = fn(filePath);
fs.writeFileSync(cachePath, cacheData);
}
cached = {
data: cacheData,
mtime: cmod || now,
lastcheck: now
};
fileCache[filePath] = cached;
}
return cached.data;
}
/**
* mangle, cache and concatenate scripts
*/
function prepareScripts() {
code.kiri = concatCode(script.kiri);
code.meta = concatCode(script.meta);
code.work = concatCode(script.work);
code.worker = concatCode(script.worker);
inject.kiri_local = htmlScript(codePrefix, script.kiri);
inject.meta_local = htmlScript(codePrefix, script.meta);
inject.kiri = htmlScript("code/", ["kiri"]);
inject.meta = htmlScript("code/", ["meta"]);
fs.readdir("./web/kiri/filter/FDM", function(err, files) {
filters_fdm = files || filters_fdm;
});
fs.readdir("./web/kiri/filter/CAM", function(err, files) {
filters_cam = files || filters_cam;
});
}
/**
* @param {String} name
* @oaram {Array} list
* @returns {String}
*/
function htmlScript(prefix, list) {
2020-02-09 18:00:02 -05:00
let code = [];
2017-04-18 17:09:33 -04:00
list.forEach(file => {
code.push('\t<script src="/' + prefix + file + '.js/' + ver.VERSION + '"></script>');
});
return code.join("\n");
}
/**
* @param {Array} array
* @returns {String}
*/
function concatCode(array) {
2020-02-09 18:00:02 -05:00
let code = [],
2017-04-18 17:09:33 -04:00
cached,
cachepath,
filepath;
array.forEach((file, index) => {
if (file.charAt(0) === "/") {
filepath = file;
cachepath = "js_mod" + file.replace(/\//g,'_');
array[index] = cachepath.substring(3).replace('.js','');
fileMap[cachepath.replace("js_","js/")] = filepath;
} else {
filepath = codePrefix + file + ".js";
cachepath = filepath;
}
cached = getCachedFile(filepath, cachepath, function(path) {
2018-09-17 12:12:04 -04:00
return minify(filepath);
2017-04-18 17:09:33 -04:00
});
code.push(cached);
});
return code.join('');
}
/**
* @param {String} cookie
* @param {String} [key]
* @returns {String | null}
*/
function getCookieValue(cookie,key) {
if (!cookie) return null;
key = (key || "key") + "=";
2020-02-09 18:00:02 -05:00
let kpos = cookie.lastIndexOf(key);
2017-04-18 17:09:33 -04:00
if (kpos >= 0) {
return cookie.substring(kpos+key.length).split(';')[0];
} else {
return null;
}
}
/**
* @param {String} ip
*/
function isNotLocal(ip) {
return ipLocal.contains(ip) ? null : ip;
}
/**
* @param {Object} req
* @returns {String}
*/
function remoteIP(req) {
2020-02-09 18:00:02 -05:00
let fwd = req.headers['x-forwarded-for'],
sra = req.socket.remoteAddress,
cra = req.connection.remoteAddress,
ip = isNotLocal(fwd) || sra || cra || '',
2017-04-18 17:09:33 -04:00
ipa = ip.split(',');
2020-02-09 18:00:02 -05:00
if (ip === '' || ipa.length > 1) {
helper.log({remote:ipa, fwd, sra, cra});
}
2017-04-18 17:09:33 -04:00
return ipa[0];
}
/**
* @param {Object} res
* @param {number} code
* @param {String} msg
*/
function quickReply(res, code, msg) {
res.writeHead(code);
res.end(msg+"\n");
}
/**
* @param {Object} req
* @param {Object} res
*/
function reply404(req, res) {
log({"404":req.url, ip:req.gs.ip});
res.writeHead(404);
res.end("[404]");
}
/**
* @param {Object} res
* @param {String} url
*/
function redirect(res, url) {
res.writeHead(302, { "Location": url });
res.end();
}
/**
* @param {Array} array
* @param {number} length
* @param {number} timespan
* @returns {number}
*/
2020-02-09 18:00:02 -05:00
function limit(array, length, timespan, inc) {
let now = time(),
add = inc || 1,
2017-04-18 17:09:33 -04:00
limit = 0;
// age out entries older than timespan
while (array.length > 0 && now-array[0] > timespan) {
array.shift();
}
// count elements over the limit
while (array.length > length) {
2020-02-09 18:00:02 -05:00
limit += add;
2017-04-18 17:09:33 -04:00
array.shift();
}
return limit;
}
/**
* @param {Object} req
* @param {Object} res
* @param {Function} next
*/
function setup(req, res, next) {
2020-02-09 18:00:02 -05:00
let parsed = url.parse(req.url, true),
2017-04-18 17:09:33 -04:00
ipaddr = remoteIP(req),
dbikey = db.key(["ip",ipaddr]),
path = parsed.pathname,
time = new Date().getTime(),
rec = ipCache[ipaddr] || {
saved : 0,
first: time,
last: [],
hits: 0,
api: [],
ip: ipaddr,
},
ua = 'unknown';
try {
ua = agent.parse(req.headers['user-agent'] || '');
} catch (e) {
2020-01-21 10:24:56 -05:00
helper.log("ua parse error on : "+req.headers['user-agent']);
2017-04-18 17:09:33 -04:00
}
// cache it
if (rec.saved === 0) {
ipCache[ipaddr] = rec;
if (!rec.host) {
// prevent overlapping lookups
// for the same address
rec.host = 'unknown';
2019-04-30 10:52:45 -04:00
try {
dns.reverse(ipaddr, (err,addr) => {
rec.host = addr;
// if (addr) log({ip:ipaddr, addr:addr});
});
} catch (e) {
2020-01-21 10:24:56 -05:00
helper.log({dns_err: e, ipaddr})
2019-04-30 10:52:45 -04:00
}
2017-04-18 17:09:33 -04:00
}
}
// grid.space request state
req.gs = {
ua: ua,
ip: ipaddr,
iprec: rec,
local: ipLocal.contains(ipaddr),
port: req.socket.address().port,
url: parsed,
path: parsed.pathname,
query: parsed.query,
};
2020-01-20 22:38:02 -05:00
// fixup local addrs
if (req.gs.local) req.gs.ip = "::1";
2017-04-18 17:09:33 -04:00
// track clients & show first instance of IP
rec.last.push(time);
rec.hits++;
// update db ip record
if (time - rec.saved > ipSaveDelay) db.get(dbikey)
.then(dbrec => {
// only on the first pull from disk
if (dbrec && rec.saved === 0) {
rec.first = dbrec.first;
rec.hits += dbrec.hits;
rec.last = dbrec.last.appendAll(rec.last);
rec.api = dbrec.api.appendAll(rec.api);
}
rec.saved = time;
return rec;
})
.then(dbrec => {
if (rec.putTO) clearTimeout(rec.putTO);
rec.putTO = setTimeout(() => {
rec.putTO = null;
db.put(dbikey, dbrec);
}, ipSaveDelay);
})
.catch(error => {
2020-01-21 10:24:56 -05:00
helper.log({dbikey, error});
2017-04-18 17:09:33 -04:00
});
// absolute limit on client requests per minute
2020-02-09 18:00:02 -05:00
let rateinc = req.headers.host ? 1 : 50;
if (limit(rec.last, 300, 60000, rateinc) && !req.gs.local) {
2017-04-18 17:09:33 -04:00
res.writeHead(503);
res.end("rate limited");
return log({rate_limit:ipaddr, len:rec.last.length});
}
if (req.method === 'OPTIONS') {
addCorsHeaders(req, res);
res.end();
} else {
next();
}
}
function addCorsHeaders(req, res) {
res.setHeader('Access-Control-Allow-Credentials', 'true');
res.setHeader('Access-Control-Allow-Headers', 'X-Moto-Ajax, Content-Type');
res.setHeader("Access-Control-Allow-Origin", req.headers['origin'] || '*');
res.setHeader("Allow", "GET,POST,OPTIONS");
}
/**
2018-01-10 20:04:51 -05:00
* meta:moto data storage and retrieval url
*
2017-04-18 17:09:33 -04:00
* @param {Object} req
* @param {Object} res
* @param {Function} next
*/
function handleData(req, res, next) {
addCorsHeaders(req, res);
res.setHeader('Cache-Control', 'private, no-cache, max-age=0');
2020-02-17 15:26:06 -05:00
let tok = req.gs.url.path.split('/'),
2017-04-18 17:09:33 -04:00
muid = req.headers['x-moto-ajax'],
space = tok[2] || null,
version = tok[3],
valid = space && space.length >= 4 && space.length <= 8;
function genKey() {
while (true) {
2020-02-09 18:00:02 -05:00
let k = Math.round(Math.random() * 9999999999).toString(36);
2017-04-18 17:09:33 -04:00
if (k.length >= 4 && k.length <= 8) return k;
}
}
function countKey(space) {
return db.key(['meta/counter',space]);
}
function ownerKey(space) {
return db.key(['meta/owner',muid,'space',space]);
}
function recordKey(space, version) {
return db.key(["meta/space",space,version]);
}
// retrieve latest space data
if (valid && req.method === 'GET' && valid) {
function send(rec, version) {
if (rec) {
res.write(obj2string({space:space,ver:version,rec:rec}));
res.end();
} else {
res.end();
}
}
function retrieve(version) {
return db.get(recordKey(space,version))
.then(record => {
send(record || null, version);
})
}
if (version) {
retrieve(version)
} else {
db.get(countKey(space)).then(version => retrieve(version));
}
return;
} else if (valid && req.method === 'POST') {
2020-02-09 18:00:02 -05:00
let dbOwner = null,
2017-04-18 17:09:33 -04:00
dbVersion = null,
postBody = null,
iprec = req.gs.iprec,
spacein = space,
version = 0,
body = '';
function checkDone() {
if (!(dbVersion && postBody)) return;
// if not owner, assign new space id
if (dbVersion > 1) {
if (!dbOwner) {
space = genKey();
version = 1;
log({forked:space,from:spacein,by:muid});
}
}
// log what we have
log({
space: space,
ver: dbVersion,
uid: muid,
ip: iprec.ip,
hits: iprec.hits,
size: postBody.length
});
if (muid && muid.length > 0) {
level.put(recordKey(space, dbVersion), body);
level.put(ownerKey(space), {ip: iprec.ip, time: time(), ver: dbVersion});
level.put(countKey(space), dbVersion);
}
res.end(obj2string({space: space, ver: dbVersion}));
}
// accumulate post body
req.on('data', data => { body += data });
req.on('end', () => {
postBody = body;
checkDone();
});
// fetch owner and version information
db.get(ownerKey(space))
.then(owner => {
dbOwner = owner;
return db.get(countKey(space));
})
.then(version => {
dbVersion = parseInt(version || "0") + 1;
checkDone();
});
return;
}
}
2018-09-17 12:12:04 -04:00
function minify(path) {
let code = fs.readFileSync(path);
let mini = uglify.minify(code.toString());
if (mini.error) {
console.trace(mini.error);
throw mini.error;
}
return mini.code;
}
2017-04-18 17:09:33 -04:00
/**
* @param {Object} req
* @param {Object} res
* @param {Function} next
*/
function handleJS(req, res, next) {
if (!(req.gs.local || debug || clearOK.indexOf(req.gs.path) >= 0)) {
return reply404(req, res);
}
2017-04-18 17:09:33 -04:00
2020-02-09 18:00:02 -05:00
let spath = req.gs.path.substring(1),
2017-04-18 17:09:33 -04:00
jspos = spath.indexOf(".js"),
fpath = jspos > 0 ? spath.substring(0,jspos+3) : spath,
cached = fileCache[fpath];
if (fileMap[fpath]) {
fpath = fileMap[fpath];
}
2017-04-18 17:09:33 -04:00
fs.stat(fpath, (err, f) => {
if (err || !f) {
return reply404(req, res);
}
2017-04-18 17:09:33 -04:00
2020-02-09 18:00:02 -05:00
let mtime = f.mtime.getTime();
2017-04-18 17:09:33 -04:00
if (!cached || cached.mtime != mtime) {
if (debug) {
2017-04-18 17:09:33 -04:00
fs.readFile(fpath, null, function(err, code) {
if (err) {
return reply404(req,res);
}
2017-04-18 17:09:33 -04:00
serveCode(req, res, fileCache[fpath] = {
clear: true,
mtime: mtime,
code: code
});
});
return;
} else {
2020-02-09 18:00:02 -05:00
let start = new Date().getTime(),
2018-09-17 12:12:04 -04:00
code = minify(fpath),
2017-04-18 17:09:33 -04:00
end = new Date().getTime();
log({minify:fpath, in:f.size, out:code.length, time:(end-start)});
cached = fileCache[fpath] = {
clear: false,
mtime: mtime,
code: code
};
}
}
serveCode(req, res, cached);
});
}
/**
* @param {Object} req
* @param {Object} res
* @param {Function} next
*/
function handleCode(req, res, next) {
2020-02-09 18:00:02 -05:00
let cookie = getCookieValue(req.headers.cookie),
2017-04-18 17:09:33 -04:00
key = req.gs.path.split('/')[2].split('.')[0],
js = code[key];
if (!js) {
return reply404(req, res);
}
2017-04-18 17:09:33 -04:00
addCorsHeaders(req, res);
serveCode(req, res, {
code: js,
mtime: startTime
});
}
function ifModifiedDate(req) {
2020-02-09 18:00:02 -05:00
let ims = req.headers['if-modified-since'];
2017-04-18 17:09:33 -04:00
if (ims) {
// because sys time has a higher resolution than
// seconds converted from IMS header. so give it
// an extra second
return new Date(ims).getTime() + 1000;
}
return 0;
}
/**
* @param {Object} req
* @param {Object} res
* @param {Obejct} code
*/
function serveCode(req, res, code) {
if (code.deny) {
return reply404(req, res);
}
2017-04-18 17:09:33 -04:00
2020-02-09 18:00:02 -05:00
let imd = ifModifiedDate(req);
2017-04-18 17:09:33 -04:00
if (imd && code.mtime <= imd && !code.nocache) {
res.writeHead(304, "Not Modified");
res.end();
return;
}
2020-02-09 18:00:02 -05:00
let cacheControl = code.nocache ?
2017-04-18 17:09:33 -04:00
'private, max-age=0' :
'public, max-age=600';
res.writeHead(200, {
'Content-Type': 'application/javascript',
'Cache-Control': cacheControl,
'Last-Modified': new Date(code.mtime).toGMTString(),
});
res.end(code.code);
}
/**
* @param {Object} req
* @param {Object} res
* @param {Function} next
*/
function rewriteHTML(req, res, next) {
function sendHTML(entry) {
2020-02-09 18:00:02 -05:00
let imd = ifModifiedDate(req);
2017-04-18 17:09:33 -04:00
if (imd && entry.mtime <= imd) {
res.writeHead(304, "Not Modified");
res.end();
return;
}
res.writeHead(200, {
'Last-Modified': new Date(entry.mtime).toGMTString(),
'Cache-Control': 'public, max-age=600',
'Content-Type': 'text/html'
});
res.write(entry.data);
res.end();
}
if (req.url.indexOf(".html") > 0) {
2020-02-09 18:00:02 -05:00
let local = req.gs.local,
2017-04-18 17:09:33 -04:00
key = local ? "local_" + req.url : req.url,
path = "web" + req.gs.path,
mtime = lastmod(path),
cached = fileCache[key],
replaced = false,
magic;
if (mtime === 0) return next();
if (cached && mtime === cached.mtime) return sendHTML(cached);
fs.readFile(path, (err, data) => {
if (!data) return next();
data = data.toString();
injectKeys.forEach(key => {
if (replaced) return;
magic = "<!--{"+key+"}-->";
if (data.indexOf(magic) > 0) {
data = data.replace(magic, inject[local ? key + "_local" : key]);
replaced = true;
}
});
if (replaced) {
cached = fileCache[key] = {
data: data,
mtime: mtime
};
sendHTML(cached);
} else {
next();
}
});
} else {
next();
}
}
/* *********************************************
* Setup / Global
********************************************* */
2020-02-09 18:00:02 -05:00
let debug = false,
nolocal = false
2017-04-18 17:09:33 -04:00
port = 8080,
args = process.argv.slice(2);
args.forEach((arg, index) => {
switch (arg) {
case 'nolocal': nolocal = true; break;
case 'debug': debug = true; break;
2017-04-18 17:09:33 -04:00
case 'port': port = process.argv[index+3]; break;
}
});
2020-02-09 18:00:02 -05:00
let ver = require('../js/license.js'),
2017-04-18 17:09:33 -04:00
fs = require('fs'),
url = require('url'),
dns = require('dns'),
util = require('util'),
valid = require('validator'),
agent = require('express-useragent'),
spawn = require('child_process').spawn,
level = require('level')('./persist', {valueEncoding:"json"}),
2017-04-18 17:09:33 -04:00
https = require('https'),
2020-01-21 10:24:56 -05:00
moment = require('moment'),
2018-09-17 12:12:04 -04:00
uglify = require('uglify-es'),
2017-04-18 17:09:33 -04:00
connect = require('connect'),
request = require('request'),
serveStatic = require('serve-static'),
compression = require('compression')(),
querystring = require('querystring'),
ipLocal = nolocal ? [] : ["127.0.0.1", "::1", "::ffff:127.0.0.1"],
2017-04-18 17:09:33 -04:00
currentDir = process.cwd(),
ipSaveDelay = 2000,
startTime = time(),
codePrefix = "js/",
fileCache = {},
fileMap = {},
2017-04-18 17:09:33 -04:00
filters_fdm = [],
filters_cam = [],
modPaths = [],
ipCache = {},
clearOK = [
'/js/ext-three.js'
],
2017-04-18 17:09:33 -04:00
script = {
kiri : [
"license",
"ext-clip",
"ext-tween",
"ext-fsave",
"add-array",
"add-three",
"geo",
"geo-debug",
"geo-render",
"geo-point",
"geo-slope",
"geo-line",
"geo-bounds",
"geo-polygon",
"geo-polygons",
2019-01-21 10:42:24 -05:00
"geo-gyroid",
2017-04-18 17:09:33 -04:00
"moto-kv",
"moto-ajax",
"moto-ctrl",
"moto-space",
"moto-load-stl",
"moto-db",
"moto-ui",
2019-04-11 10:47:58 -04:00
"kiri-lang",
2019-05-02 15:09:54 -04:00
"kiri-fill",
2017-04-18 17:09:33 -04:00
"kiri-db",
"kiri-slice",
"kiri-slicer",
"kiri-driver-fdm",
"kiri-driver-cam",
"kiri-driver-laser",
"kiri-pack",
"kiri-layer",
"kiri-widget",
"kiri-print",
"kiri-codec",
"kiri-work",
"kiri"
2017-04-18 17:09:33 -04:00
],
meta : [
"license",
"ext-tween",
"ext-fsave",
"add-array",
"add-three",
"moto-kv",
"moto-ajax",
"moto-ctrl",
"moto-space",
"moto-load-stl",
"moto-db",
"moto-ui",
"kiri-db",
"meta"
],
work : [
"license",
2018-09-19 11:14:55 -04:00
"ext-n3d",
2017-04-18 17:09:33 -04:00
"ext-clip",
"add-array",
"add-three",
"geo",
"geo-debug",
"geo-point",
"geo-slope",
"geo-line",
"geo-bounds",
"geo-polygon",
"geo-polygons",
2019-01-21 10:42:24 -05:00
"geo-gyroid",
2019-04-11 10:47:58 -04:00
"kiri-lang",
2019-05-02 15:09:54 -04:00
"kiri-fill",
2017-04-18 17:09:33 -04:00
"kiri-slice",
"kiri-slicer",
"kiri-driver-fdm",
"kiri-driver-cam",
"kiri-driver-laser",
"kiri-pack",
"kiri-widget",
"kiri-print",
"kiri-codec"
],
worker : [
"license",
"kiri-worker"
2017-04-18 17:09:33 -04:00
]
},
code = {},
inject = {},
injectKeys = ["kiri", "meta"];
/* *********************************************
* Promises-based leveldb interface
********************************************* */
const db = {
// --------
key: arr => arr.join("/"),
// --------
get: key => {
if (Array.isArray(key)) key = db.key(key);
return promise((resolve,reject) => {
level.get(key,(err,record) => {
resolve(record,err);
});
});
},
// --------
put: (key, value) => {
if (Array.isArray(key)) key = db.key(key);
return promise((resolve,reject) => {
level.put(key,value,(err) => {
if (err) reject(err);
else resolve();
});
});
},
// --------
del: key => {
return promise((resolve,reject) => {
level.del(key, (err) => {
if (err) reject(err);
else resolve();
});
});
}
};
/* *********************************************
* REST API (extensible in modules)
********************************************* */
const api = {
rateLimit: (req, res, next) => {
req.gs.iprec.api.push(time());
// allow 60 calls in 60 seconds
if (limit(req.gs.iprec.api, 60, 60000) && !req.gs.local) {
quickReply(res, 503, "rate limited");
2020-01-26 22:15:11 -05:00
try { log({rate_limit_api: req.gs.ip, url: req.gs.url}); } catch (e) { helper.log(e) }
2017-04-18 17:09:33 -04:00
} else {
next();
}
},
"filters-fdm": (req, res, next) => {
2018-02-20 16:30:50 -05:00
res.setHeader("Content-Type", "application/javascript");
2017-04-18 17:09:33 -04:00
res.end(obj2string(filters_fdm));
},
"filters-cam": (req, res, next) => {
2018-02-20 16:30:50 -05:00
res.setHeader("Content-Type", "application/javascript");
2017-04-18 17:09:33 -04:00
res.end(obj2string(filters_cam));
}
};
/* *********************************************
* Dispatch Helpers
********************************************* */
// dispatch for path prefixs
function prepath(pre) {
function handle(req, res, next) {
pre.uid = pre.uid || guid();
req.ppi = req.ppi || {};
2020-02-09 18:00:02 -05:00
let path = req.gs.path,
2017-04-18 17:09:33 -04:00
key, fn, i = req.ppi[pre.uid] || 0;
while (i < pre.length) {
key = pre[i][0];
fn = pre[i++][1];
if (path.indexOf(key) === 0) {
return fn(req, res, () => {
req.ppi[pre.uid] = i;
handle(req, res, next);
});
}
}
next();
}
return handle;
}
// dispatch full fixed paths
function fullpath(map) {
return (req, res, next) => {
2020-02-09 18:00:02 -05:00
let fn = map[req.gs.path];
2017-04-18 17:09:33 -04:00
if (fn) fn(req, res, next);
else next();
};
}
// dispatch full paths based on a prefix and a function map
function fixedmap(prefix, map) {
return (req, res, next) => {
2020-02-09 18:00:02 -05:00
let path = req.gs.path;
2017-04-18 17:09:33 -04:00
if (path.indexOf(prefix) != 0) return next();
2020-02-09 18:00:02 -05:00
let fn = map[path.substring(prefix.length)];
2017-04-18 17:09:33 -04:00
if (fn) fn(req, res, next);
else next();
};
}
// HTTP 302 redirect
function redir(path) {
return (req, res, next) => redirect(res, path);
}
// mangle request path
function remap(path) {
return (req, res, next) => {
req.url = req.gs.path = path;
next();
}
}
// load module and call returned function with helper object
function initModule(file, dir) {
2020-01-21 10:24:56 -05:00
helper.log({module:file});
require(file)({
2017-04-18 17:09:33 -04:00
api: api,
const: {
script: script,
rootdir: currentDir,
moddir: dir,
2020-01-26 17:04:47 -05:00
args: args,
debug: debug
2017-04-18 17:09:33 -04:00
},
util: {
2020-01-21 10:24:56 -05:00
log: helper.log,
2017-04-18 17:09:33 -04:00
time: time,
guid: guid,
mkdirs: mkdirs,
lastmod: lastmod,
obj2string: obj2string,
string2obj: string2obj,
getCookieValue: getCookieValue
},
db: {
api: db,
level: level
},
inject: {
kiri: file => {
script.kiri.splice(0, 0, dir + "/" + file);
}
},
2017-04-18 17:09:33 -04:00
path: {
any: arg => { modPaths.push(arg) },
pre: arg => { modPaths.push(prepath(arg)) },
map: arg => { modPaths.push(fixedmap(arg)) },
full: arg => { modPaths.push(fullpath(arg)) },
static: (root, pre) => { modPaths.push(handleStatic(root, pre)) },
2017-04-18 17:09:33 -04:00
code: (endpoint, path) => {
if (debug) {
2017-04-18 17:09:33 -04:00
code[endpoint] = fs.readFileSync(path);
} else {
2018-09-17 12:12:04 -04:00
code[endpoint] = minify(path);
2017-04-18 17:09:33 -04:00
}
2020-01-27 12:24:49 -05:00
},
redir: redir,
remap: remap
2017-04-18 17:09:33 -04:00
},
handler: {
2020-01-26 16:49:16 -05:00
addCORS: addCorsHeaders,
static: handleStatic,
2017-04-18 17:09:33 -04:00
redirect: redirect,
reply404: reply404,
reply: quickReply
}
})
}
function handleStatic(root, pre) {
let statServe = serveStatic(root);
return function(req, res, next) {
if (pre) {
if (req.url.indexOf(pre) === 0) {
if (req.url === pre) {
req.url = "/";
} else {
req.url = req.url.substring(pre.length);
}
return statServe(req, res, next);
}
return next();
}
statServe(req, res, next);
};
}
// add static assets to be served
function addStatic(dir, pre) {
2020-01-21 10:24:56 -05:00
helper.log({static:dir});
modPaths.push(handleStatic(dir, pre));
}
// either add module assets to path or require(init.js)
function loadModule(dir) {
2020-01-27 17:49:31 -05:00
if (lastmod(dir + "/.ignore")) {
return;
}
const modjs = dir + "/init.js";
lastmod(modjs) ? initModule(modjs, dir) : addStatic(dir);
2017-04-18 17:09:33 -04:00
}
/* *********************************************
* Start it up
********************************************* */
// load modules
lastmod("mod") && fs.readdirSync(currentDir + "/mod").forEach(dir => {
const fullpath = currentDir + "/mod/" + dir;
if (dir.charAt(0) === '.') return;
2020-01-26 13:21:13 -05:00
const stats = fs.lstatSync(fullpath);
if (!(stats.isDirectory() || stats.isSymbolicLink())) return;
loadModule(fullpath);
2017-04-18 17:09:33 -04:00
});
// create cache dir if missing
2018-02-25 17:49:50 -05:00
lastmod(".cache") || mkdirs([".cache"]);
2017-04-18 17:09:33 -04:00
// precache responses
prepareScripts();
// create web handler chain
2020-02-09 18:00:02 -05:00
let handler = connect().use(setup);
2017-04-18 17:09:33 -04:00
// add path handlers registered by modules
modPaths.forEach(fn => {
handler = handler.use(fn);
});
// add the rest of the handler chain
handler.use(fullpath({
"/meta/index.html" : redir("/meta/"),
"/kiri/index.html" : redir("/kiri/"),
"/kiri)" : redir("/kiri/"),
"/meta" : remap("/meta/index.html"),
"/meta/" : remap("/meta/index.html"),
"/kiri" : remap("/kiri/index.html"),
"/kiri/" : remap("/kiri/index.html")
}))
.use(prepath([
[ "/space", redir("/meta/")],
[ "/api/", api.rateLimit ],
[ "/data/", handleData ],
[ "/code/", handleCode ],
[ "/js/", handleJS ]
]))
.use(fixedmap("/api/", api))
.use(rewriteHTML)
.use(compression)
.use(handleStatic(currentDir + "/web/"))
2017-04-18 17:09:33 -04:00
.listen(port);
2020-01-21 10:24:56 -05:00
helper.log("------------------------------------------");
helper.log({port, debug, nolocal, version: ver.VERSION});