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,795 @@
|
|
1
|
+
const window = { location: {} };
|
2
|
+
|
3
|
+
import { api, Process, config, chatRoom, utils } from "./../../src/index";
|
4
|
+
import "../../src/config/api";
|
5
|
+
|
6
|
+
const actionhero = new Process();
|
7
|
+
let clientA: any;
|
8
|
+
let clientB: any;
|
9
|
+
let clientC: any;
|
10
|
+
|
11
|
+
let url: string;
|
12
|
+
|
13
|
+
const localhosts = ["127.0.0.1", "::ffff:127.0.0.1", "::1"];
|
14
|
+
|
15
|
+
const connectClients = async () => {
|
16
|
+
// get ActionheroWebsocketClient in scope
|
17
|
+
const ActionheroWebsocketClient = eval(
|
18
|
+
// @ts-ignore
|
19
|
+
api.servers.servers.websocket.compileActionheroWebsocketClientJS(),
|
20
|
+
); // eslint-disable-line
|
21
|
+
|
22
|
+
const S = api.servers.servers.websocket.server.Socket;
|
23
|
+
url = "http://localhost:" + config.web!.port;
|
24
|
+
const clientAsocket = new S(url);
|
25
|
+
const clientBsocket = new S(url);
|
26
|
+
const clientCsocket = new S(url);
|
27
|
+
|
28
|
+
clientA = new ActionheroWebsocketClient({}, clientAsocket); // eslint-disable-line
|
29
|
+
clientB = new ActionheroWebsocketClient({}, clientBsocket); // eslint-disable-line
|
30
|
+
clientC = new ActionheroWebsocketClient({}, clientCsocket); // eslint-disable-line
|
31
|
+
|
32
|
+
await utils.sleep(100);
|
33
|
+
};
|
34
|
+
|
35
|
+
const awaitMethod = async (
|
36
|
+
client: any,
|
37
|
+
method: string,
|
38
|
+
returnsError = false,
|
39
|
+
): Promise<{
|
40
|
+
[key: string]: any;
|
41
|
+
}> => {
|
42
|
+
return new Promise((resolve, reject) => {
|
43
|
+
client[method]((a: any, b: any) => {
|
44
|
+
if (returnsError && a) {
|
45
|
+
return reject(a);
|
46
|
+
}
|
47
|
+
if (returnsError) {
|
48
|
+
return resolve(b);
|
49
|
+
}
|
50
|
+
return resolve(a);
|
51
|
+
});
|
52
|
+
});
|
53
|
+
};
|
54
|
+
|
55
|
+
const awaitAction = async (
|
56
|
+
client: any,
|
57
|
+
action: string,
|
58
|
+
params = {},
|
59
|
+
): Promise<any> => {
|
60
|
+
return new Promise((resolve) => {
|
61
|
+
client.action(action, params, (response: Record<string, any>) => {
|
62
|
+
return resolve(response);
|
63
|
+
});
|
64
|
+
});
|
65
|
+
};
|
66
|
+
|
67
|
+
const awaitFile = async (client: any, file: string): Promise<any> => {
|
68
|
+
return new Promise((resolve) => {
|
69
|
+
client.file(file, (response: Record<string, any>) => {
|
70
|
+
return resolve(response);
|
71
|
+
});
|
72
|
+
});
|
73
|
+
};
|
74
|
+
|
75
|
+
const awaitRoom = async (
|
76
|
+
client: any,
|
77
|
+
method: string,
|
78
|
+
room: string,
|
79
|
+
): Promise<any> => {
|
80
|
+
return new Promise((resolve) => {
|
81
|
+
client[method](room, (response: Record<string, any>) => {
|
82
|
+
return resolve(response);
|
83
|
+
});
|
84
|
+
});
|
85
|
+
};
|
86
|
+
|
87
|
+
describe("Server: Web Socket", () => {
|
88
|
+
beforeAll(async () => {
|
89
|
+
await actionhero.start();
|
90
|
+
await api.redis.clients.client.flushdb();
|
91
|
+
await api.redis.clients.client.flushdb();
|
92
|
+
await chatRoom.add("defaultRoom");
|
93
|
+
await chatRoom.add("otherRoom");
|
94
|
+
|
95
|
+
url = "http://localhost:" + config.web!.port;
|
96
|
+
config.websocket!.clientUrl = url;
|
97
|
+
await connectClients();
|
98
|
+
});
|
99
|
+
|
100
|
+
afterAll(async () => await actionhero.stop());
|
101
|
+
|
102
|
+
test("socket client connections should work: client 1", async () => {
|
103
|
+
const data = await awaitMethod(clientA, "connect", true);
|
104
|
+
expect(data.context).toEqual("response");
|
105
|
+
expect(data.data.totalActions).toEqual(0);
|
106
|
+
expect(clientA.welcomeMessage).toEqual("Welcome to the actionhero api");
|
107
|
+
});
|
108
|
+
|
109
|
+
test("socket client connections should work: client 2", async () => {
|
110
|
+
const data = await awaitMethod(clientB, "connect", true);
|
111
|
+
expect(data.context).toEqual("response");
|
112
|
+
expect(data.data.totalActions).toEqual(0);
|
113
|
+
expect(clientB.welcomeMessage).toEqual("Welcome to the actionhero api");
|
114
|
+
});
|
115
|
+
|
116
|
+
test("socket client connections should work: client 3", async () => {
|
117
|
+
const data = await awaitMethod(clientC, "connect", true);
|
118
|
+
expect(data.context).toEqual("response");
|
119
|
+
expect(data.data.totalActions).toEqual(0);
|
120
|
+
expect(clientC.welcomeMessage).toEqual("Welcome to the actionhero api");
|
121
|
+
});
|
122
|
+
|
123
|
+
describe("with connection", () => {
|
124
|
+
beforeAll(async () => {
|
125
|
+
await awaitMethod(clientA, "connect", true);
|
126
|
+
await awaitMethod(clientB, "connect", true);
|
127
|
+
await awaitMethod(clientC, "connect", true);
|
128
|
+
});
|
129
|
+
|
130
|
+
test("I can get my connection details", async () => {
|
131
|
+
const response = await awaitMethod(clientA, "detailsView");
|
132
|
+
expect(response.data.connectedAt).toBeLessThan(new Date().getTime());
|
133
|
+
expect(localhosts).toContain(response.data.remoteIP);
|
134
|
+
});
|
135
|
+
|
136
|
+
test("can run actions with errors", async () => {
|
137
|
+
const response = await awaitAction(clientA, "cacheTest");
|
138
|
+
expect(response.error).toEqual(
|
139
|
+
"key is a required parameter for this action",
|
140
|
+
);
|
141
|
+
});
|
142
|
+
|
143
|
+
test("properly handles duplicate room commands at the same time", async () => {
|
144
|
+
awaitRoom(clientA, "roomAdd", "defaultRoom");
|
145
|
+
awaitRoom(clientA, "roomAdd", "defaultRoom");
|
146
|
+
|
147
|
+
await utils.sleep(500);
|
148
|
+
|
149
|
+
expect(clientA.rooms).toEqual(["defaultRoom"]);
|
150
|
+
});
|
151
|
+
|
152
|
+
test("properly responds with messageId", async () => {
|
153
|
+
let aTime: Date;
|
154
|
+
let bTime: Date;
|
155
|
+
const startingMessageId = clientA.messageId;
|
156
|
+
let responseA: Record<string, any>;
|
157
|
+
let responseB: Record<string, any>;
|
158
|
+
awaitRoom(clientA, "roomAdd", "defaultRoom"); // fast
|
159
|
+
const promiseA = awaitAction(clientA, "sleepTest"); // slow
|
160
|
+
awaitRoom(clientA, "roomAdd", "defaultRoom"); // fast
|
161
|
+
const promiseB = awaitAction(clientA, "randomNumber"); // fast
|
162
|
+
|
163
|
+
promiseA.then((data) => {
|
164
|
+
responseA = data;
|
165
|
+
aTime = new Date();
|
166
|
+
});
|
167
|
+
|
168
|
+
promiseB.then((data) => {
|
169
|
+
responseB = data;
|
170
|
+
bTime = new Date();
|
171
|
+
});
|
172
|
+
|
173
|
+
await utils.sleep(2001);
|
174
|
+
|
175
|
+
//@ts-ignore
|
176
|
+
expect(responseA.messageId).toEqual(startingMessageId + 2);
|
177
|
+
//@ts-ignore
|
178
|
+
expect(responseB.messageId).toEqual(startingMessageId + 4);
|
179
|
+
//@ts-ignore
|
180
|
+
expect(aTime.getTime()).toBeGreaterThan(bTime.getTime());
|
181
|
+
});
|
182
|
+
|
183
|
+
test("messageId can be configurable", async () => {
|
184
|
+
const response = await awaitAction(clientA, "randomNumber", {
|
185
|
+
messageId: "aaa",
|
186
|
+
});
|
187
|
+
expect(response.messageId).toBe("aaa");
|
188
|
+
});
|
189
|
+
|
190
|
+
test("can run actions properly without params", async () => {
|
191
|
+
const response = await awaitAction(clientA, "randomNumber");
|
192
|
+
expect(response.error).toBeUndefined();
|
193
|
+
expect(response.randomNumber).toBeTruthy();
|
194
|
+
});
|
195
|
+
|
196
|
+
test("can run actions properly with params", async () => {
|
197
|
+
const response = await awaitAction(clientA, "cacheTest", {
|
198
|
+
key: "test key",
|
199
|
+
value: "test value",
|
200
|
+
});
|
201
|
+
expect(response.error).toBeUndefined();
|
202
|
+
expect(response.cacheTestResults).toBeTruthy();
|
203
|
+
});
|
204
|
+
|
205
|
+
test("does not have sticky params", async () => {
|
206
|
+
const response = await awaitAction(clientA, "cacheTest", {
|
207
|
+
key: "test key",
|
208
|
+
value: "test value",
|
209
|
+
});
|
210
|
+
expect(response.cacheTestResults.loadResp.key).toEqual(
|
211
|
+
"cacheTest_test key",
|
212
|
+
);
|
213
|
+
expect(response.cacheTestResults.loadResp.value).toEqual("test value");
|
214
|
+
const responseAgain = await awaitAction(clientA, "cacheTest");
|
215
|
+
expect(responseAgain.error).toEqual(
|
216
|
+
"key is a required parameter for this action",
|
217
|
+
);
|
218
|
+
});
|
219
|
+
|
220
|
+
test("will limit how many simultaneous connections I can have", async () => {
|
221
|
+
const responses: Record<string, any>[] = [];
|
222
|
+
clientA.action(
|
223
|
+
"sleepTest",
|
224
|
+
{ sleepDuration: 100 },
|
225
|
+
(response: Record<string, any>) => {
|
226
|
+
responses.push(response);
|
227
|
+
},
|
228
|
+
);
|
229
|
+
clientA.action(
|
230
|
+
"sleepTest",
|
231
|
+
{ sleepDuration: 200 },
|
232
|
+
(response: Record<string, any>) => {
|
233
|
+
responses.push(response);
|
234
|
+
},
|
235
|
+
);
|
236
|
+
clientA.action(
|
237
|
+
"sleepTest",
|
238
|
+
{ sleepDuration: 300 },
|
239
|
+
(response: Record<string, any>) => {
|
240
|
+
responses.push(response);
|
241
|
+
},
|
242
|
+
);
|
243
|
+
clientA.action(
|
244
|
+
"sleepTest",
|
245
|
+
{ sleepDuration: 400 },
|
246
|
+
(response: Record<string, any>) => {
|
247
|
+
responses.push(response);
|
248
|
+
},
|
249
|
+
);
|
250
|
+
clientA.action(
|
251
|
+
"sleepTest",
|
252
|
+
{ sleepDuration: 500 },
|
253
|
+
(response: Record<string, any>) => {
|
254
|
+
responses.push(response);
|
255
|
+
},
|
256
|
+
);
|
257
|
+
clientA.action(
|
258
|
+
"sleepTest",
|
259
|
+
{ sleepDuration: 600 },
|
260
|
+
(response: Record<string, any>) => {
|
261
|
+
responses.push(response);
|
262
|
+
},
|
263
|
+
);
|
264
|
+
|
265
|
+
await utils.sleep(1000);
|
266
|
+
|
267
|
+
expect(responses).toHaveLength(6);
|
268
|
+
for (const i in responses) {
|
269
|
+
const response = responses[i];
|
270
|
+
if (i.toString() === "0") {
|
271
|
+
expect(response.error).toEqual("you have too many pending requests");
|
272
|
+
} else {
|
273
|
+
expect(response.error).toBeUndefined();
|
274
|
+
}
|
275
|
+
}
|
276
|
+
});
|
277
|
+
|
278
|
+
describe("files", () => {
|
279
|
+
test("can request file data", async () => {
|
280
|
+
const data = await awaitFile(clientA, "simple.html");
|
281
|
+
expect(data.error).toBeUndefined();
|
282
|
+
expect(data.content).toContain("<h1>Actionhero</h1>");
|
283
|
+
expect(data.mime).toEqual("text/html");
|
284
|
+
expect(data.length).toEqual(101);
|
285
|
+
});
|
286
|
+
|
287
|
+
test("missing files", async () => {
|
288
|
+
const data = await awaitFile(clientA, "missing.html");
|
289
|
+
expect(data.error).toEqual("that file is not found");
|
290
|
+
expect(data.mime).toEqual("text/html");
|
291
|
+
expect(data.content).toBeNull();
|
292
|
+
});
|
293
|
+
});
|
294
|
+
|
295
|
+
describe("chat", () => {
|
296
|
+
beforeAll(() => {
|
297
|
+
chatRoom.addMiddleware({
|
298
|
+
name: "join chat middleware",
|
299
|
+
join: async (connection: any, room: string) => {
|
300
|
+
await api.chatRoom.broadcast(
|
301
|
+
//@ts-ignore
|
302
|
+
null,
|
303
|
+
room,
|
304
|
+
`I have entered the room: ${connection.id}`,
|
305
|
+
);
|
306
|
+
},
|
307
|
+
});
|
308
|
+
|
309
|
+
chatRoom.addMiddleware({
|
310
|
+
name: "leave chat middleware",
|
311
|
+
leave: async (connection: any, room: string) => {
|
312
|
+
api.chatRoom.broadcast(
|
313
|
+
//@ts-ignore
|
314
|
+
null,
|
315
|
+
room,
|
316
|
+
`I have left the room: ${connection.id}`,
|
317
|
+
);
|
318
|
+
},
|
319
|
+
});
|
320
|
+
});
|
321
|
+
|
322
|
+
afterAll(() => {
|
323
|
+
api.chatRoom.middleware = {};
|
324
|
+
api.chatRoom.globalMiddleware = [];
|
325
|
+
});
|
326
|
+
|
327
|
+
beforeEach(async () => {
|
328
|
+
await awaitRoom(clientA, "roomAdd", "defaultRoom");
|
329
|
+
await awaitRoom(clientB, "roomAdd", "defaultRoom");
|
330
|
+
await awaitRoom(clientC, "roomAdd", "defaultRoom");
|
331
|
+
// timeout to skip welcome messages as clients join rooms
|
332
|
+
await utils.sleep(100);
|
333
|
+
});
|
334
|
+
|
335
|
+
afterEach(async () => {
|
336
|
+
await awaitRoom(clientA, "roomLeave", "defaultRoom");
|
337
|
+
await awaitRoom(clientB, "roomLeave", "defaultRoom");
|
338
|
+
await awaitRoom(clientC, "roomLeave", "defaultRoom");
|
339
|
+
await awaitRoom(clientA, "roomLeave", "otherRoom");
|
340
|
+
await awaitRoom(clientB, "roomLeave", "otherRoom");
|
341
|
+
await awaitRoom(clientC, "roomLeave", "otherRoom");
|
342
|
+
});
|
343
|
+
|
344
|
+
test("can change rooms and get room details", async () => {
|
345
|
+
await awaitRoom(clientA, "roomAdd", "otherRoom");
|
346
|
+
const response = await awaitMethod(clientA, "detailsView");
|
347
|
+
expect(response.error).toBeUndefined();
|
348
|
+
expect(response.data.rooms[0]).toEqual("defaultRoom");
|
349
|
+
expect(response.data.rooms[1]).toEqual("otherRoom");
|
350
|
+
|
351
|
+
const roomResponse = await awaitRoom(clientA, "roomView", "otherRoom");
|
352
|
+
expect(roomResponse.data.membersCount).toEqual(1);
|
353
|
+
});
|
354
|
+
|
355
|
+
test("will update client room info when they change rooms", async () => {
|
356
|
+
expect(clientA.rooms[0]).toEqual("defaultRoom");
|
357
|
+
expect(clientA.rooms[1]).toBeUndefined();
|
358
|
+
const response = await awaitRoom(clientA, "roomAdd", "otherRoom");
|
359
|
+
expect(response.error).toBeUndefined();
|
360
|
+
expect(clientA.rooms[0]).toEqual("defaultRoom");
|
361
|
+
expect(clientA.rooms[1]).toEqual("otherRoom");
|
362
|
+
|
363
|
+
const leaveResponse = await awaitRoom(
|
364
|
+
clientA,
|
365
|
+
"roomLeave",
|
366
|
+
"defaultRoom",
|
367
|
+
);
|
368
|
+
expect(leaveResponse.error).toBeUndefined();
|
369
|
+
expect(clientA.rooms[0]).toEqual("otherRoom");
|
370
|
+
expect(clientA.rooms[1]).toBeUndefined();
|
371
|
+
});
|
372
|
+
|
373
|
+
test("clients can talk to each other", async () => {
|
374
|
+
await new Promise((resolve) => {
|
375
|
+
const listener = (response: Record<string, any>) => {
|
376
|
+
clientA.removeListener("say", listener);
|
377
|
+
expect(response.context).toEqual("user");
|
378
|
+
expect(response.message).toEqual("hello from client 2");
|
379
|
+
resolve(null);
|
380
|
+
};
|
381
|
+
|
382
|
+
clientA.on("say", listener);
|
383
|
+
clientB.say("defaultRoom", "hello from client 2");
|
384
|
+
});
|
385
|
+
});
|
386
|
+
|
387
|
+
test("The client say method does not rely on argument order", async () => {
|
388
|
+
await new Promise((resolve) => {
|
389
|
+
const listener = (response: Record<string, any>) => {
|
390
|
+
clientA.removeListener("say", listener);
|
391
|
+
expect(response.context).toEqual("user");
|
392
|
+
expect(response.message).toEqual("hello from client 2");
|
393
|
+
resolve(null);
|
394
|
+
};
|
395
|
+
|
396
|
+
clientB.say = (room: string, message: Record<string, any>) => {
|
397
|
+
clientB.send({ message: message, room: room, event: "say" });
|
398
|
+
};
|
399
|
+
|
400
|
+
clientA.on("say", listener);
|
401
|
+
clientB.say("defaultRoom", "hello from client 2");
|
402
|
+
});
|
403
|
+
});
|
404
|
+
|
405
|
+
test("connections are notified when I join a room", async () => {
|
406
|
+
await new Promise((resolve) => {
|
407
|
+
const listener = (response: Record<string, any>) => {
|
408
|
+
clientA.removeListener("say", listener);
|
409
|
+
expect(response.context).toEqual("user");
|
410
|
+
expect(response.message).toEqual(
|
411
|
+
"I have entered the room: " + clientB.id,
|
412
|
+
);
|
413
|
+
resolve(null);
|
414
|
+
};
|
415
|
+
|
416
|
+
clientA.roomAdd("otherRoom", () => {
|
417
|
+
clientA.on("say", listener);
|
418
|
+
clientB.roomAdd("otherRoom");
|
419
|
+
});
|
420
|
+
});
|
421
|
+
});
|
422
|
+
|
423
|
+
test("connections are notified when I leave a room", async () => {
|
424
|
+
await new Promise((resolve) => {
|
425
|
+
const listener = (response: Record<string, any>) => {
|
426
|
+
clientA.removeListener("say", listener);
|
427
|
+
expect(response.context).toEqual("user");
|
428
|
+
expect(response.message).toEqual(
|
429
|
+
"I have left the room: " + clientB.id,
|
430
|
+
);
|
431
|
+
resolve(null);
|
432
|
+
};
|
433
|
+
|
434
|
+
clientA.on("say", listener);
|
435
|
+
clientB.roomLeave("defaultRoom");
|
436
|
+
});
|
437
|
+
});
|
438
|
+
|
439
|
+
test("will not get messages for rooms I am not in", async () => {
|
440
|
+
const response = await awaitRoom(clientB, "roomAdd", "otherRoom");
|
441
|
+
expect(response.error).toBeUndefined();
|
442
|
+
expect(clientB.rooms.length).toEqual(2);
|
443
|
+
expect(clientC.rooms.length).toEqual(1);
|
444
|
+
|
445
|
+
const listener = () => {
|
446
|
+
clientC.removeListener("say", listener);
|
447
|
+
throw new Error("should not get here");
|
448
|
+
};
|
449
|
+
|
450
|
+
clientC.on("say", listener);
|
451
|
+
|
452
|
+
clientB.say("otherRoom", "you should not hear this");
|
453
|
+
await utils.sleep(1000);
|
454
|
+
clientC.removeListener("say", listener);
|
455
|
+
});
|
456
|
+
|
457
|
+
test("connections can see member counts changing within rooms as folks join and leave", async () => {
|
458
|
+
const response = await awaitRoom(clientA, "roomView", "defaultRoom");
|
459
|
+
expect(response.data.membersCount).toEqual(3);
|
460
|
+
await awaitRoom(clientB, "roomLeave", "defaultRoom");
|
461
|
+
const responseAgain = await awaitRoom(
|
462
|
+
clientA,
|
463
|
+
"roomView",
|
464
|
+
"defaultRoom",
|
465
|
+
);
|
466
|
+
expect(responseAgain.data.membersCount).toEqual(2);
|
467
|
+
});
|
468
|
+
|
469
|
+
describe("middleware - say and onSayReceive", () => {
|
470
|
+
afterEach(() => {
|
471
|
+
api.chatRoom.middleware = {};
|
472
|
+
api.chatRoom.globalMiddleware = [];
|
473
|
+
});
|
474
|
+
|
475
|
+
test("each listener receive custom message", async () => {
|
476
|
+
let messagesReceived = 0;
|
477
|
+
chatRoom.addMiddleware({
|
478
|
+
name: "say for each",
|
479
|
+
say: async (
|
480
|
+
connection: any,
|
481
|
+
room: string,
|
482
|
+
messagePayload: Record<string, any>,
|
483
|
+
) => {
|
484
|
+
messagePayload.message += " - To: " + connection.id;
|
485
|
+
return messagePayload;
|
486
|
+
},
|
487
|
+
});
|
488
|
+
|
489
|
+
const listenerA = (response: Record<string, any>) => {
|
490
|
+
messagesReceived++;
|
491
|
+
clientA.removeListener("say", listenerA);
|
492
|
+
expect(response.message).toEqual(
|
493
|
+
"Test Message - To: " + clientA.id,
|
494
|
+
); // clientA.id (Receiver)
|
495
|
+
};
|
496
|
+
|
497
|
+
const listenerB = (response: Record<string, any>) => {
|
498
|
+
messagesReceived++;
|
499
|
+
clientB.removeListener("say", listenerB);
|
500
|
+
expect(response.message).toEqual(
|
501
|
+
"Test Message - To: " + clientB.id,
|
502
|
+
); // clientB.id (Receiver)
|
503
|
+
};
|
504
|
+
|
505
|
+
const listenerC = (response: Record<string, any>) => {
|
506
|
+
messagesReceived++;
|
507
|
+
clientC.removeListener("say", listenerC);
|
508
|
+
expect(response.message).toEqual(
|
509
|
+
"Test Message - To: " + clientC.id,
|
510
|
+
); // clientC.id (Receiver)
|
511
|
+
};
|
512
|
+
|
513
|
+
clientA.on("say", listenerA);
|
514
|
+
clientB.on("say", listenerB);
|
515
|
+
clientC.on("say", listenerC);
|
516
|
+
clientB.say("defaultRoom", "Test Message");
|
517
|
+
|
518
|
+
await utils.sleep(1000);
|
519
|
+
|
520
|
+
expect(messagesReceived).toEqual(3);
|
521
|
+
});
|
522
|
+
|
523
|
+
test("only one message should be received per connection", async () => {
|
524
|
+
let firstSayCall = true;
|
525
|
+
chatRoom.addMiddleware({
|
526
|
+
name: "first say middleware",
|
527
|
+
say: async (
|
528
|
+
connection: any,
|
529
|
+
room: string,
|
530
|
+
messagePayload: Record<string, any>,
|
531
|
+
) => {
|
532
|
+
if (firstSayCall) {
|
533
|
+
firstSayCall = false;
|
534
|
+
await utils.sleep(200);
|
535
|
+
}
|
536
|
+
return messagePayload;
|
537
|
+
},
|
538
|
+
});
|
539
|
+
|
540
|
+
let messagesReceived = 0;
|
541
|
+
const listenerA = () => {
|
542
|
+
clientA.removeListener("say", listenerA);
|
543
|
+
messagesReceived += 1;
|
544
|
+
};
|
545
|
+
|
546
|
+
const listenerB = () => {
|
547
|
+
clientB.removeListener("say", listenerB);
|
548
|
+
messagesReceived += 2;
|
549
|
+
};
|
550
|
+
|
551
|
+
const listenerC = () => {
|
552
|
+
clientC.removeListener("say", listenerC);
|
553
|
+
messagesReceived += 4;
|
554
|
+
};
|
555
|
+
|
556
|
+
clientA.on("say", listenerA);
|
557
|
+
clientB.on("say", listenerB);
|
558
|
+
clientC.on("say", listenerC);
|
559
|
+
clientB.say("defaultRoom", "Test Message");
|
560
|
+
|
561
|
+
await utils.sleep(1000);
|
562
|
+
|
563
|
+
expect(messagesReceived).toEqual(7);
|
564
|
+
});
|
565
|
+
|
566
|
+
test("each listener receive same custom message", async () => {
|
567
|
+
let messagesReceived = 0;
|
568
|
+
chatRoom.addMiddleware({
|
569
|
+
name: "say for each",
|
570
|
+
onSayReceive: (
|
571
|
+
connection: any,
|
572
|
+
room: string,
|
573
|
+
messagePayload: Record<string, any>,
|
574
|
+
) => {
|
575
|
+
messagePayload.message += " - To: " + connection.id;
|
576
|
+
return messagePayload;
|
577
|
+
},
|
578
|
+
});
|
579
|
+
|
580
|
+
const listenerA = (response: Record<string, any>) => {
|
581
|
+
messagesReceived++;
|
582
|
+
clientA.removeListener("say", listenerA);
|
583
|
+
expect(response.message).toEqual(
|
584
|
+
"Test Message - To: " + clientB.id,
|
585
|
+
); // clientB.id (Sender)
|
586
|
+
};
|
587
|
+
|
588
|
+
const listenerB = (response: Record<string, any>) => {
|
589
|
+
messagesReceived++;
|
590
|
+
clientB.removeListener("say", listenerB);
|
591
|
+
expect(response.message).toEqual(
|
592
|
+
"Test Message - To: " + clientB.id,
|
593
|
+
); // clientB.id (Sender)
|
594
|
+
};
|
595
|
+
|
596
|
+
const listenerC = (response: Record<string, any>) => {
|
597
|
+
messagesReceived++;
|
598
|
+
clientC.removeListener("say", listenerC);
|
599
|
+
expect(response.message).toEqual(
|
600
|
+
"Test Message - To: " + clientB.id,
|
601
|
+
); // clientB.id (Sender)
|
602
|
+
};
|
603
|
+
|
604
|
+
clientA.on("say", listenerA);
|
605
|
+
clientB.on("say", listenerB);
|
606
|
+
clientC.on("say", listenerC);
|
607
|
+
clientB.say("defaultRoom", "Test Message");
|
608
|
+
|
609
|
+
await utils.sleep(1000);
|
610
|
+
|
611
|
+
expect(messagesReceived).toEqual(3);
|
612
|
+
});
|
613
|
+
|
614
|
+
test("blocking middleware return an error", async () => {
|
615
|
+
chatRoom.addMiddleware({
|
616
|
+
name: "blocking chat middleware",
|
617
|
+
join: () => {
|
618
|
+
throw new Error("joining rooms blocked");
|
619
|
+
},
|
620
|
+
});
|
621
|
+
|
622
|
+
const joinResponse = await awaitRoom(clientA, "roomAdd", "otherRoom");
|
623
|
+
expect(joinResponse.error).toEqual("Error: joining rooms blocked");
|
624
|
+
expect(joinResponse.status).toEqual("Error: joining rooms blocked");
|
625
|
+
});
|
626
|
+
});
|
627
|
+
|
628
|
+
test("say middleware can return null to particular receivers message", async () => {
|
629
|
+
chatRoom.addMiddleware({
|
630
|
+
name: "silencing chat middleware",
|
631
|
+
say: (
|
632
|
+
connection: any,
|
633
|
+
room: string,
|
634
|
+
payload: Record<string, any>,
|
635
|
+
) => {
|
636
|
+
if (connection.id === clientB.id) {
|
637
|
+
return null;
|
638
|
+
}
|
639
|
+
return payload;
|
640
|
+
},
|
641
|
+
});
|
642
|
+
let messagesReceivedA = 0;
|
643
|
+
let messagesReceivedB = 0;
|
644
|
+
let messagesReceivedC = 0;
|
645
|
+
const listenerA = () => {
|
646
|
+
clientA.removeListener("say", listenerA);
|
647
|
+
messagesReceivedA++;
|
648
|
+
};
|
649
|
+
const listenerB = () => {
|
650
|
+
clientB.removeListener("say", listenerB);
|
651
|
+
messagesReceivedB++;
|
652
|
+
};
|
653
|
+
const listenerC = () => {
|
654
|
+
clientC.removeListener("say", listenerC);
|
655
|
+
messagesReceivedC++;
|
656
|
+
};
|
657
|
+
clientA.on("say", listenerA);
|
658
|
+
clientB.on("say", listenerB);
|
659
|
+
clientC.on("say", listenerC);
|
660
|
+
|
661
|
+
await utils.sleep(500); // Give a chance for the welcome message to pass
|
662
|
+
// Reset the message counts (in case welcome message incremented them)
|
663
|
+
messagesReceivedA = 0;
|
664
|
+
messagesReceivedB = 0;
|
665
|
+
messagesReceivedC = 0;
|
666
|
+
|
667
|
+
clientA.say("defaultRoom", "Test Message");
|
668
|
+
|
669
|
+
await utils.sleep(1000);
|
670
|
+
|
671
|
+
// B's message will have been squelched
|
672
|
+
expect(messagesReceivedB).toEqual(0);
|
673
|
+
// I don't know why these below are not 1. A and C's listener are getting called
|
674
|
+
// twice event though only one say is happening.
|
675
|
+
expect(messagesReceivedA).toEqual(1);
|
676
|
+
expect(messagesReceivedC).toEqual(1);
|
677
|
+
});
|
678
|
+
test("sayReceive middleware can return null to silence a message", async () => {
|
679
|
+
chatRoom.addMiddleware({
|
680
|
+
name: "silencing chat middleware",
|
681
|
+
onSayReceive: () => {},
|
682
|
+
});
|
683
|
+
let messagesReceivedA = 0;
|
684
|
+
let messagesReceivedB = 0;
|
685
|
+
let messagesReceivedC = 0;
|
686
|
+
const listenerA = () => {
|
687
|
+
messagesReceivedA++;
|
688
|
+
};
|
689
|
+
const listenerB = () => {
|
690
|
+
messagesReceivedB++;
|
691
|
+
};
|
692
|
+
const listenerC = () => {
|
693
|
+
messagesReceivedC++;
|
694
|
+
};
|
695
|
+
clientA.on("say", listenerA);
|
696
|
+
clientB.on("say", listenerB);
|
697
|
+
clientC.on("say", listenerC);
|
698
|
+
clientA.say("defaultRoom", "Test Message");
|
699
|
+
|
700
|
+
await utils.sleep(1000);
|
701
|
+
|
702
|
+
// all messages will have been squelched
|
703
|
+
expect(messagesReceivedB).toEqual(0);
|
704
|
+
expect(messagesReceivedA).toEqual(0);
|
705
|
+
expect(messagesReceivedC).toEqual(0);
|
706
|
+
});
|
707
|
+
});
|
708
|
+
|
709
|
+
describe("param collisions", () => {
|
710
|
+
let originalSimultaneousActions: number;
|
711
|
+
|
712
|
+
beforeAll(() => {
|
713
|
+
originalSimultaneousActions = config.general!.simultaneousActions;
|
714
|
+
config.general!.simultaneousActions = 99999999;
|
715
|
+
});
|
716
|
+
|
717
|
+
afterAll(() => {
|
718
|
+
config.general!.simultaneousActions = originalSimultaneousActions;
|
719
|
+
});
|
720
|
+
|
721
|
+
test("will not have param collisions", async () => {
|
722
|
+
let completed = 0;
|
723
|
+
let started = 0;
|
724
|
+
const sleeps = [100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110];
|
725
|
+
|
726
|
+
await new Promise((resolve) => {
|
727
|
+
const toComplete = (sleep: number, response: Record<string, any>) => {
|
728
|
+
expect(sleep).toEqual(response.sleepDuration);
|
729
|
+
completed++;
|
730
|
+
if (completed === started) {
|
731
|
+
resolve(null);
|
732
|
+
}
|
733
|
+
};
|
734
|
+
|
735
|
+
sleeps.forEach((sleep) => {
|
736
|
+
started++;
|
737
|
+
clientA.action(
|
738
|
+
"sleepTest",
|
739
|
+
{ sleepDuration: sleep },
|
740
|
+
(response: Record<string, any>) => {
|
741
|
+
toComplete(sleep, response);
|
742
|
+
},
|
743
|
+
);
|
744
|
+
});
|
745
|
+
});
|
746
|
+
});
|
747
|
+
});
|
748
|
+
|
749
|
+
describe("disconnect", () => {
|
750
|
+
beforeEach(async () => {
|
751
|
+
try {
|
752
|
+
clientA.disconnect();
|
753
|
+
clientB.disconnect();
|
754
|
+
clientC.disconnect();
|
755
|
+
} catch (e) {}
|
756
|
+
|
757
|
+
await connectClients();
|
758
|
+
clientA.connect();
|
759
|
+
clientB.connect();
|
760
|
+
clientC.connect();
|
761
|
+
await utils.sleep(500);
|
762
|
+
});
|
763
|
+
|
764
|
+
test("client can disconnect", async () => {
|
765
|
+
expect(api.servers.servers.websocket.connections().length).toEqual(3);
|
766
|
+
|
767
|
+
clientA.disconnect();
|
768
|
+
clientB.disconnect();
|
769
|
+
clientC.disconnect();
|
770
|
+
|
771
|
+
await utils.sleep(500);
|
772
|
+
|
773
|
+
expect(api.servers.servers.websocket.connections().length).toEqual(0);
|
774
|
+
});
|
775
|
+
|
776
|
+
test("can be sent disconnect events from the server", async () => {
|
777
|
+
const response = await awaitMethod(clientA, "detailsView");
|
778
|
+
expect(localhosts).toContain(response.data.remoteIP);
|
779
|
+
|
780
|
+
let count = 0;
|
781
|
+
for (const id in api.connections.connections) {
|
782
|
+
count++;
|
783
|
+
api.connections.connections[id].destroy();
|
784
|
+
}
|
785
|
+
expect(count).toEqual(3);
|
786
|
+
|
787
|
+
clientA.detailsView(() => {
|
788
|
+
throw new Error("should not get response");
|
789
|
+
});
|
790
|
+
|
791
|
+
await utils.sleep(500);
|
792
|
+
});
|
793
|
+
});
|
794
|
+
});
|
795
|
+
});
|