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,134 @@
|
|
1
|
+
import * as path from "path";
|
2
|
+
import { api, config, log, utils, Initializer, Server } from "../index";
|
3
|
+
import { PluginConfig } from "../classes/config";
|
4
|
+
import { safeGlobSync } from "../modules/utils/safeGlob";
|
5
|
+
|
6
|
+
export interface ServersApi {
|
7
|
+
servers: {
|
8
|
+
[key: string]: Server;
|
9
|
+
};
|
10
|
+
}
|
11
|
+
|
12
|
+
/**
|
13
|
+
* Manages the servers in this Actionhero instance.
|
14
|
+
*/
|
15
|
+
export class Servers extends Initializer {
|
16
|
+
constructor() {
|
17
|
+
super();
|
18
|
+
this.name = "servers";
|
19
|
+
this.loadPriority = 599;
|
20
|
+
this.startPriority = 900;
|
21
|
+
this.stopPriority = 100;
|
22
|
+
}
|
23
|
+
|
24
|
+
async initialize() {
|
25
|
+
api.servers = {
|
26
|
+
servers: {},
|
27
|
+
};
|
28
|
+
|
29
|
+
const serverFolders = [path.resolve(path.join(__dirname, "..", "servers"))];
|
30
|
+
|
31
|
+
config.general.paths.server.forEach((p: string) => {
|
32
|
+
p = path.resolve(p);
|
33
|
+
if (serverFolders.indexOf(p) < 0) {
|
34
|
+
serverFolders.push(p);
|
35
|
+
}
|
36
|
+
});
|
37
|
+
|
38
|
+
let files: string[] = [];
|
39
|
+
|
40
|
+
for (const i in serverFolders) {
|
41
|
+
const p = serverFolders[i];
|
42
|
+
files = files.concat(safeGlobSync(path.join(p, "**", "**/*(*.js|*.ts)")));
|
43
|
+
}
|
44
|
+
|
45
|
+
for (const [_, plugin] of Object.entries(config.plugins as PluginConfig)) {
|
46
|
+
if (plugin.servers !== false) {
|
47
|
+
const pluginPath: string = path.normalize(plugin.path);
|
48
|
+
|
49
|
+
// old style at the root of the project
|
50
|
+
files = files.concat(
|
51
|
+
safeGlobSync(path.join(pluginPath, "servers", "**", "*.js")),
|
52
|
+
);
|
53
|
+
|
54
|
+
files = files.concat(
|
55
|
+
safeGlobSync(path.join(pluginPath, "dist", "servers", "**", "*.js")),
|
56
|
+
);
|
57
|
+
}
|
58
|
+
}
|
59
|
+
|
60
|
+
files = utils.ensureNoTsHeaderOrSpecFiles(files);
|
61
|
+
|
62
|
+
let server: Server;
|
63
|
+
|
64
|
+
for (const j in files) {
|
65
|
+
const filename = files[j];
|
66
|
+
const ExportedClasses = await import(filename);
|
67
|
+
|
68
|
+
const exportLen = Object.keys(ExportedClasses).length;
|
69
|
+
// we have named exports
|
70
|
+
if (exportLen) {
|
71
|
+
if (exportLen > 1) {
|
72
|
+
throw new Error(
|
73
|
+
`server file ${filename} exports more than one server`,
|
74
|
+
);
|
75
|
+
}
|
76
|
+
|
77
|
+
server = new ExportedClasses[Object.keys(ExportedClasses)[0]]();
|
78
|
+
} else {
|
79
|
+
// there is one default export
|
80
|
+
server = new ExportedClasses();
|
81
|
+
}
|
82
|
+
|
83
|
+
server.config = config[server.type]; // for shorthand access
|
84
|
+
if (server.config && server.config.enabled === true) {
|
85
|
+
await server.initialize();
|
86
|
+
|
87
|
+
if (api.servers.servers[server.type]) {
|
88
|
+
log(
|
89
|
+
`an existing server with the same type \`${server.type}\` will be overridden by the file ${filename}`,
|
90
|
+
"crit",
|
91
|
+
);
|
92
|
+
}
|
93
|
+
|
94
|
+
api.servers.servers[server.type] = server;
|
95
|
+
log(`Initialized server: ${server.type}`, "debug");
|
96
|
+
}
|
97
|
+
}
|
98
|
+
}
|
99
|
+
|
100
|
+
async start() {
|
101
|
+
for (const serverName of Object.keys(api.servers.servers)) {
|
102
|
+
const bindIp = config[serverName]?.bindIP?.toString();
|
103
|
+
const port = config[serverName]?.port?.toString();
|
104
|
+
|
105
|
+
const server = api.servers.servers[serverName];
|
106
|
+
if (server && server.config.enabled === true) {
|
107
|
+
const message = `Starting server: \`${serverName}\` ${
|
108
|
+
bindIp
|
109
|
+
? `@ ${serverName === "web" ? "http://" : ""}${bindIp}${
|
110
|
+
port ? `:${port}` : ""
|
111
|
+
}`
|
112
|
+
: ""
|
113
|
+
}`;
|
114
|
+
log(message, "notice");
|
115
|
+
await server.start();
|
116
|
+
log(`Server started: ${serverName}`, "debug");
|
117
|
+
}
|
118
|
+
}
|
119
|
+
}
|
120
|
+
|
121
|
+
async stop() {
|
122
|
+
const serverNames = Object.keys(api.servers.servers);
|
123
|
+
for (const i in serverNames) {
|
124
|
+
const serverName = serverNames[i];
|
125
|
+
const server = api.servers.servers[serverName];
|
126
|
+
if ((server && server.config.enabled === true) || !server) {
|
127
|
+
log(`Stopping server: ${serverName}`, "notice");
|
128
|
+
await server.stop();
|
129
|
+
server.removeAllListeners();
|
130
|
+
log(`Server stopped: ${serverName}`, "debug");
|
131
|
+
}
|
132
|
+
}
|
133
|
+
}
|
134
|
+
}
|
@@ -0,0 +1,195 @@
|
|
1
|
+
import { EventEmitter } from "stream";
|
2
|
+
import * as uuid from "uuid";
|
3
|
+
import { ActionProcessor } from "../classes/actionProcessor";
|
4
|
+
import { connectionVerbs } from "../classes/connection";
|
5
|
+
import {
|
6
|
+
api,
|
7
|
+
config,
|
8
|
+
log,
|
9
|
+
env,
|
10
|
+
Initializer,
|
11
|
+
Server,
|
12
|
+
Connection,
|
13
|
+
} from "../index";
|
14
|
+
import { SpecHelperConnection } from "../modules/specHelper";
|
15
|
+
|
16
|
+
export interface SpecHelperApi {
|
17
|
+
returnMetadata?: boolean;
|
18
|
+
// Server?: Server;
|
19
|
+
Server?: any;
|
20
|
+
Connection?: any;
|
21
|
+
}
|
22
|
+
|
23
|
+
/**
|
24
|
+
* A special "mock" server which enables you to test actions and tasks in a simple way. Only available in the TEST environment.
|
25
|
+
*/
|
26
|
+
export class SpecHelper extends Initializer {
|
27
|
+
enabled: boolean;
|
28
|
+
|
29
|
+
constructor() {
|
30
|
+
super();
|
31
|
+
this.name = "specHelper";
|
32
|
+
this.loadPriority = 900;
|
33
|
+
this.startPriority = 901;
|
34
|
+
this.enabled = false;
|
35
|
+
}
|
36
|
+
|
37
|
+
async initialize() {
|
38
|
+
if (env === "test") this.enabled = true;
|
39
|
+
if (!this.enabled) return;
|
40
|
+
|
41
|
+
class TestServer extends Server {
|
42
|
+
constructor() {
|
43
|
+
super();
|
44
|
+
|
45
|
+
this.type = "testServer";
|
46
|
+
this.attributes = {
|
47
|
+
canChat: true,
|
48
|
+
logConnections: false,
|
49
|
+
logExits: false,
|
50
|
+
sendWelcomeMessage: true,
|
51
|
+
verbs: connectionVerbs,
|
52
|
+
};
|
53
|
+
}
|
54
|
+
|
55
|
+
async initialize() {}
|
56
|
+
|
57
|
+
async start() {
|
58
|
+
log("loading the testServer", "info");
|
59
|
+
this.on("connection", (connection) => {
|
60
|
+
this.handleConnection(connection);
|
61
|
+
});
|
62
|
+
this.on("actionComplete", (data) => {
|
63
|
+
this.actionComplete(data);
|
64
|
+
});
|
65
|
+
}
|
66
|
+
|
67
|
+
async stop() {}
|
68
|
+
|
69
|
+
async sendMessage(
|
70
|
+
connection: SpecHelperConnection,
|
71
|
+
message: any,
|
72
|
+
messageId: string | number,
|
73
|
+
) {
|
74
|
+
process.nextTick(() => {
|
75
|
+
connection.messages.push(message);
|
76
|
+
if (typeof connection.actionCallbacks[messageId] === "function") {
|
77
|
+
connection.actionCallbacks[messageId](message, connection);
|
78
|
+
delete connection.actionCallbacks[messageId];
|
79
|
+
}
|
80
|
+
});
|
81
|
+
}
|
82
|
+
|
83
|
+
async sendFile(
|
84
|
+
connection: Connection,
|
85
|
+
error: NodeJS.ErrnoException,
|
86
|
+
fileStream: EventEmitter,
|
87
|
+
mime: string,
|
88
|
+
length: number,
|
89
|
+
) {
|
90
|
+
let content = "";
|
91
|
+
const messageId = connection.messageId;
|
92
|
+
const response = {
|
93
|
+
content: null as string,
|
94
|
+
mime: mime,
|
95
|
+
length: length,
|
96
|
+
error: undefined as Error,
|
97
|
+
};
|
98
|
+
|
99
|
+
if (error) {
|
100
|
+
response.error = error;
|
101
|
+
}
|
102
|
+
|
103
|
+
try {
|
104
|
+
if (!error) {
|
105
|
+
fileStream.on("data", (d) => (content += d));
|
106
|
+
fileStream.on("end", () => {
|
107
|
+
response.content = content;
|
108
|
+
this.sendMessage(connection, response, messageId);
|
109
|
+
});
|
110
|
+
} else {
|
111
|
+
this.sendMessage(connection, response, messageId);
|
112
|
+
}
|
113
|
+
} catch (e) {
|
114
|
+
this.log(e, "warning");
|
115
|
+
this.sendMessage(connection, response, messageId);
|
116
|
+
}
|
117
|
+
}
|
118
|
+
|
119
|
+
handleConnection(connection: SpecHelperConnection) {
|
120
|
+
connection.messages = [];
|
121
|
+
connection.actionCallbacks = {};
|
122
|
+
}
|
123
|
+
|
124
|
+
async actionComplete(data: ActionProcessor<any>) {
|
125
|
+
data.response.error;
|
126
|
+
if (typeof data.response === "string" || Array.isArray(data.response)) {
|
127
|
+
// nothing to do...
|
128
|
+
} else {
|
129
|
+
if (data.response.error) {
|
130
|
+
data.response.error =
|
131
|
+
await config.errors.serializers.servers.specHelper(
|
132
|
+
data.response.error,
|
133
|
+
);
|
134
|
+
}
|
135
|
+
|
136
|
+
if (api.specHelper.returnMetadata) {
|
137
|
+
data.response.messageId = data.messageId;
|
138
|
+
|
139
|
+
data.response.serverInformation = {
|
140
|
+
serverName: config.general.serverName,
|
141
|
+
apiVersion: config.general.apiVersion,
|
142
|
+
};
|
143
|
+
|
144
|
+
data.response.requesterInformation = {
|
145
|
+
id: data.connection.id,
|
146
|
+
remoteIP: data.connection.remoteIP,
|
147
|
+
receivedParams: {},
|
148
|
+
};
|
149
|
+
|
150
|
+
for (const k in data.params) {
|
151
|
+
data.response.requesterInformation.receivedParams[k] =
|
152
|
+
data.params[k];
|
153
|
+
}
|
154
|
+
}
|
155
|
+
}
|
156
|
+
|
157
|
+
if (data.toRender === true) {
|
158
|
+
this.sendMessage(data.connection, data.response, data.messageId);
|
159
|
+
}
|
160
|
+
}
|
161
|
+
}
|
162
|
+
|
163
|
+
api.specHelper = {
|
164
|
+
returnMetadata: true,
|
165
|
+
Server: TestServer,
|
166
|
+
};
|
167
|
+
|
168
|
+
/**
|
169
|
+
* A special connection usable in tests. Create via `await api.specHelper.Connection.createAsync()`
|
170
|
+
*/
|
171
|
+
api.specHelper.Connection = class {
|
172
|
+
static async createAsync() {
|
173
|
+
const id = uuid.v4();
|
174
|
+
|
175
|
+
await api.servers.servers.testServer.buildConnection({
|
176
|
+
id: id,
|
177
|
+
fingerprint: id,
|
178
|
+
rawConnection: {},
|
179
|
+
remoteAddress: "testServer",
|
180
|
+
remotePort: 0,
|
181
|
+
});
|
182
|
+
return api.connections.connections[id];
|
183
|
+
}
|
184
|
+
};
|
185
|
+
}
|
186
|
+
|
187
|
+
async start() {
|
188
|
+
if (!this.enabled) return;
|
189
|
+
|
190
|
+
const server = new api.specHelper.Server();
|
191
|
+
server.config = { enabled: true };
|
192
|
+
await server.start(api);
|
193
|
+
api.servers.servers.testServer = server;
|
194
|
+
}
|
195
|
+
}
|
@@ -0,0 +1,253 @@
|
|
1
|
+
import * as fs from "fs";
|
2
|
+
import * as path from "path";
|
3
|
+
import * as Mime from "mime";
|
4
|
+
import { api, config, log, Initializer } from "../index";
|
5
|
+
import { Connection } from "./../classes/connection";
|
6
|
+
import { PluginConfig } from "../classes/config";
|
7
|
+
|
8
|
+
export interface StaticFileApi {
|
9
|
+
searchLocations: Array<string>;
|
10
|
+
get?: StaticFileInitializer["get"];
|
11
|
+
sendFile?: StaticFileInitializer["sendFile"];
|
12
|
+
searchPath?: StaticFileInitializer["searchPath"];
|
13
|
+
checkExistence?: StaticFileInitializer["checkExistence"];
|
14
|
+
sendFileNotFound?: StaticFileInitializer["sendFileNotFound"];
|
15
|
+
logRequest?: StaticFileInitializer["logRequest"];
|
16
|
+
fileLogger?: StaticFileInitializer["fileLogger"];
|
17
|
+
}
|
18
|
+
|
19
|
+
/**
|
20
|
+
* Contains helpers for returning flies to connections.
|
21
|
+
*/
|
22
|
+
export class StaticFileInitializer extends Initializer {
|
23
|
+
constructor() {
|
24
|
+
super();
|
25
|
+
this.name = "staticFile";
|
26
|
+
this.loadPriority = 510;
|
27
|
+
}
|
28
|
+
|
29
|
+
/**
|
30
|
+
* For a connection with `connection.params.file` set, return a file if we can find it, or a not-found message.
|
31
|
+
* `searchLocations` will be checked in the following order: first paths in this project, then plugins.
|
32
|
+
* This can be used in Actions to return files to clients. If done, set `data.toRender = false` within the action.
|
33
|
+
* return is of the form: {connection, error, fileStream, mime, length}
|
34
|
+
*/
|
35
|
+
get = async (
|
36
|
+
connection: Connection,
|
37
|
+
counter = 0,
|
38
|
+
): Promise<{
|
39
|
+
connection: Connection;
|
40
|
+
error?: any;
|
41
|
+
mime: string;
|
42
|
+
length: any;
|
43
|
+
fileStream?: fs.ReadStream;
|
44
|
+
lastModified?: Date;
|
45
|
+
}> => {
|
46
|
+
let file: string;
|
47
|
+
if (!connection.params.file || !api.staticFile.searchPath(counter)) {
|
48
|
+
return api.staticFile.sendFileNotFound(
|
49
|
+
connection,
|
50
|
+
await config.errors.fileNotProvided(connection),
|
51
|
+
);
|
52
|
+
}
|
53
|
+
|
54
|
+
if (!path.isAbsolute(connection.params.file)) {
|
55
|
+
file = path.normalize(
|
56
|
+
path.join(api.staticFile.searchPath(counter), connection.params.file),
|
57
|
+
);
|
58
|
+
} else {
|
59
|
+
file = connection.params.file;
|
60
|
+
}
|
61
|
+
|
62
|
+
if (
|
63
|
+
file.indexOf(path.normalize(api.staticFile.searchPath(counter))) !== 0
|
64
|
+
) {
|
65
|
+
return api.staticFile.get(connection, counter + 1);
|
66
|
+
} else {
|
67
|
+
const { exists, truePath } = await api.staticFile.checkExistence(file);
|
68
|
+
if (exists) {
|
69
|
+
return api.staticFile.sendFile(truePath, connection);
|
70
|
+
} else {
|
71
|
+
return api.staticFile.get(connection, counter + 1);
|
72
|
+
}
|
73
|
+
}
|
74
|
+
};
|
75
|
+
|
76
|
+
searchPath = (counter = 0) => {
|
77
|
+
if (
|
78
|
+
api.staticFile.searchLocations.length === 0 ||
|
79
|
+
counter >= api.staticFile.searchLocations.length
|
80
|
+
) {
|
81
|
+
return null;
|
82
|
+
} else {
|
83
|
+
return api.staticFile.searchLocations[counter];
|
84
|
+
}
|
85
|
+
};
|
86
|
+
|
87
|
+
sendFile = async (file: string, connection: Connection) => {
|
88
|
+
let lastModified: Date;
|
89
|
+
|
90
|
+
try {
|
91
|
+
const stats = await asyncStats(file);
|
92
|
+
const mime = Mime.getType(file);
|
93
|
+
const length = stats.size;
|
94
|
+
const start = new Date().getTime();
|
95
|
+
lastModified = stats.mtime;
|
96
|
+
|
97
|
+
const fileStream = fs.createReadStream(file);
|
98
|
+
api.staticFile.fileLogger(fileStream, connection, start, file, length);
|
99
|
+
|
100
|
+
await new Promise((resolve) => {
|
101
|
+
fileStream.on("open", () => {
|
102
|
+
resolve(null);
|
103
|
+
});
|
104
|
+
});
|
105
|
+
|
106
|
+
return { connection, fileStream, mime, length, lastModified };
|
107
|
+
} catch (error) {
|
108
|
+
return api.staticFile.sendFileNotFound(
|
109
|
+
connection,
|
110
|
+
await config.errors.fileReadError(connection, error),
|
111
|
+
);
|
112
|
+
}
|
113
|
+
};
|
114
|
+
|
115
|
+
fileLogger = (
|
116
|
+
fileStream: any,
|
117
|
+
connection: Connection,
|
118
|
+
start: number,
|
119
|
+
file: string,
|
120
|
+
length: number | bigint,
|
121
|
+
) => {
|
122
|
+
fileStream.on("end", () => {
|
123
|
+
const duration = new Date().getTime() - start;
|
124
|
+
api.staticFile.logRequest(file, connection, length, duration, true);
|
125
|
+
});
|
126
|
+
|
127
|
+
fileStream.on("error", (error: NodeJS.ErrnoException) => {
|
128
|
+
throw error;
|
129
|
+
});
|
130
|
+
};
|
131
|
+
|
132
|
+
sendFileNotFound = async (connection: Connection, errorMessage: string) => {
|
133
|
+
connection.error = new Error(errorMessage);
|
134
|
+
api.staticFile.logRequest("{not found}", connection, null, null, false);
|
135
|
+
const response = await config.errors.fileNotFound(connection);
|
136
|
+
return {
|
137
|
+
connection,
|
138
|
+
error: response,
|
139
|
+
mime: "text/html",
|
140
|
+
length: response.length,
|
141
|
+
};
|
142
|
+
};
|
143
|
+
|
144
|
+
checkExistence = async (
|
145
|
+
file: string,
|
146
|
+
): Promise<{ exists: boolean; truePath: string }> => {
|
147
|
+
try {
|
148
|
+
const stats = await asyncStats(file);
|
149
|
+
|
150
|
+
if (stats.isDirectory()) {
|
151
|
+
const indexPath = file + "/" + config.general.directoryFileType;
|
152
|
+
return api.staticFile.checkExistence(indexPath);
|
153
|
+
}
|
154
|
+
|
155
|
+
if (stats.isSymbolicLink()) {
|
156
|
+
let truePath = await asyncReadLink(file);
|
157
|
+
truePath = path.normalize(truePath);
|
158
|
+
return api.staticFile.checkExistence(truePath);
|
159
|
+
}
|
160
|
+
|
161
|
+
if (stats.isFile()) {
|
162
|
+
return { exists: true, truePath: file };
|
163
|
+
}
|
164
|
+
|
165
|
+
return { exists: false, truePath: file };
|
166
|
+
} catch (error) {
|
167
|
+
return { exists: false, truePath: file };
|
168
|
+
}
|
169
|
+
};
|
170
|
+
|
171
|
+
logRequest = (
|
172
|
+
file: string,
|
173
|
+
connection: Connection,
|
174
|
+
length: number | bigint,
|
175
|
+
duration: number,
|
176
|
+
success: boolean,
|
177
|
+
) => {
|
178
|
+
log(`[ file @ ${connection.type} ]`, config.general.fileRequestLogLevel, {
|
179
|
+
to: connection.remoteIP,
|
180
|
+
file: file,
|
181
|
+
requestedFile: connection.params.file,
|
182
|
+
size: length,
|
183
|
+
duration: duration,
|
184
|
+
success: success,
|
185
|
+
});
|
186
|
+
};
|
187
|
+
|
188
|
+
async initialize() {
|
189
|
+
api.staticFile = {
|
190
|
+
searchLocations: [],
|
191
|
+
get: this.get,
|
192
|
+
searchPath: this.searchPath,
|
193
|
+
sendFile: this.sendFile,
|
194
|
+
fileLogger: this.fileLogger,
|
195
|
+
sendFileNotFound: this.sendFileNotFound,
|
196
|
+
checkExistence: this.checkExistence,
|
197
|
+
logRequest: this.logRequest,
|
198
|
+
};
|
199
|
+
|
200
|
+
// load in the explicit public paths first
|
201
|
+
if (config.general.paths) {
|
202
|
+
config.general.paths.public.forEach(function (p: string) {
|
203
|
+
api.staticFile.searchLocations.push(path.normalize(p));
|
204
|
+
});
|
205
|
+
}
|
206
|
+
|
207
|
+
// source the public directories from plugins
|
208
|
+
for (const plugin of Object.values(config.plugins as PluginConfig)) {
|
209
|
+
if (plugin.public !== false) {
|
210
|
+
const pluginPublicPath: string = path.normalize(
|
211
|
+
path.join(plugin.path, "public"),
|
212
|
+
);
|
213
|
+
|
214
|
+
if (
|
215
|
+
fs.existsSync(pluginPublicPath) &&
|
216
|
+
api.staticFile.searchLocations.indexOf(pluginPublicPath) < 0
|
217
|
+
) {
|
218
|
+
api.staticFile.searchLocations.push(pluginPublicPath);
|
219
|
+
}
|
220
|
+
}
|
221
|
+
}
|
222
|
+
|
223
|
+
log(
|
224
|
+
"static files will be served from these directories",
|
225
|
+
"debug",
|
226
|
+
api.staticFile.searchLocations,
|
227
|
+
);
|
228
|
+
}
|
229
|
+
}
|
230
|
+
|
231
|
+
async function asyncStats(
|
232
|
+
file: string,
|
233
|
+
): Promise<ReturnType<typeof fs.statSync>> {
|
234
|
+
return new Promise((resolve, reject) => {
|
235
|
+
fs.stat(file, (error, stats) => {
|
236
|
+
if (error) {
|
237
|
+
return reject(error);
|
238
|
+
}
|
239
|
+
return resolve(stats);
|
240
|
+
});
|
241
|
+
});
|
242
|
+
}
|
243
|
+
|
244
|
+
async function asyncReadLink(file: string): Promise<string> {
|
245
|
+
return new Promise((resolve, reject) => {
|
246
|
+
fs.readlink(file, (error, linkString) => {
|
247
|
+
if (error) {
|
248
|
+
return reject(error);
|
249
|
+
}
|
250
|
+
return resolve(linkString);
|
251
|
+
});
|
252
|
+
});
|
253
|
+
}
|