qcobjects
Version:
QCObjects is an Open-source framework that empowers full-stack developers to make micro-services and micro-frontends into an N-Tier architecture.
307 lines (287 loc) • 13.4 kB
text/typescript
import { IService, TCacheController } from "types";
import { asyncLoad } from "./asyncLoad";
import { ComplexStorageCache } from "./ComplexStorageCache";
import { _DataStringify } from "./DataStringify";
import { logger } from "./Logger";
import { _require_, isBrowser } from "./platform";
import { _top } from "./top";
/**
* Loads a simple component from a template
*
* @author: Jean Machuca <correojean@gmail.com>
* @param service a Service object
*/
export const serviceLoader = function (service:IService, _async = false):Promise<unknown>|undefined {
const _serviceLoaderInBrowser = function (service:IService):Promise<unknown> {
var _promise = new Promise(
function (resolve, reject) {
logger.debug("LOADING SERVICE DATA {{DATA}} FROM {{URL}}".replace("{{DATA}}", _DataStringify(service.data)).replace("{{URL}}", service.url));
const xhr = new XMLHttpRequest();
xhr.withCredentials = service.withCredentials;
const xhrasync = true; // always async because xhr sync is deprecated
xhr.open(service.method, service.url, xhrasync);
for (const header in service.headers) {
try {
if (typeof service.headers[header] !== "function") {
xhr.setRequestHeader(header, service.headers[header]);
}
} catch (e:any) {
logger.debug("Something went wrong when assign the header " + header);
logger.debug(`An error ocurred: ${e}`);
}
}
xhr.onload = function () {
if (xhr.status === 200) {
const response = xhr.responseText;
logger.debug("Data received {{DATA}}".replace("{{DATA}}", _DataStringify(response)));
logger.debug("CREATING SERVICE {{NAME}}".replace("{{NAME}}", service.name));
service.template = response;
if (service.cached && (typeof cache !== "undefined")) {
cache.save(service.name, service.template);
}
if (typeof service.done === "function") {
var standardResponse = {
"request": xhr,
service
};
service.done.call(service, standardResponse);
resolve.call(_promise, standardResponse);
}
} else {
if (typeof service.fail === "function") {
var standardResponse = {
"request": xhr,
service
};
service.fail.call(service, standardResponse);
reject.call(_promise, standardResponse);
}
}
};
const _directLoad = function () {
logger.debug("SENDING THE NORMAL REQUEST ");
try {
xhr.send(_DataStringify(service.data));
} catch (e:any) {
logger.debug("SOMETHING WRONG WITH REQUEST ");
logger.debug(`An error ocurred: ${e}`);
reject.call(_promise, {
request: xhr,
service
});
}
};
if (service.cached) {
var cache = new ComplexStorageCache({
index: service.data,
load() {
_directLoad.call(this);
},
alternate(cacheController:TCacheController) {
if (service.method === "GET") {
service.template = cacheController.cache.getCached(service.name);
if (typeof service.done === "function") {
const standardResponse = {
"request": xhr,
service
};
service.done.call(service, standardResponse);
resolve.call(_promise, standardResponse);
}
} else {
_directLoad();
}
}
});
(_top as any).lastCache = cache;
} else {
_directLoad();
}
return xhr;
}
);
return _promise;
};
const _serviceLoaderInNode = function (service:IService) {
var _promise = new Promise(
function (resolve, reject) {
if (typeof URL === "undefined") {
global.URL = (_require_("url")).URL;
// eslint-disable-next-line no-unused-vars
const URL = global.URL;
}
const serviceURL = new URL(service.url);
var req;
service.useHTTP2 = Object.hasOwn(service, "useHTTP2") && service.useHTTP2;
const captureEvents = function (req:any) {
logger.debug("LOADING SERVICE DATA (non-browser) {{DATA}} FROM {{URL}}".replace("{{DATA}}", _DataStringify(service.data)).replace("{{URL}}", service.url));
let dataXML:any;
const standardResponse = {
"http2Client": client,
"request": req,
service,
"responseHeaders": null
};
if (typeof service.data === "object" && service.data !== null) {
if (service.useHTTP2) {
try {
logger.debug("Sending data...");
const buffer = new Buffer(_DataStringify(service.data));
req.write(buffer);
} catch (e:any) {
logger.debug("It was not possible to send any data");
logger.debug(`An error ocurred: ${e}`);
}
}
}
dataXML = "";
req.on("response", (responseHeaders:any) => {
logger.debug("receiving response...");
standardResponse.responseHeaders = responseHeaders;
/*
for (const name in responseHeaders) {
logger.debug(`${name}: ${responseHeaders[name]}`);
}
*/
dataXML = "";
});
req.on("data", (chunk:any) => {
logger.debug("receiving data...");
// do something with the data
dataXML += "" + chunk.toString();
service.template = dataXML;
});
if (service.useHTTP2) {
req.resume();
}
req.on("end", () => {
logger.debug("ending call...");
service.template = dataXML;
if (Object.hasOwn(service, "useHTTP2") && service.useHTTP2) {
client.destroy();
} else {
req.destroy();
}
service.done.call(service, standardResponse);
resolve.call(_promise, standardResponse);
});
if (service.useHTTP2) {
req.end();
}
};
try {
let requestOptions;
if (service.useHTTP2) {
logger.debug("using http2");
const http2 = _require_("http2");
var client = (http2).connect(serviceURL.origin);
requestOptions = Object.assign({
":method": service.method,
":path": serviceURL.pathname
}, service.options);
requestOptions = Object.assign(requestOptions, service.headers);
req = client.request(requestOptions);
req.setEncoding("utf8");
captureEvents(req);
} else {
if (serviceURL.protocol === "http:") {
const http = _require_("http");
const request = (http).request;
requestOptions = Object.assign({
"url": service.url,
headers: service.headers
}, service.options);
req = request(service.url);
captureEvents(req);
} else if (serviceURL.protocol === "https:") {
const https = _require_("https");
requestOptions = Object.assign({
hostname: serviceURL.hostname,
port: serviceURL.port,
path: serviceURL.pathname,
method: service.method,
headers: service.headers
}, service.options);
const _req_ = (https).request(requestOptions, function (req:any) {
captureEvents(req);
});
_req_.end();
} else {
const e = "Protocol not supported: " + serviceURL.protocol;
logger.debug(e);
throw new Error(e);
}
}
} catch (e:any) {
logger.debug(e);
service.fail.call(service, e);
reject.call(_promise, e);
}
}).catch( (e:any) => {
logger.debug(`Something happened when trying to call the service: ${service.name}. Error: ${e}`);
service.fail.call(service, e);
});
return _promise;
};
const _serviceLoaderMockup = function (service:IService) {
var _promise = new Promise(
function (resolve) {
logger.debug(`Calling mockup service ${service.name} ...`);
const standardResponse = {
"request": null,
service,
"responseHeaders": service.responseHeaders
};
if (typeof service.mockup === "function") {
service.mockup.call(service, standardResponse);
} else {
service.done.call(service, standardResponse);
}
resolve.call(_promise, standardResponse);
});
return _promise;
};
const _serviceLoaderLocal = function (service:IService) {
var _promise = new Promise(
function (resolve) {
logger.debug(`Calling local service ${service.name} ...`);
const standardResponse = {
"request": null,
service,
"responseHeaders": service.responseHeaders
};
if (typeof service.local === "function") {
service.local.call(service, standardResponse);
} else {
service.done.call(service, standardResponse);
}
resolve.call(_promise, standardResponse);
});
return _promise;
};
let _ret_: Promise<unknown>;
switch (service.kind) {
case "rest":
if (isBrowser) {
if (typeof _async !== "undefined" && _async) {
_ret_ = asyncLoad(_serviceLoaderInBrowser, [service, _async]);
} else {
_ret_ = _serviceLoaderInBrowser(service);
}
} else {
_ret_ = _serviceLoaderInNode(service);
}
break;
case "mockup":
_ret_ = _serviceLoaderMockup(service);
break;
case "local":
_ret_ = _serviceLoaderLocal(service);
break;
default:
logger.debug(`The value of the kind property of the service ${service.name} is not valid`);
_ret_ = Promise.resolve();
break;
}
return _ret_;
};