webkooljs
Version:
Webkool javascript library for nodejs
745 lines (661 loc) • 16.4 kB
JavaScript
/*
* Copyright (C) 2005-2020 Haruni SARL.
* Written by Sébastien BUREL <sb@haruni.net>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
"use strict";
var application = exports.application = {
properties: {}
};
class Behavior {
constructor(it) {
for (var i in it) {
this[i] = it[i];
}
}
onComplete(handler, model, query) {
}
onError(handler, model, error) {
application.error("Error in " + handler.url + ": " + error.toString());
}
onLoad(handler, model, query) {
}
onRender(handler, scope) {
if (handler) {
if (handler.contentType == 'application/json' && handler.result)
return JSON.stringify(scope, null, 4);
application.warn("WARNING '" + handler.url + "' has no rendering.");
}
}
onRequest(handler, model, query) {
}
static template(it0) {
var constructor = this;
var result;
if (it0) {
result = function(it1) {
var it = {};
for (var i in it0)
it[i] = it0[i];
if (it1) {
for (i in it1) {
it[i] = it1[i];
}
}
return new constructor(it);
};
result.prototype = this.prototype;
result.template = this.template;
}
else {
result = this;
}
return result;
}
}
exports.Behavior = Behavior;
class Template {
constructor(name, it) {
this.name = name;
for (var i in it) {
this[i] = it[i];
}
}
onRender(handler, scope) {
return '';
}
static bind(name, template) {
templateConstructors[name] = template;
}
static template(it0) {
var constructor = this;
var result;
if (it0) {
result = function(name, it1) {
var it = {};
for (var i in it0)
it[i] = it0[i];
if (it1) {
for (i in it1) {
it[i] = it1[i];
}
}
return new constructor(name, it);
};
result.prototype = this.prototype;
result.template = this.template;
}
else {
result = constructor;
}
return result;
}
}
exports.Template = Template;
class Handler {
constructor(parent, url, query, it) {
for (var i in it)
this[i] = it[i];
this.contentType = this.contentType ? this.contentType : 'text/html; charset=utf-8';
this.status = this.status ? this.status : 200;
this.filter = this.filter ? this.filter : null;
this.parent = null;
this.model = null;
this.query = null;
this._error = null;
this._first = null;
this._last = null;
this._next = null;
this._previous = null;
this._request = null;
this._response = null;
this._target = null;
this._completed = false;
if (parent) {
if (parent._first) {
this._previous = parent._last;
parent._last._next = this;
}
else
parent._first = this;
parent._last = this;
this.parent = parent;
this.model = parent.model;
}
else
this.model = application.getModel();
this.url = url;
this.query = query;
if (this.Behavior) {
this.behavior = new this.Behavior();
}
else {
this.behavior = new Behavior();
}
}
cancel() {
this._error = new Error('Canceled');
this.synchronize();
}
dequeue(child) {
if (!child._error) {
var event = child.url;
var behavior = this.behavior;
if (behavior && (event in behavior))
behavior[event].call(behavior, this, this.model, child.query);
}
var previous = child._previous;
var next = child._next;
if (previous)
previous._next = next;
else
this._first = next;
if (next)
next._previous = previous;
else
this._last = previous;
if (child._error && !this._error)
this.doError(child._error);
this.synchronize();
}
doError(error) {
this._error = error;
}
doRender() {
var buffer, result, behavior = this.behavior;
if (this._response) {
result = behavior.onRender.call(behavior, this, this.result || {});
if (result) {
buffer = new Buffer(result);
this._response.writeHead(this.status, { 'Content-Type': this.contentType, 'Content-Length': buffer.length });
this._response.end(buffer);
}
}
else if (this._target) {
result = behavior.onRender.call(behavior, this, this.result || {});
if (result) {
this._target.innerHTML = result;
behavior.onLoad.call(behavior, this, this.model, this.query);
}
}
else if (this._callback) {
result = behavior.onRender.call(behavior, this, this.result || {});
if (result) {
this._callback(null, this.result || {});
}
}
}
doRequest() {
application.schedule(this.doRequestTimeout.bind(this));
}
doRequestTimeout() {
try {
try {
this.behavior.onRequest(this, this.model, this.query);
}
catch(error) {
this.doError(error);
}
this.synchronize();
}
catch (error) {
application.reportError(this, error);
}
}
getFilter() {
return this.filter;
}
redirect(url, query) {
application.debug("> " + url);
query = query || {};
var target = this.parent;
if (!this._completed)
application.warn("WARNING! '" + this.url + "': redirect to '" + url + "' called before complete.");
if (target)
application.warn("WARNING! '" + this.url + "': redirect to '" + url + "' called.");
var _constructor = application.findHandler(url, query);
var handler = new _constructor(target, url, query);
if ("_request" in this) {
handler._request = this._request;
delete this._request;
}
if ("_response" in this) {
handler._response = this._response;
delete this._response;
}
if ("_target" in this) {
handler._target = this._target;
delete this._target;
}
handler.doRequest();
return handler;
}
request(url, query) {
application.debug("> " + url);
query = query || {};
var parent = this;
if (this._completed) {
parent = this.parent;
if (!parent) {
application.warn("WARNING! '" + this.url + "': request '" + url + "' detached.");
return this.redirect(url, query);
}
}
var _constructor = application.findHandler(url, query);
var handler = new _constructor(parent, url, query);
handler.doRequest();
return handler;
}
serializeQuery(query) {
var items = [], name;
for (name in query) {
items.push(name + '=' + encodeURIComponent(query[name]));
}
return items.join('&');
}
synchronize() {
if (!this._last) {
if (!this._error) {
this._completed = true;
this.behavior.onComplete(this, this.model, this.query);
}
else
this.behavior.onError(this, this.model, this._error);
if (this.parent)
this.parent.dequeue(this);
else {
if (!this._error)
this.doRender();
else
throw this._error;
}
}
}
valueOf() {
return this.result;
}
static bind(url, handler) {
handlerConstructors[url] = handler;
if (handler.filter) {
filters[url] = handler.filter;
}
else {
var filter = handler.prototype.getFilter();
if (filter) {
filters[url] = filter;
}
}
}
static template(it0) {
var constructor = this;
var result;
if (it0) {
result = function(parent, url, query, it1) {
var it = {};
for (var i in it0)
it[i] = it0[i];
if (it1) {
for (i in it1)
it[i] = it1[i];
}
return new constructor(parent, url, query, it);
};
result.prototype = this.prototype;
result.filter = it0.filter;
result.template = this.template;
}
else {
result = constructor;
}
return result;
}
}
exports.Handler = Handler;
class Model {
}
exports.Model = Model;
const Error404Handler = Handler.template({
contentType : "text/html; charset=utf-8",
status : "404",
Behavior: Behavior.template ({
onComplete(handler, model, query) {
handler.result = {
url: handler.url,
};
},
onRender(handler, scope) {
return `<!DOCTYPE html>
<html>
<head>
<title>Error 404</title>
</head>
<body>
<h1>Not Found</h1><p>The requested URL "${scope.url}" was not found on this server.</p>
</body>
</html>`
}
})
});
var handlerConstructors = {};
var templateConstructors = {};
var filters = {};
class Application {
constructor() {
application = exports.application = this;
this.debugging = false;
this.properties = {port: 8080};
this.warning = true;
this.addHandler("/error404", Error404Handler);
}
addHandler(url, handler, filter) {
handlerConstructors[url] = handler;
if (filter)
filters[url] = filter;
else {
filter = handler.prototype.getFilter();
if (filter)
filters[url] = filter;
}
}
addProperty(name, property) {
this.properties[name] = property;
}
addTemplate(name, template) {
templateConstructors[name] = template;
}
debug() {
if (this.debugging) {
console.log.apply(console, arguments);
}
}
error() {
console.log.apply(console, arguments);
}
findHandler(url, query) {
var constructor = handlerConstructors[url];
if (constructor) {
var filter = filters[url];
if (filter && !(filter)(url, query)) {
constructor = undefined;
}
}
if (!constructor) {
for (var url1 in filters) {
if ((filters[url1])(url, query)) {
constructor = handlerConstructors[url1];
break;
}
}
}
if (!constructor) {
this.error("Handler not found '" + url + "'");
constructor = handlerConstructors['/error404'];
}
return constructor;
}
findTemplate(name) {
return templateConstructors[name];
}
getModel() {
if (!this.model)
this.model = new Model();
return this.model;
}
parseQuery(url, query) {
var param, params, i, l;
query = query || {};
if (url) {
params = url.split('&');
l = params.length;
for (i = 0; i < l; i += 1) {
param = params[i].split('=');
if (param.length == 2)
query[param[0]] = decodeURIComponent(param[1]);
}
}
return query;
}
render(url, scope) {
scope = scope || {};
var _constructor = this.findTemplate(url);
if (_constructor) {
var template = new _constructor(url, {});
return template.onRender(undefined, scope);
}
else {
throw 'Template "' + url + '" not found!';
}
}
reportError(handler, error) {
var message = error.toString();
if (error.stack)
message += '\n' + error.stack.toString();
while (handler.parent)
handler = handler.parent;
if (handler) {
if (handler._response)
this.sendErrorToResponse(handler._response, handler.url, message);
else if (handler._target) {
document.title = "500 Internal error";
handler._target.innerHTML = `
<h1>Internal Server Error</h1>
<p>The requested URL "${handler.url}" encounter the following error :
${message.replace(/\n/g, '<br/>')}
</p>`;
}
}
this.error(message);
}
request(url, query) {
query = query || {};
var _constructor = this.findHandler(url, query);
var handler = new _constructor(null /*parent*/, url, query);
handler.doRequest();
return handler;
}
schedule(callback) {
return setImmediate(callback);
}
sendErrorToResponse(response, url, message) {
var html = `<!DOCTYPE HTML>
<html>
<head>
<title>500 Internal Server Error</title>
</head>
<body>
<h1>Internal Server Error</h1>
<p>The requested URL "${url}" encounter and error : ${message.replace(/\n/g, '<br/>')}</p>
</body>
</html>`;
response.writeHead(500, { 'Content-Type': 'text/html; charset=utf-8', 'Content-Length': html.length });
response.end(html);
}
warn() {
if (this.warning) {
console.log.apply(console, arguments);
}
}
}
exports.Application = Application;
class Client extends Application {
constructor() {
super();
if ("onhashchange" in window) {
window.onhashchange = function () {
application.request(window.location.hash.substring(1));
};
}
else {
var storedHash = window.location.hash;
window.setInterval(function () {
if (window.location.hash != storedHash) {
storedHash = window.location.hash;
application.request(window.location.hash.substring(1));
}
}, 100);
}
}
landing(url) {
if (window.location.hash) {
this.request(window.location.hash.substring(1));
}
else {
window.location = window.location.href + "#" + url;
}
}
request(url, query) {
var offset = url.indexOf('?');
if (offset > 0) {
query = this.parseQuery(url.substring(offset + 1), query);
url = url.substring(0, offset);
}
var handler = super.request(url, query);
handler._target = document.body;
}
schedule(callback) {
return setTimeout(callback, 1);
}
}
class Tool extends Application {
constructor(_callback) {
super();
this._callback = _callback;
}
request(url, query) {
var offset = url.indexOf('?');
if (offset > 0) {
query = this.parseQuery(url.substring(offset + 1), query);
url = url.substring(0, offset);
}
var handler = super.request(url, query);
handler._callback = this._callback;
}
reportError(handler, error) {
var message = error.toString();
if (error.stack)
message += '\n' + error.stack.toString();
console.log(message);
this._callback(message, null);
}
}
exports.Tool = Tool;
class Server extends Application {
constructor() {
super();
this.httpServer = undefined;
}
getModel() {
return new Model();
}
httpRequest(request, response) {
var formidable = require('formidable');
var query = {}, url = request.url, handler;
try {
this.debug("# " + url);
if (request.method === 'POST') {
if (url.indexOf('?') > 0)
throw new Error("POST request should not have a query: " + url);
var server = this;
if (request.headers['content-type'] == 'text/plain;charset=UTF-8') {
request.headers['content-type'] = 'application/x-www-form-urlencoded;charset=UTF-8';
}
var form = new formidable.IncomingForm();
form.on('error', function (error) {
throw error;
});
form.on('field', function (field, value) {
query[field] = value;
});
form.onPart = function (part) {
if (!part.filename)
return form.handlePart(part);
var buffer = new Buffer(0);
part.on('data', function (data) {
buffer = Buffer.concat([buffer, data]);
});
part.on('end', function () {
query.data = buffer;
});
part.on('error', function (error) {
throw error;
});
};
form.on('end', function () {
handler = server.request(url, query);
handler._response = response;
handler._request = request;
});
form.parse(request);
}
else {
var offset = url.indexOf('?');
if (offset > 0) {
query = this.parseQuery(url.substring(offset + 1));
url = url.substring(0, offset);
}
handler = this.request(url, query);
handler._response = response;
handler._request = request;
}
}
catch (error) {
var message;
if (error.stack)
message = error.stack.toString();
else
message = error.toString();
this.error(message);
this.sendErrorToResponse(response, request.url, message);
}
}
start() {
var http = require('http'), server = this;
this.httpServer = http.createServer(function(request, response) {
server.httpRequest(request, response);
});
this.debug('server listening on port: ' + this.properties.port);
this.httpServer.listen(this.properties.port);
}
stop() {
if (this.httpServer) {
this.debug('stop listening on port: ' + this.properties.port);
this.httpServer.close();
}
this.httpServer = undefined;
}
}
exports.Server = Server;
function AWSProxy(request) {
return new Promise((resolve, reject ) => {
let application = new Tool((error, message) => {
if (error)
return reject(error);
resolve(message);
});
let model = application.getModel()
model.context = request.requestContext
model.event = request
if (request.body) {
let body = JSON.parse(request.body)
application.request(request.path, body);
}
else if (request.queryStringParameters) {
application.request(request.path, request.queryStringParameters)
}
else {
application.request(request.path, {})
}
});
};
exports.AWSProxy = AWSProxy;