actionhero
Version:
actionhero.js is a multi-transport API Server with integrated cluster capabilities and delayed tasks
324 lines (323 loc) • 12.5 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ActionProcessor = void 0;
const config_1 = require("./../modules/config");
const log_1 = require("../modules/log");
const utils_1 = require("../modules/utils");
const dotProp = require("dot-prop");
const os_1 = require("os");
const index_1 = require("../index");
class ActionProcessor {
constructor(connection) {
this.connection = connection;
this.action = null;
this.toProcess = true;
this.toRender = true;
this.messageId = connection.messageId || 0;
this.params = Object.assign({}, connection.params);
this.missingParams = [];
this.validatorErrors = [];
this.actionStartTime = null;
this.actionTemplate = null;
this.working = false;
this.response = {};
this.duration = null;
this.actionStatus = null;
this.session = {};
}
incrementTotalActions(count = 1) {
this.connection.totalActions = this.connection.totalActions + count;
}
incrementPendingActions(count = 1) {
this.connection.pendingActions = this.connection.pendingActions + count;
if (this.connection.pendingActions < 0) {
this.connection.pendingActions = 0;
}
}
getPendingActionCount() {
return this.connection.pendingActions;
}
async completeAction(status) {
let error = null;
this.actionStatus = String(status);
if (status instanceof Error) {
error =
typeof config_1.config.errors.genericError === "function"
? await config_1.config.errors.genericError(this, status)
: status;
}
else if (status === "server_shutting_down") {
error = await config_1.config.errors.serverShuttingDown(this);
}
else if (status === "too_many_requests") {
error = await config_1.config.errors.tooManyPendingActions(this);
}
else if (status === "unknown_action") {
error = await config_1.config.errors.unknownAction(this);
}
else if (status === "unsupported_server_type") {
error = await config_1.config.errors.unsupportedServerType(this);
}
else if (status === "missing_params") {
error = await config_1.config.errors.missingParams(this, this.missingParams);
}
else if (status === "validator_errors") {
error = await config_1.config.errors.invalidParams(this, this.validatorErrors);
}
else if (status) {
error = status;
}
if (error && typeof error === "string") {
error = new Error(error);
}
if (error && (typeof this.response === "string" || !this.response.error)) {
if (typeof this.response === "string" || Array.isArray(this.response)) {
this.response = error.toString();
}
else {
this.response.error = error;
}
}
this.incrementPendingActions(-1);
this.duration = new Date().getTime() - this.actionStartTime;
this.working = false;
this.logAction(error);
return this;
}
logAction(error) {
let logLevel = "info";
if (this.actionTemplate && this.actionTemplate.logLevel) {
logLevel = this.actionTemplate.logLevel;
}
const filteredParams = utils_1.utils.filterObjectForLogging(this.params);
const logLine = {
to: this.connection.remoteIP,
action: this.action,
params: JSON.stringify(filteredParams),
duration: this.duration,
error: "",
};
if (error) {
if (error instanceof Error) {
logLine.error = error.toString();
}
else {
try {
logLine.error = JSON.stringify(error);
}
catch (e) {
logLine.error = String(error);
}
}
}
log_1.log(`[ action @ ${this.connection.type} ]`, logLevel, logLine);
if (error === null || error === void 0 ? void 0 : error.stack) {
error.stack.split(os_1.EOL).map((l) => log_1.log(` ! ${l}`, "error"));
}
}
async preProcessAction() {
const processorNames = index_1.api.actions.globalMiddleware.slice(0);
if (this.actionTemplate.middleware) {
this.actionTemplate.middleware.forEach(function (m) {
processorNames.push(m);
});
}
for (const i in processorNames) {
const name = processorNames[i];
if (typeof index_1.api.actions.middleware[name].preProcessor === "function") {
await index_1.api.actions.middleware[name].preProcessor(this);
}
}
}
async postProcessAction() {
const processorNames = index_1.api.actions.globalMiddleware.slice(0);
if (this.actionTemplate.middleware) {
this.actionTemplate.middleware.forEach((m) => {
processorNames.push(m);
});
}
for (const i in processorNames) {
const name = processorNames[i];
if (typeof index_1.api.actions.middleware[name].postProcessor === "function") {
await index_1.api.actions.middleware[name].postProcessor(this);
}
}
}
reduceParams(schemaKey) {
let inputs = this.actionTemplate.inputs || {};
let params = this.params;
if (schemaKey) {
inputs = this.actionTemplate.inputs[schemaKey].schema;
params = this.params[schemaKey];
}
const inputNames = Object.keys(inputs) || [];
if (config_1.config.general.disableParamScrubbing !== true) {
for (const p in params) {
if (index_1.api.params.globalSafeParams.indexOf(p) < 0 &&
inputNames.indexOf(p) < 0) {
delete params[p];
}
}
}
}
prepareStringMethod(method) {
const cmdParts = method.split(".");
const cmd = cmdParts.shift();
if (cmd !== "api") {
throw new Error("cannot operate on a method outside of the api object");
}
return dotProp.get(index_1.api, cmdParts.join("."));
}
async validateParam(props, params, key, schemaKey) {
// default
if (params[key] === undefined && props.default !== undefined) {
if (typeof props.default === "function") {
params[key] = await props.default.call(index_1.api, params[key], this);
}
else {
params[key] = props.default;
}
}
// formatter
if (params[key] !== undefined && props.formatter !== undefined) {
if (!Array.isArray(props.formatter)) {
props.formatter = [props.formatter];
}
for (const i in props.formatter) {
const formatter = props.formatter[i];
if (typeof formatter === "function") {
params[key] = await formatter.call(index_1.api, params[key], this);
}
else {
const method = this.prepareStringMethod(formatter);
params[key] = await method.call(index_1.api, params[key], this);
}
}
}
// validator
if (params[key] !== undefined && props.validator !== undefined) {
if (!Array.isArray(props.validator)) {
props.validator = [props.validator];
}
for (const j in props.validator) {
const validator = props.validator[j];
let validatorResponse;
try {
if (typeof validator === "function") {
validatorResponse = await validator.call(index_1.api, params[key], this);
}
else {
const method = this.prepareStringMethod(validator);
validatorResponse = await method.call(index_1.api, params[key], this);
}
// validator function returned nothing; assume param is OK
if (validatorResponse === null || validatorResponse === undefined) {
return;
}
// validator returned something that was not `true`
if (validatorResponse !== true) {
if (validatorResponse === false) {
this.validatorErrors.push(new Error(`Input for parameter "${key}" failed validation!`));
}
else {
this.validatorErrors.push(validatorResponse);
}
}
}
catch (error) {
// validator threw an error
this.validatorErrors.push(error);
}
}
}
// required
if (props.required === true) {
if (config_1.config.general.missingParamChecks.indexOf(params[key]) >= 0) {
let missingKey = key;
if (schemaKey) {
missingKey = `${schemaKey}.${missingKey}`;
}
this.missingParams.push(missingKey);
}
}
}
async validateParams(schemaKey) {
let inputs = this.actionTemplate.inputs || {};
let params = this.params;
if (schemaKey) {
inputs = this.actionTemplate.inputs[schemaKey].schema;
params = this.params[schemaKey];
}
for (const key in inputs) {
const props = inputs[key];
await this.validateParam(props, params, key, schemaKey);
if (props.schema && params[key]) {
this.reduceParams(key);
await this.validateParams(key);
}
}
}
lockParams() {
this.params = Object.freeze(this.params);
}
async processAction() {
this.actionStartTime = new Date().getTime();
this.working = true;
this.incrementTotalActions();
this.incrementPendingActions();
this.action = this.params.action;
if (index_1.api.actions.versions[this.action]) {
if (!this.params.apiVersion) {
this.params.apiVersion =
index_1.api.actions.versions[this.action][index_1.api.actions.versions[this.action].length - 1];
}
this.actionTemplate =
index_1.api.actions.actions[this.action][this.params.apiVersion];
}
if (index_1.api.running !== true) {
return this.completeAction("server_shutting_down");
}
if (this.getPendingActionCount() > config_1.config.general.simultaneousActions) {
return this.completeAction("too_many_requests");
}
if (!this.action || !this.actionTemplate) {
return this.completeAction("unknown_action");
}
if (this.actionTemplate.blockedConnectionTypes &&
this.actionTemplate.blockedConnectionTypes.indexOf(this.connection.type) >= 0) {
return this.completeAction("unsupported_server_type");
}
return this.runAction();
}
async runAction() {
try {
await this.preProcessAction();
await this.reduceParams();
await this.validateParams();
this.lockParams();
}
catch (error) {
return this.completeAction(error);
}
if (this.missingParams.length > 0) {
return this.completeAction("missing_params");
}
if (this.validatorErrors.length > 0) {
return this.completeAction("validator_errors");
}
if (this.toProcess === true) {
try {
await this.actionTemplate.run(this);
await this.postProcessAction();
return this.completeAction();
}
catch (error) {
return this.completeAction(error);
}
}
else {
return this.completeAction();
}
}
}
exports.ActionProcessor = ActionProcessor;