0z2i6v3u5t 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.devcontainer/devcontainer.json +4 -0
- package/.devcontainer/setup.sh +11 -0
- package/.dockerignore +2 -0
- package/.github/CONTRIBUTING.md +52 -0
- package/.github/FUNDING.yml +3 -0
- package/.github/ISSUE_TEMPLATE/bug_report.yml +59 -0
- package/.github/ISSUE_TEMPLATE/config.yml +5 -0
- package/.github/ISSUE_TEMPLATE/feature_request.yml +43 -0
- package/.github/dependabot.yml +17 -0
- package/.github/workflows/codeql.yml +76 -0
- package/.github/workflows/publish_docs.yml +25 -0
- package/.github/workflows/test.yml +78 -0
- package/.nvmrc +1 -0
- package/.prettierignore +1 -0
- package/.prettierrc +1 -0
- package/.vscode/launch.json +42 -0
- package/CODE_OF_CONDUCT.md +76 -0
- package/Dockerfile +17 -0
- package/LICENSE +21 -0
- package/README.md +3 -0
- package/SECURITY.md +5 -0
- package/__tests__/actions/cacheTest.ts +58 -0
- package/__tests__/actions/randomNumber.ts +26 -0
- package/__tests__/actions/recursiveAction.ts +16 -0
- package/__tests__/actions/sleepTest.ts +24 -0
- package/__tests__/actions/status.ts +17 -0
- package/__tests__/actions/swagger.ts +76 -0
- package/__tests__/actions/validationTest.ts +63 -0
- package/__tests__/cli/cli.ts +126 -0
- package/__tests__/core/api.ts +632 -0
- package/__tests__/core/cache.ts +400 -0
- package/__tests__/core/chatRoom.ts +589 -0
- package/__tests__/core/cli.ts +349 -0
- package/__tests__/core/cluster.ts +132 -0
- package/__tests__/core/config.ts +78 -0
- package/__tests__/core/errors.ts +112 -0
- package/__tests__/core/log.ts +23 -0
- package/__tests__/core/middleware.ts +427 -0
- package/__tests__/core/plugins/partialPlugin.ts +94 -0
- package/__tests__/core/plugins/withPlugin.ts +88 -0
- package/__tests__/core/plugins/withoutPlugin.ts +81 -0
- package/__tests__/core/process.ts +42 -0
- package/__tests__/core/specHelper.ts +330 -0
- package/__tests__/core/staticFile/compression.ts +99 -0
- package/__tests__/core/staticFile/staticFile.ts +180 -0
- package/__tests__/core/tasks/customQueueFunction.ts +67 -0
- package/__tests__/core/tasks/fullWorkerFlow.ts +199 -0
- package/__tests__/core/tasks/tasks.ts +605 -0
- package/__tests__/integration/browser.ts +133 -0
- package/__tests__/integration/ioredis-mock.ts +194 -0
- package/__tests__/integration/sendBuffer.ts +97 -0
- package/__tests__/integration/sendFile.ts +24 -0
- package/__tests__/integration/sharedFingerprint.ts +82 -0
- package/__tests__/integration/taskFlow.ts +110 -0
- package/__tests__/jest.ts +5 -0
- package/__tests__/modules/action.ts +103 -0
- package/__tests__/modules/config.ts +19 -0
- package/__tests__/modules/utils/ensureNoTsHeaderOrSpecFiles.ts +24 -0
- package/__tests__/servers/web/allowedRequestHosts.ts +88 -0
- package/__tests__/servers/web/enableMultiples.ts +83 -0
- package/__tests__/servers/web/fileUpload.ts +79 -0
- package/__tests__/servers/web/jsonp.ts +57 -0
- package/__tests__/servers/web/nonMultiples.ts +83 -0
- package/__tests__/servers/web/rawBody.ts +208 -0
- package/__tests__/servers/web/returnErrorCodes.ts +55 -0
- package/__tests__/servers/web/routes/deepRoutes.ts +96 -0
- package/__tests__/servers/web/routes/routes.ts +579 -0
- package/__tests__/servers/web/routes/veryDeepRoutes.ts +92 -0
- package/__tests__/servers/web/web.ts +1031 -0
- package/__tests__/servers/websocket.ts +795 -0
- package/__tests__/tasks/runAction.ts +37 -0
- package/__tests__/template.ts.example +20 -0
- package/__tests__/testCliCommands/hello.ts +44 -0
- package/__tests__/testPlugin/public/plugin.html +1 -0
- package/__tests__/testPlugin/src/actions/pluginAction.ts +14 -0
- package/__tests__/testPlugin/src/bin/hello.ts +22 -0
- package/__tests__/testPlugin/src/initializers/pluginInitializer.ts +17 -0
- package/__tests__/testPlugin/src/tasks/pluginTask.ts +15 -0
- package/__tests__/testPlugin/tsconfig.json +10 -0
- package/__tests__/utils/utils.ts +492 -0
- package/app.json +23 -0
- package/bin/deploy-docs +39 -0
- package/client/ActionheroWebsocketClient.js +277 -0
- package/docker-compose.yml +73 -0
- package/package.json +24 -0
- package/public/chat.html +194 -0
- package/public/css/cosmo.css +12 -0
- package/public/favicon.ico +0 -0
- package/public/index.html +115 -0
- package/public/javascript/.gitkeep +0 -0
- package/public/linkedSession.html +80 -0
- package/public/logo/actionhero-small.png +0 -0
- package/public/logo/actionhero.png +0 -0
- package/public/pixel.gif +0 -0
- package/public/simple.html +2 -0
- package/public/swagger.html +32 -0
- package/public/websocketLoadTest.html +322 -0
- package/src/actions/cacheTest.ts +58 -0
- package/src/actions/createChatRoom.ts +20 -0
- package/src/actions/randomNumber.ts +17 -0
- package/src/actions/recursiveAction.ts +13 -0
- package/src/actions/sendFile.ts +12 -0
- package/src/actions/sleepTest.ts +40 -0
- package/src/actions/status.ts +73 -0
- package/src/actions/swagger.ts +155 -0
- package/src/actions/validationTest.ts +36 -0
- package/src/bin/actionhero.ts +225 -0
- package/src/bin/methods/actions/list.ts +30 -0
- package/src/bin/methods/console.ts +26 -0
- package/src/bin/methods/generate/action.ts +58 -0
- package/src/bin/methods/generate/cli.ts +51 -0
- package/src/bin/methods/generate/initializer.ts +54 -0
- package/src/bin/methods/generate/plugin.ts +57 -0
- package/src/bin/methods/generate/server.ts +38 -0
- package/src/bin/methods/generate/task.ts +68 -0
- package/src/bin/methods/generate.ts +176 -0
- package/src/bin/methods/task/enqueue.ts +35 -0
- package/src/classes/action.ts +98 -0
- package/src/classes/actionProcessor.ts +463 -0
- package/src/classes/api.ts +51 -0
- package/src/classes/cli.ts +67 -0
- package/src/classes/config.ts +15 -0
- package/src/classes/connection.ts +321 -0
- package/src/classes/exceptionReporter.ts +9 -0
- package/src/classes/initializer.ts +59 -0
- package/src/classes/initializers.ts +5 -0
- package/src/classes/input.ts +9 -0
- package/src/classes/inputs.ts +34 -0
- package/src/classes/process/actionheroVersion.ts +15 -0
- package/src/classes/process/env.ts +16 -0
- package/src/classes/process/id.ts +34 -0
- package/src/classes/process/pid.ts +32 -0
- package/src/classes/process/projectRoot.ts +16 -0
- package/src/classes/process/typescript.ts +47 -0
- package/src/classes/process.ts +479 -0
- package/src/classes/server.ts +251 -0
- package/src/classes/task.ts +87 -0
- package/src/config/api.ts +107 -0
- package/src/config/errors.ts +162 -0
- package/src/config/logger.ts +113 -0
- package/src/config/plugins.ts +37 -0
- package/src/config/redis.ts +78 -0
- package/src/config/routes.ts +44 -0
- package/src/config/tasks.ts +84 -0
- package/src/config/web.ts +136 -0
- package/src/config/websocket.ts +62 -0
- package/src/index.ts +46 -0
- package/src/initializers/actions.ts +125 -0
- package/src/initializers/chatRoom.ts +214 -0
- package/src/initializers/connections.ts +124 -0
- package/src/initializers/exceptions.ts +155 -0
- package/src/initializers/params.ts +52 -0
- package/src/initializers/redis.ts +191 -0
- package/src/initializers/resque.ts +248 -0
- package/src/initializers/routes.ts +229 -0
- package/src/initializers/servers.ts +134 -0
- package/src/initializers/specHelper.ts +195 -0
- package/src/initializers/staticFile.ts +253 -0
- package/src/initializers/tasks.ts +188 -0
- package/src/modules/action.ts +89 -0
- package/src/modules/cache.ts +326 -0
- package/src/modules/chatRoom.ts +321 -0
- package/src/modules/config.ts +246 -0
- package/src/modules/log.ts +62 -0
- package/src/modules/redis.ts +93 -0
- package/src/modules/route.ts +59 -0
- package/src/modules/specHelper.ts +182 -0
- package/src/modules/task.ts +527 -0
- package/src/modules/utils/argv.ts +3 -0
- package/src/modules/utils/arrayStartingMatch.ts +21 -0
- package/src/modules/utils/arrayUnique.ts +15 -0
- package/src/modules/utils/collapseObjectToArray.ts +33 -0
- package/src/modules/utils/deepCopy.ts +3 -0
- package/src/modules/utils/ensureNoTsHeaderOrSpecFiles.ts +19 -0
- package/src/modules/utils/eventLoopDelay.ts +34 -0
- package/src/modules/utils/fileUtils.ts +119 -0
- package/src/modules/utils/filterObjectForLogging.ts +51 -0
- package/src/modules/utils/filterResponseForLogging.ts +53 -0
- package/src/modules/utils/getExternalIPAddress.ts +17 -0
- package/src/modules/utils/hashMerge.ts +63 -0
- package/src/modules/utils/isPlainObject.ts +45 -0
- package/src/modules/utils/isRunning.ts +7 -0
- package/src/modules/utils/parseCookies.ts +20 -0
- package/src/modules/utils/parseHeadersForClientAddress.ts +53 -0
- package/src/modules/utils/parseIPv6URI.ts +24 -0
- package/src/modules/utils/replaceDistWithSrc.ts +9 -0
- package/src/modules/utils/safeGlob.ts +6 -0
- package/src/modules/utils/sleep.ts +8 -0
- package/src/modules/utils/sortGlobalMiddleware.ts +17 -0
- package/src/modules/utils/sourceRelativeLinkPath.ts +29 -0
- package/src/modules/utils.ts +66 -0
- package/src/server.ts +20 -0
- package/src/servers/web.ts +894 -0
- package/src/servers/websocket.ts +304 -0
- package/src/tasks/runAction.ts +29 -0
- package/tea.yaml +9 -0
- package/templates/README.md.template +17 -0
- package/templates/action.ts.template +15 -0
- package/templates/boot.js.template +9 -0
- package/templates/cli.ts.template +15 -0
- package/templates/gitignore.template +23 -0
- package/templates/initializer.ts.template +17 -0
- package/templates/package-plugin.json.template +12 -0
- package/templates/package.json.template +45 -0
- package/templates/projectMap.txt +39 -0
- package/templates/projectServer.ts.template +20 -0
- package/templates/server.ts.template +37 -0
- package/templates/task.ts.template +16 -0
- package/templates/test/action.ts.template +13 -0
- package/templates/test/task.ts.template +20 -0
- package/tsconfig.json +11 -0
@@ -0,0 +1,463 @@
|
|
1
|
+
import * as dotProp from "dot-prop";
|
2
|
+
import { api } from "../index";
|
3
|
+
import { log, ActionheroLogLevel } from "../modules/log";
|
4
|
+
import { utils } from "../modules/utils";
|
5
|
+
import { config } from "./../modules/config";
|
6
|
+
import { Action } from "./action";
|
7
|
+
import { Connection } from "./connection";
|
8
|
+
import { Input } from "./input";
|
9
|
+
|
10
|
+
export enum ActionsStatus {
|
11
|
+
Complete,
|
12
|
+
GenericError,
|
13
|
+
ServerShuttingDown,
|
14
|
+
TooManyRequests,
|
15
|
+
UnknownAction,
|
16
|
+
UnsupportedServerType,
|
17
|
+
MissingParams,
|
18
|
+
ValidatorErrors,
|
19
|
+
}
|
20
|
+
|
21
|
+
export class ActionProcessor<ActionClass extends Action> {
|
22
|
+
connection: Connection;
|
23
|
+
action: ActionClass["name"];
|
24
|
+
toProcess: boolean;
|
25
|
+
toRender: boolean;
|
26
|
+
messageId: number | string;
|
27
|
+
params: {
|
28
|
+
action?: string;
|
29
|
+
apiVersion?: string | number;
|
30
|
+
[key: string]: any;
|
31
|
+
};
|
32
|
+
// params: ActionClass["inputs"];
|
33
|
+
missingParams: Array<string>;
|
34
|
+
validatorErrors: Array<string | Error>;
|
35
|
+
actionStartTime: number;
|
36
|
+
actionTemplate: ActionClass;
|
37
|
+
working: boolean;
|
38
|
+
response: {
|
39
|
+
[key: string]: any;
|
40
|
+
};
|
41
|
+
duration: number;
|
42
|
+
actionStatus: ActionsStatus;
|
43
|
+
|
44
|
+
// allow for setting of any value via middleware
|
45
|
+
session: any;
|
46
|
+
|
47
|
+
constructor(connection: Connection) {
|
48
|
+
this.connection = connection;
|
49
|
+
this.action = null;
|
50
|
+
this.toProcess = true;
|
51
|
+
this.toRender = true;
|
52
|
+
this.messageId = connection.messageId || 0;
|
53
|
+
this.params = Object.assign(
|
54
|
+
{ action: null, apiVersion: null },
|
55
|
+
connection.params,
|
56
|
+
);
|
57
|
+
this.missingParams = [];
|
58
|
+
this.validatorErrors = [];
|
59
|
+
this.actionStartTime = null;
|
60
|
+
this.actionTemplate = null;
|
61
|
+
this.working = false;
|
62
|
+
this.response = {};
|
63
|
+
this.duration = null;
|
64
|
+
this.actionStatus = null;
|
65
|
+
this.session = {};
|
66
|
+
}
|
67
|
+
|
68
|
+
private incrementTotalActions(count = 1) {
|
69
|
+
this.connection.totalActions = this.connection.totalActions + count;
|
70
|
+
}
|
71
|
+
|
72
|
+
private incrementPendingActions(count = 1) {
|
73
|
+
this.connection.pendingActions = this.connection.pendingActions + count;
|
74
|
+
if (this.connection.pendingActions < 0) {
|
75
|
+
this.connection.pendingActions = 0;
|
76
|
+
}
|
77
|
+
}
|
78
|
+
|
79
|
+
getPendingActionCount() {
|
80
|
+
return this.connection.pendingActions;
|
81
|
+
}
|
82
|
+
|
83
|
+
private async completeAction(
|
84
|
+
status: ActionsStatus,
|
85
|
+
_error?: NodeJS.ErrnoException,
|
86
|
+
) {
|
87
|
+
let error: NodeJS.ErrnoException | string = null;
|
88
|
+
this.actionStatus = status;
|
89
|
+
|
90
|
+
if (status === ActionsStatus.GenericError) {
|
91
|
+
error =
|
92
|
+
typeof config.errors.genericError === "function"
|
93
|
+
? await config.errors.genericError(this, _error)
|
94
|
+
: _error;
|
95
|
+
} else if (status === ActionsStatus.ServerShuttingDown) {
|
96
|
+
error = await config.errors.serverShuttingDown(this);
|
97
|
+
} else if (status === ActionsStatus.TooManyRequests) {
|
98
|
+
error = await config.errors.tooManyPendingActions(this);
|
99
|
+
} else if (status === ActionsStatus.UnknownAction) {
|
100
|
+
error = await config.errors.unknownAction(this);
|
101
|
+
} else if (status === ActionsStatus.UnsupportedServerType) {
|
102
|
+
error = await config.errors.unsupportedServerType(this);
|
103
|
+
} else if (status === ActionsStatus.MissingParams) {
|
104
|
+
error = await config.errors.missingParams(this, this.missingParams);
|
105
|
+
} else if (status === ActionsStatus.ValidatorErrors) {
|
106
|
+
error = await config.errors.invalidParams(this, this.validatorErrors);
|
107
|
+
} else if (status) {
|
108
|
+
error = _error;
|
109
|
+
}
|
110
|
+
|
111
|
+
if (typeof error === "string") error = new Error(error);
|
112
|
+
|
113
|
+
if (error && (typeof this.response === "string" || !this.response.error)) {
|
114
|
+
if (typeof this.response === "string" || Array.isArray(this.response)) {
|
115
|
+
//@ts-ignore
|
116
|
+
this.response = error.toString();
|
117
|
+
} else {
|
118
|
+
this.response.error = error;
|
119
|
+
}
|
120
|
+
}
|
121
|
+
|
122
|
+
this.incrementPendingActions(-1);
|
123
|
+
this.duration = new Date().getTime() - this.actionStartTime;
|
124
|
+
this.working = false;
|
125
|
+
this.logAndReportAction(status, error);
|
126
|
+
|
127
|
+
return this;
|
128
|
+
}
|
129
|
+
|
130
|
+
private logAndReportAction(
|
131
|
+
status: ActionsStatus,
|
132
|
+
error: NodeJS.ErrnoException,
|
133
|
+
) {
|
134
|
+
const { type, rawConnection } = this.connection;
|
135
|
+
|
136
|
+
let logLevel: ActionheroLogLevel = "info";
|
137
|
+
if (this.actionTemplate && this.actionTemplate.logLevel) {
|
138
|
+
logLevel = this.actionTemplate.logLevel;
|
139
|
+
}
|
140
|
+
|
141
|
+
const filteredParams = utils.filterObjectForLogging(this.params);
|
142
|
+
let logLine = {
|
143
|
+
to: this.connection.remoteIP,
|
144
|
+
action: this.action,
|
145
|
+
params: JSON.stringify(filteredParams),
|
146
|
+
duration: this.duration,
|
147
|
+
method: type === "web" ? rawConnection.method : undefined,
|
148
|
+
pathname: type === "web" ? rawConnection.parsedURL.pathname : undefined,
|
149
|
+
error: "",
|
150
|
+
response: undefined as string,
|
151
|
+
};
|
152
|
+
|
153
|
+
if (config.general.enableResponseLogging) {
|
154
|
+
logLine.response = JSON.stringify(
|
155
|
+
utils.filterResponseForLogging(this.response),
|
156
|
+
);
|
157
|
+
}
|
158
|
+
|
159
|
+
if (error) {
|
160
|
+
let errorFields;
|
161
|
+
const formatErrorLogLine =
|
162
|
+
config.errors.serializers.actionProcessor ||
|
163
|
+
this.applyDefaultErrorLogLineFormat;
|
164
|
+
({ logLevel = "error", errorFields } = formatErrorLogLine(error));
|
165
|
+
logLine = { ...logLine, ...errorFields };
|
166
|
+
}
|
167
|
+
|
168
|
+
log(`[ action @ ${this.connection.type} ]`, logLevel, logLine);
|
169
|
+
|
170
|
+
if (
|
171
|
+
error &&
|
172
|
+
(status !== ActionsStatus.UnknownAction ||
|
173
|
+
config.errors.reportUnknownActions)
|
174
|
+
) {
|
175
|
+
api.exceptionHandlers.action(error, logLine);
|
176
|
+
}
|
177
|
+
}
|
178
|
+
|
179
|
+
applyDefaultErrorLogLineFormat(error: NodeJS.ErrnoException) {
|
180
|
+
const logLevel = "error" as ActionheroLogLevel;
|
181
|
+
const errorFields: { error: string } = { error: null };
|
182
|
+
if (error instanceof Error) {
|
183
|
+
errorFields.error = error.toString();
|
184
|
+
Object.getOwnPropertyNames(error)
|
185
|
+
.filter((prop) => prop !== "message")
|
186
|
+
.sort((a, b) => (a === "stack" || b === "stack" ? -1 : 1))
|
187
|
+
//@ts-ignore
|
188
|
+
.forEach((prop) => (errorFields[prop] = error[prop]));
|
189
|
+
} else {
|
190
|
+
try {
|
191
|
+
errorFields.error = JSON.stringify(error);
|
192
|
+
} catch (e) {
|
193
|
+
errorFields.error = String(error);
|
194
|
+
}
|
195
|
+
}
|
196
|
+
|
197
|
+
return { errorFields, logLevel };
|
198
|
+
}
|
199
|
+
|
200
|
+
private async preProcessAction() {
|
201
|
+
const processorNames = api.actions.globalMiddleware.slice(0);
|
202
|
+
|
203
|
+
if (this.actionTemplate.middleware) {
|
204
|
+
this.actionTemplate.middleware.forEach(function (m) {
|
205
|
+
processorNames.push(m);
|
206
|
+
});
|
207
|
+
}
|
208
|
+
|
209
|
+
for (const i in processorNames) {
|
210
|
+
const name = processorNames[i];
|
211
|
+
if (typeof api.actions.middleware[name].preProcessor === "function") {
|
212
|
+
await api.actions.middleware[name].preProcessor(this);
|
213
|
+
}
|
214
|
+
}
|
215
|
+
}
|
216
|
+
|
217
|
+
private async postProcessAction() {
|
218
|
+
const processorNames = api.actions.globalMiddleware.slice(0);
|
219
|
+
|
220
|
+
if (this.actionTemplate.middleware) {
|
221
|
+
this.actionTemplate.middleware.forEach((m) => {
|
222
|
+
processorNames.push(m);
|
223
|
+
});
|
224
|
+
}
|
225
|
+
|
226
|
+
for (const i in processorNames) {
|
227
|
+
const name = processorNames[i];
|
228
|
+
if (typeof api.actions.middleware[name].postProcessor === "function") {
|
229
|
+
await api.actions.middleware[name].postProcessor(this);
|
230
|
+
}
|
231
|
+
}
|
232
|
+
}
|
233
|
+
|
234
|
+
private reduceParams(schemaKey?: string) {
|
235
|
+
let inputs = this.actionTemplate.inputs || {};
|
236
|
+
let params = this.params;
|
237
|
+
|
238
|
+
if (schemaKey) {
|
239
|
+
inputs = this.actionTemplate.inputs[schemaKey].schema;
|
240
|
+
params = this.params[schemaKey];
|
241
|
+
}
|
242
|
+
|
243
|
+
const inputNames = Object.keys(inputs) || [];
|
244
|
+
if (config.general.disableParamScrubbing !== true) {
|
245
|
+
for (const p in params) {
|
246
|
+
if (
|
247
|
+
api.params.globalSafeParams.indexOf(p) < 0 &&
|
248
|
+
inputNames.indexOf(p) < 0
|
249
|
+
) {
|
250
|
+
delete params[p];
|
251
|
+
}
|
252
|
+
}
|
253
|
+
}
|
254
|
+
}
|
255
|
+
|
256
|
+
private prepareStringMethod(method: string): Function {
|
257
|
+
const cmdParts = method.split(".");
|
258
|
+
const cmd = cmdParts.shift();
|
259
|
+
if (cmd !== "api") {
|
260
|
+
throw new Error("cannot operate on a method outside of the api object");
|
261
|
+
}
|
262
|
+
return dotProp.get(api, cmdParts.join("."));
|
263
|
+
}
|
264
|
+
|
265
|
+
private async validateParam(
|
266
|
+
props: Input,
|
267
|
+
params: ActionProcessor<any>["params"],
|
268
|
+
key: string,
|
269
|
+
schemaKey: string,
|
270
|
+
) {
|
271
|
+
// default
|
272
|
+
if (params[key] === undefined && props.default !== undefined) {
|
273
|
+
if (typeof props.default === "function") {
|
274
|
+
params[key] = await props.default.call(this, params[key]);
|
275
|
+
} else {
|
276
|
+
params[key] = props.default;
|
277
|
+
}
|
278
|
+
}
|
279
|
+
|
280
|
+
// formatter
|
281
|
+
if (params[key] !== undefined && props.formatter !== undefined) {
|
282
|
+
if (!Array.isArray(props.formatter)) {
|
283
|
+
props.formatter = [props.formatter];
|
284
|
+
}
|
285
|
+
|
286
|
+
for (const i in props.formatter) {
|
287
|
+
const formatter = props.formatter[i];
|
288
|
+
if (typeof formatter === "function") {
|
289
|
+
params[key] = await formatter.call(this, params[key], key);
|
290
|
+
} else {
|
291
|
+
const method = this.prepareStringMethod(formatter);
|
292
|
+
params[key] = await method.call(this, params[key], key);
|
293
|
+
}
|
294
|
+
}
|
295
|
+
}
|
296
|
+
|
297
|
+
// validator
|
298
|
+
if (params[key] !== undefined && props.validator !== undefined) {
|
299
|
+
if (!Array.isArray(props.validator)) {
|
300
|
+
props.validator = [props.validator];
|
301
|
+
}
|
302
|
+
|
303
|
+
for (const j in props.validator) {
|
304
|
+
const validator = props.validator[j];
|
305
|
+
let validatorResponse;
|
306
|
+
try {
|
307
|
+
if (typeof validator === "function") {
|
308
|
+
validatorResponse = await validator.call(this, params[key], key);
|
309
|
+
} else {
|
310
|
+
const method = this.prepareStringMethod(validator);
|
311
|
+
validatorResponse = await method.call(this, params[key], key);
|
312
|
+
}
|
313
|
+
|
314
|
+
// validator function returned nothing; assume param is OK
|
315
|
+
if (validatorResponse === null || validatorResponse === undefined) {
|
316
|
+
return;
|
317
|
+
}
|
318
|
+
|
319
|
+
// validator returned something that was not `true`
|
320
|
+
if (validatorResponse !== true) {
|
321
|
+
if (validatorResponse === false) {
|
322
|
+
this.validatorErrors.push(
|
323
|
+
new Error(`Input for parameter "${key}" failed validation!`),
|
324
|
+
);
|
325
|
+
} else {
|
326
|
+
this.validatorErrors.push(validatorResponse);
|
327
|
+
}
|
328
|
+
}
|
329
|
+
} catch (error) {
|
330
|
+
// validator threw an error
|
331
|
+
this.validatorErrors.push(error);
|
332
|
+
}
|
333
|
+
}
|
334
|
+
}
|
335
|
+
|
336
|
+
// required
|
337
|
+
if (props.required === true) {
|
338
|
+
if (config.general.missingParamChecks.indexOf(params[key]) >= 0) {
|
339
|
+
let missingKey = key;
|
340
|
+
if (schemaKey) {
|
341
|
+
missingKey = `${schemaKey}.${missingKey}`;
|
342
|
+
}
|
343
|
+
this.missingParams.push(missingKey);
|
344
|
+
}
|
345
|
+
}
|
346
|
+
}
|
347
|
+
|
348
|
+
private async validateParams(schemaKey?: string) {
|
349
|
+
let inputs = this.actionTemplate.inputs || {};
|
350
|
+
let params = this.params;
|
351
|
+
|
352
|
+
if (schemaKey) {
|
353
|
+
inputs = this.actionTemplate.inputs[schemaKey].schema;
|
354
|
+
params = this.params[schemaKey];
|
355
|
+
}
|
356
|
+
|
357
|
+
for (const key in inputs) {
|
358
|
+
const props = inputs[key];
|
359
|
+
await this.validateParam(props, params, key, schemaKey);
|
360
|
+
|
361
|
+
if (props.schema && params[key]) {
|
362
|
+
this.reduceParams(key);
|
363
|
+
await this.validateParams(key);
|
364
|
+
}
|
365
|
+
}
|
366
|
+
}
|
367
|
+
|
368
|
+
lockParams() {
|
369
|
+
this.params = Object.freeze(this.params);
|
370
|
+
}
|
371
|
+
|
372
|
+
async processAction(
|
373
|
+
actionName?: string,
|
374
|
+
apiVersion = this.params.apiVersion,
|
375
|
+
) {
|
376
|
+
this.actionStartTime = new Date().getTime();
|
377
|
+
this.working = true;
|
378
|
+
this.incrementTotalActions();
|
379
|
+
this.incrementPendingActions();
|
380
|
+
this.action = actionName || this.params.action;
|
381
|
+
|
382
|
+
if (api.actions.versions[this.action]) {
|
383
|
+
if (!apiVersion) {
|
384
|
+
apiVersion =
|
385
|
+
api.actions.versions[this.action][
|
386
|
+
api.actions.versions[this.action].length - 1
|
387
|
+
];
|
388
|
+
}
|
389
|
+
|
390
|
+
//@ts-ignore
|
391
|
+
this.actionTemplate = api.actions.actions[this.action][apiVersion];
|
392
|
+
|
393
|
+
// send back the version we use to send in the api response
|
394
|
+
if (!this.params.apiVersion) this.params.apiVersion = apiVersion;
|
395
|
+
}
|
396
|
+
|
397
|
+
if (api.running !== true) {
|
398
|
+
return this.completeAction(ActionsStatus.ServerShuttingDown);
|
399
|
+
}
|
400
|
+
|
401
|
+
if (this.getPendingActionCount() > config.general.simultaneousActions) {
|
402
|
+
return this.completeAction(ActionsStatus.TooManyRequests);
|
403
|
+
}
|
404
|
+
|
405
|
+
if (!this.action || !this.actionTemplate) {
|
406
|
+
return this.completeAction(ActionsStatus.UnknownAction);
|
407
|
+
}
|
408
|
+
|
409
|
+
if (
|
410
|
+
this.actionTemplate.blockedConnectionTypes &&
|
411
|
+
this.actionTemplate.blockedConnectionTypes.indexOf(
|
412
|
+
this.connection.type,
|
413
|
+
) >= 0
|
414
|
+
) {
|
415
|
+
return this.completeAction(ActionsStatus.UnsupportedServerType);
|
416
|
+
}
|
417
|
+
|
418
|
+
return this.runAction();
|
419
|
+
}
|
420
|
+
|
421
|
+
private async runAction() {
|
422
|
+
try {
|
423
|
+
const preProcessResponse = await this.preProcessAction();
|
424
|
+
if (preProcessResponse !== undefined && preProcessResponse !== null) {
|
425
|
+
Object.assign(this.response, preProcessResponse);
|
426
|
+
}
|
427
|
+
|
428
|
+
await this.reduceParams();
|
429
|
+
await this.validateParams();
|
430
|
+
this.lockParams();
|
431
|
+
} catch (error) {
|
432
|
+
return this.completeAction(ActionsStatus.GenericError, error);
|
433
|
+
}
|
434
|
+
|
435
|
+
if (this.missingParams.length > 0) {
|
436
|
+
return this.completeAction(ActionsStatus.MissingParams);
|
437
|
+
}
|
438
|
+
|
439
|
+
if (this.validatorErrors.length > 0) {
|
440
|
+
return this.completeAction(ActionsStatus.ValidatorErrors);
|
441
|
+
}
|
442
|
+
|
443
|
+
if (this.toProcess === true) {
|
444
|
+
try {
|
445
|
+
const actionResponse = await this.actionTemplate.run(this);
|
446
|
+
if (actionResponse !== undefined && actionResponse !== null) {
|
447
|
+
Object.assign(this.response, actionResponse);
|
448
|
+
}
|
449
|
+
|
450
|
+
const postProcessResponse = await this.postProcessAction();
|
451
|
+
if (postProcessResponse !== undefined && postProcessResponse !== null) {
|
452
|
+
Object.assign(this.response, postProcessResponse);
|
453
|
+
}
|
454
|
+
|
455
|
+
return this.completeAction(ActionsStatus.Complete);
|
456
|
+
} catch (error) {
|
457
|
+
return this.completeAction(ActionsStatus.GenericError, error);
|
458
|
+
}
|
459
|
+
} else {
|
460
|
+
return this.completeAction(ActionsStatus.Complete);
|
461
|
+
}
|
462
|
+
}
|
463
|
+
}
|
@@ -0,0 +1,51 @@
|
|
1
|
+
import { Process } from "./process";
|
2
|
+
|
3
|
+
import { ActionsApi } from "./../initializers/actions";
|
4
|
+
import { TaskApi } from "./../initializers/tasks";
|
5
|
+
import { ConnectionsApi } from "./../initializers/connections";
|
6
|
+
import { ServersApi } from "./../initializers/servers";
|
7
|
+
import { ChatRoomApi } from "./../initializers/chatRoom";
|
8
|
+
import { ExceptionHandlerAPI } from "./../initializers/exceptions";
|
9
|
+
import { ParamsApi } from "./../initializers/params";
|
10
|
+
import { ResqueApi } from "./../initializers/resque";
|
11
|
+
import { RedisApi } from "./../initializers/redis";
|
12
|
+
import { StaticFileApi } from "./../initializers/staticFile";
|
13
|
+
import { RoutesApi } from "./../initializers/routes";
|
14
|
+
import { SpecHelperApi } from "./../initializers/specHelper";
|
15
|
+
|
16
|
+
export class Api {
|
17
|
+
running: boolean;
|
18
|
+
bootTime: number;
|
19
|
+
|
20
|
+
process?: Process;
|
21
|
+
|
22
|
+
commands: {
|
23
|
+
initialize?: Function;
|
24
|
+
start?: Function;
|
25
|
+
stop?: Function;
|
26
|
+
restart?: Function;
|
27
|
+
};
|
28
|
+
|
29
|
+
connections: ConnectionsApi;
|
30
|
+
actions: ActionsApi;
|
31
|
+
tasks: TaskApi;
|
32
|
+
servers: ServersApi;
|
33
|
+
chatRoom: ChatRoomApi;
|
34
|
+
params: ParamsApi;
|
35
|
+
staticFile: StaticFileApi;
|
36
|
+
redis: RedisApi;
|
37
|
+
resque: ResqueApi;
|
38
|
+
routes: RoutesApi;
|
39
|
+
exceptionHandlers: ExceptionHandlerAPI;
|
40
|
+
specHelper: SpecHelperApi;
|
41
|
+
|
42
|
+
// this is left in as way for older methods to still extend the api object
|
43
|
+
// going forward, all interfaces should be exposed via export to be consumed directly
|
44
|
+
[key: string]: any;
|
45
|
+
|
46
|
+
constructor() {
|
47
|
+
this.bootTime = new Date().getTime();
|
48
|
+
this.running = false;
|
49
|
+
this.commands = {};
|
50
|
+
}
|
51
|
+
}
|
@@ -0,0 +1,67 @@
|
|
1
|
+
/**
|
2
|
+
* An Actionhero CLI Command.
|
3
|
+
* For inputs, you can provide Options (--thing=stuff) with the "Inputs" object, or define Arguments in the name of the command (`greet [name]`)
|
4
|
+
*/
|
5
|
+
export abstract class CLI {
|
6
|
+
/**The name of the CLI command. */
|
7
|
+
name: string;
|
8
|
+
/**The description of the CLI command (default this.name) */
|
9
|
+
description: string;
|
10
|
+
/**An example of how to run this CLI command */
|
11
|
+
example: string;
|
12
|
+
/**The inputs of the CLI command (default: {}) */
|
13
|
+
inputs: {
|
14
|
+
[key: string]: {
|
15
|
+
required?: boolean;
|
16
|
+
requiredValue?: boolean;
|
17
|
+
default?: string | boolean;
|
18
|
+
letter?: string;
|
19
|
+
flag?: boolean;
|
20
|
+
placeholder?: string;
|
21
|
+
variadic?: boolean;
|
22
|
+
description?: string;
|
23
|
+
formatter?: Function;
|
24
|
+
validator?: Function;
|
25
|
+
};
|
26
|
+
};
|
27
|
+
|
28
|
+
/** Should the server initialize before running this command? */
|
29
|
+
initialize: boolean;
|
30
|
+
|
31
|
+
/** Should the server start before running this command? */
|
32
|
+
start: boolean;
|
33
|
+
|
34
|
+
constructor() {
|
35
|
+
this.description = this.description ?? this.name;
|
36
|
+
this.example = this.example ?? "";
|
37
|
+
this.inputs = this.inputs ?? {};
|
38
|
+
this.initialize = this.initialize ?? true;
|
39
|
+
this.start = this.start ?? false;
|
40
|
+
}
|
41
|
+
|
42
|
+
/**
|
43
|
+
* The main "do something" method for this CLI command. It is an `async` method.
|
44
|
+
* If error is thrown in this method, it will be logged to STDERR, and the process will terminate with a non-0 exit code.
|
45
|
+
*/
|
46
|
+
|
47
|
+
abstract run(data: { [key: string]: any }): Promise<boolean>;
|
48
|
+
|
49
|
+
/**
|
50
|
+
* An optional method to append additional information to the --help response for this CLI command
|
51
|
+
*/
|
52
|
+
help() {}
|
53
|
+
|
54
|
+
validate() {
|
55
|
+
if (!this.name) {
|
56
|
+
throw new Error("name is required for this cli command");
|
57
|
+
}
|
58
|
+
if (!this.description) {
|
59
|
+
throw new Error(
|
60
|
+
`description is required for the cli commend \`${this.name}\``,
|
61
|
+
);
|
62
|
+
}
|
63
|
+
if (!this.run || typeof this.run !== "function") {
|
64
|
+
throw new Error(`cli command \`${this.name}\` has no run method`);
|
65
|
+
}
|
66
|
+
}
|
67
|
+
}
|
@@ -0,0 +1,15 @@
|
|
1
|
+
export interface ActionheroConfigInterface {
|
2
|
+
[key: string]: Record<string, unknown>;
|
3
|
+
}
|
4
|
+
|
5
|
+
export type PluginConfigRecord = {
|
6
|
+
path: string;
|
7
|
+
actions?: boolean;
|
8
|
+
tasks?: boolean;
|
9
|
+
initializers?: boolean;
|
10
|
+
servers?: boolean;
|
11
|
+
public?: boolean;
|
12
|
+
cli?: boolean;
|
13
|
+
};
|
14
|
+
|
15
|
+
export type PluginConfig = { [name: string]: PluginConfigRecord };
|