systemlynx 1.19.12 → 1.21.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/API.md +741 -118
- package/README.md +106 -3
- package/RFCs/001-websocket-named-events-room-scoping.md +133 -0
- package/RFCs/002-module-handle-and-internal-error-events.md +135 -0
- package/index.test.js +10 -2
- package/package.json +1 -1
- package/systemlynx/App/App.js +14 -0
- package/systemlynx/App/tests/App.test.js +138 -57
- package/systemlynx/Client/components/ServiceRequestHandler.js +2 -2
- package/systemlynx/Client/components/SocketDispatcher.js +80 -1
- package/systemlynx/Client/tests/Client.test.js +4 -0
- package/systemlynx/Client/tests/SocketDispatcher.test.js +12 -6
- package/systemlynx/Dispatcher/Dispatcher.js +71 -27
- package/systemlynx/Dispatcher/Dispatcher.test.js +87 -4
- package/systemlynx/LoadBalancer/tests/LoadBalancer.test.js +4 -0
- package/systemlynx/ServerManager/components/Router.js +13 -0
- package/systemlynx/ServerManager/components/SocketEmitter.js +7 -1
- package/systemlynx/ServerManager/tests/SocketEmitter.test.js +12 -9
- package/systemlynx/Service/Service.test.js +3 -1
|
@@ -12,19 +12,25 @@ describe("createApp()", () => {
|
|
|
12
12
|
.that.has.all.keys(
|
|
13
13
|
"module",
|
|
14
14
|
"on",
|
|
15
|
+
"once",
|
|
15
16
|
"emit",
|
|
16
17
|
"before",
|
|
17
18
|
"after",
|
|
18
19
|
"$clearEvent",
|
|
20
|
+
"destroy",
|
|
19
21
|
"use",
|
|
20
22
|
"startService",
|
|
21
23
|
"loadService",
|
|
22
24
|
"onLoad",
|
|
23
25
|
"config",
|
|
24
26
|
"server",
|
|
25
|
-
"WebSocket"
|
|
27
|
+
"WebSocket",
|
|
28
|
+
"getModule",
|
|
29
|
+
"getModules"
|
|
26
30
|
)
|
|
27
31
|
.that.respondsTo("module")
|
|
32
|
+
.that.respondsTo("getModule")
|
|
33
|
+
.that.respondsTo("getModules")
|
|
28
34
|
.that.respondsTo("on")
|
|
29
35
|
.that.respondsTo("emit")
|
|
30
36
|
.that.respondsTo("$clearEvent")
|
|
@@ -62,7 +68,9 @@ describe("App: Loading Services", () => {
|
|
|
62
68
|
.that.has.all.keys(
|
|
63
69
|
"emit",
|
|
64
70
|
"on",
|
|
71
|
+
"once",
|
|
65
72
|
"$clearEvent",
|
|
73
|
+
"destroy",
|
|
66
74
|
"resetConnection",
|
|
67
75
|
"disconnect",
|
|
68
76
|
"headers",
|
|
@@ -103,7 +111,9 @@ describe("App: Loading Services", () => {
|
|
|
103
111
|
.that.has.all.keys(
|
|
104
112
|
"emit",
|
|
105
113
|
"on",
|
|
114
|
+
"once",
|
|
106
115
|
"$clearEvent",
|
|
116
|
+
"destroy",
|
|
107
117
|
"resetConnection",
|
|
108
118
|
"disconnect",
|
|
109
119
|
"headers",
|
|
@@ -136,48 +146,52 @@ describe("App: Loading Services", () => {
|
|
|
136
146
|
|
|
137
147
|
await new Promise((resolve) => {
|
|
138
148
|
const App = createApp();
|
|
139
|
-
App.loadService("test", url)
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
149
|
+
App.loadService("test", url);
|
|
150
|
+
App.on("service_loaded", (test) => {
|
|
151
|
+
expect(test)
|
|
152
|
+
.to.be.an("object")
|
|
153
|
+
.that.has.all.keys(
|
|
154
|
+
"emit",
|
|
155
|
+
"on",
|
|
156
|
+
"once",
|
|
157
|
+
"$clearEvent",
|
|
158
|
+
"destroy",
|
|
159
|
+
"resetConnection",
|
|
160
|
+
"disconnect",
|
|
161
|
+
"headers",
|
|
162
|
+
"setHeaders",
|
|
163
|
+
"mod"
|
|
164
|
+
)
|
|
165
|
+
.that.respondsTo("emit")
|
|
166
|
+
.that.respondsTo("$clearEvent")
|
|
167
|
+
.that.respondsTo("on")
|
|
168
|
+
.that.respondsTo("resetConnection")
|
|
169
|
+
.that.respondsTo("headers")
|
|
170
|
+
.that.respondsTo("setHeaders");
|
|
171
|
+
});
|
|
172
|
+
App.on("service_loaded:test", (test) => {
|
|
173
|
+
expect(test)
|
|
174
|
+
.to.be.an("object")
|
|
175
|
+
.that.has.all.keys(
|
|
176
|
+
"emit",
|
|
177
|
+
"on",
|
|
178
|
+
"once",
|
|
179
|
+
"$clearEvent",
|
|
180
|
+
"destroy",
|
|
181
|
+
"resetConnection",
|
|
182
|
+
"disconnect",
|
|
183
|
+
"headers",
|
|
184
|
+
"setHeaders",
|
|
185
|
+
"mod"
|
|
186
|
+
)
|
|
187
|
+
.that.respondsTo("emit")
|
|
188
|
+
.that.respondsTo("$clearEvent")
|
|
189
|
+
.that.respondsTo("on")
|
|
190
|
+
.that.respondsTo("resetConnection")
|
|
191
|
+
.that.respondsTo("headers")
|
|
192
|
+
.that.respondsTo("setHeaders");
|
|
193
|
+
resolve();
|
|
194
|
+
});
|
|
181
195
|
});
|
|
182
196
|
});
|
|
183
197
|
|
|
@@ -204,7 +218,9 @@ describe("App: Loading Services", () => {
|
|
|
204
218
|
.that.has.all.keys(
|
|
205
219
|
"emit",
|
|
206
220
|
"on",
|
|
221
|
+
"once",
|
|
207
222
|
"$clearEvent",
|
|
223
|
+
"destroy",
|
|
208
224
|
"resetConnection",
|
|
209
225
|
"disconnect",
|
|
210
226
|
"headers",
|
|
@@ -236,8 +252,10 @@ describe("App SystemObjects: Initializing Modules, Modules and configurations",
|
|
|
236
252
|
"useService",
|
|
237
253
|
"useConfig",
|
|
238
254
|
"on",
|
|
255
|
+
"once",
|
|
239
256
|
"emit",
|
|
240
257
|
"$clearEvent",
|
|
258
|
+
"destroy",
|
|
241
259
|
"before",
|
|
242
260
|
"after"
|
|
243
261
|
)
|
|
@@ -257,8 +275,10 @@ describe("App SystemObjects: Initializing Modules, Modules and configurations",
|
|
|
257
275
|
"useService",
|
|
258
276
|
"useConfig",
|
|
259
277
|
"on",
|
|
278
|
+
"once",
|
|
260
279
|
"emit",
|
|
261
280
|
"$clearEvent",
|
|
281
|
+
"destroy",
|
|
262
282
|
"before",
|
|
263
283
|
"after"
|
|
264
284
|
)
|
|
@@ -377,6 +397,66 @@ describe("App SystemObjects: Initializing Modules, Modules and configurations",
|
|
|
377
397
|
);
|
|
378
398
|
});
|
|
379
399
|
|
|
400
|
+
it("should expose live modules via App.getModule(name) and App.getModules() after ready", async () => {
|
|
401
|
+
const App = createApp();
|
|
402
|
+
const route = "test-service";
|
|
403
|
+
const port = "8495";
|
|
404
|
+
|
|
405
|
+
App.module("mod", function () {
|
|
406
|
+
this.test = () => {};
|
|
407
|
+
}).module("mod2", function () {
|
|
408
|
+
this.test = () => {};
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
// before initialization the live module reference does not exist yet
|
|
412
|
+
expect(App.getModule("mod")).to.equal(undefined);
|
|
413
|
+
expect(App.getModules()).to.be.an("object").that.is.empty;
|
|
414
|
+
|
|
415
|
+
await new Promise((resolve) => App.startService({ route, port }).on("ready", resolve));
|
|
416
|
+
|
|
417
|
+
const mod = App.getModule("mod");
|
|
418
|
+
expect(mod).to.be.an("object").that.respondsTo("on").that.respondsTo("emit");
|
|
419
|
+
expect(App.getModule("missing")).to.equal(undefined);
|
|
420
|
+
|
|
421
|
+
const modules = App.getModules();
|
|
422
|
+
expect(modules).to.be.an("object").that.has.all.keys("mod", "mod2");
|
|
423
|
+
expect(modules.mod).to.respondTo("on").that.respondsTo("emit");
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
it("should emit a local 'error' event on the module when a method throws back to the client", async () => {
|
|
427
|
+
const App = createApp();
|
|
428
|
+
const route = "test-service";
|
|
429
|
+
const port = "8496";
|
|
430
|
+
|
|
431
|
+
App.module("errMod", function () {
|
|
432
|
+
this.boom = () => {
|
|
433
|
+
throw { status: 400, message: "boom went the method" };
|
|
434
|
+
};
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
await new Promise((resolve) => App.startService({ route, port }).on("ready", resolve));
|
|
438
|
+
|
|
439
|
+
const errMod = App.getModule("errMod");
|
|
440
|
+
const errorEvent = new Promise((resolve) => errMod.on("error", resolve));
|
|
441
|
+
|
|
442
|
+
const url = `http://localhost:${port}/${route}/errMod/boom`;
|
|
443
|
+
try {
|
|
444
|
+
await HttpClient.request({ method: "POST", url, body: { __arguments: [{ x: 1 }] } });
|
|
445
|
+
} catch (e) {
|
|
446
|
+
// expected — the method throws and the error is returned over HTTP
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
const info = await errorEvent;
|
|
450
|
+
expect(info)
|
|
451
|
+
.to.be.an("object")
|
|
452
|
+
.that.has.all.keys("module_name", "fn", "arguments", "status", "message", "error");
|
|
453
|
+
expect(info.module_name).to.equal("errMod");
|
|
454
|
+
expect(info.fn).to.equal("boom");
|
|
455
|
+
expect(info.status).to.equal(400);
|
|
456
|
+
expect(info.message).to.equal("boom went the method");
|
|
457
|
+
expect(info.arguments).to.deep.equal([{ x: 1 }]);
|
|
458
|
+
});
|
|
459
|
+
|
|
380
460
|
it("should be able to use App.config(constructor) to construct a configuration module", async () => {
|
|
381
461
|
const App = createApp();
|
|
382
462
|
|
|
@@ -472,19 +552,19 @@ describe("SystemContext", () => {
|
|
|
472
552
|
.that.respondsTo("useConfig");
|
|
473
553
|
this.configPassed = true;
|
|
474
554
|
next();
|
|
475
|
-
})
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
555
|
+
});
|
|
556
|
+
App.on("ready", function () {
|
|
557
|
+
expect(this)
|
|
558
|
+
.to.be.an("object")
|
|
559
|
+
.that.respondsTo("useService")
|
|
560
|
+
.that.respondsTo("useModule")
|
|
561
|
+
.that.respondsTo("useConfig");
|
|
562
|
+
const mod1 = this.useModule("mod1");
|
|
563
|
+
const config = this.useConfig();
|
|
564
|
+
expect(mod1.testPassed).to.equal(true);
|
|
565
|
+
expect(config.configPassed).to.equal(true);
|
|
566
|
+
});
|
|
567
|
+
App.on("ready", function () {
|
|
488
568
|
expect(this)
|
|
489
569
|
.to.be.an("object")
|
|
490
570
|
.that.respondsTo("useService")
|
|
@@ -543,7 +623,8 @@ describe("SystemContext", () => {
|
|
|
543
623
|
expect(event.type).to.equal("WebSocket");
|
|
544
624
|
resolve();
|
|
545
625
|
});
|
|
546
|
-
|
|
626
|
+
// small delay so "subscribe" WebSocket message is processed before the HTTP call triggers the emit
|
|
627
|
+
setTimeout(() => EventTesterModule.sendEvent(eventName), 100);
|
|
547
628
|
})
|
|
548
629
|
);
|
|
549
630
|
});
|
|
@@ -17,13 +17,13 @@ const extractFilesFromArguments = (__arguments) => {
|
|
|
17
17
|
|
|
18
18
|
__arguments.forEach((arg) => {
|
|
19
19
|
if (isObject(arg)) {
|
|
20
|
-
if (
|
|
20
|
+
if (arg.file) {
|
|
21
21
|
if (foundFile)
|
|
22
22
|
throw new Error("Only one file or files allowed across arguments.");
|
|
23
23
|
foundFile = arg.file;
|
|
24
24
|
fileType = "file";
|
|
25
25
|
arg.file = "__file__";
|
|
26
|
-
} else if (
|
|
26
|
+
} else if (arg.files) {
|
|
27
27
|
if (foundFile)
|
|
28
28
|
throw new Error("Only one file or files allowed across arguments.");
|
|
29
29
|
foundFile = arg.files;
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
const io = require("socket.io-client");
|
|
3
3
|
const createDispatcher = require("../../Dispatcher/Dispatcher");
|
|
4
4
|
|
|
5
|
+
const RESERVED = new Set(["connect", "disconnect", "error", "connect_error"]);
|
|
6
|
+
|
|
5
7
|
module.exports = function SocketDispatcher(
|
|
6
8
|
{ namespace, socketPath: path },
|
|
7
9
|
events = {},
|
|
@@ -13,8 +15,82 @@ module.exports = function SocketDispatcher(
|
|
|
13
15
|
: createDispatcher.apply(this, [events, systemContext]);
|
|
14
16
|
|
|
15
17
|
const socket = io.connect(namespace, { path });
|
|
18
|
+
const subscriptionCounts = new Map();
|
|
19
|
+
|
|
20
|
+
const trackSubscribe = (name) => {
|
|
21
|
+
const n = (subscriptionCounts.get(name) || 0) + 1;
|
|
22
|
+
subscriptionCounts.set(name, n);
|
|
23
|
+
if (n === 1) socket.emit("subscribe", name);
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const trackUnsubscribe = (name) => {
|
|
27
|
+
const n = (subscriptionCounts.get(name) || 0) - 1;
|
|
28
|
+
if (n <= 0) {
|
|
29
|
+
subscriptionCounts.delete(name);
|
|
30
|
+
socket.emit("unsubscribe", name);
|
|
31
|
+
} else {
|
|
32
|
+
subscriptionCounts.set(name, n);
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const originalOn = dispatcher.on.bind(dispatcher);
|
|
37
|
+
dispatcher.on = function (name, cb, options) {
|
|
38
|
+
const unsub = originalOn(name, cb, options);
|
|
39
|
+
if (!RESERVED.has(name)) {
|
|
40
|
+
trackSubscribe(name);
|
|
41
|
+
return function () {
|
|
42
|
+
unsub();
|
|
43
|
+
trackUnsubscribe(name);
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
return unsub;
|
|
47
|
+
};
|
|
16
48
|
|
|
17
|
-
|
|
49
|
+
const originalOnce = dispatcher.once.bind(dispatcher);
|
|
50
|
+
dispatcher.once = function (name, cb, options) {
|
|
51
|
+
if (RESERVED.has(name)) return originalOnce(name, cb, options);
|
|
52
|
+
let done = false;
|
|
53
|
+
trackSubscribe(name);
|
|
54
|
+
const unsub = originalOnce(
|
|
55
|
+
name,
|
|
56
|
+
function (...args) {
|
|
57
|
+
if (!done) {
|
|
58
|
+
done = true;
|
|
59
|
+
trackUnsubscribe(name);
|
|
60
|
+
cb(...args);
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
options
|
|
64
|
+
);
|
|
65
|
+
return function () {
|
|
66
|
+
if (!done) {
|
|
67
|
+
done = true;
|
|
68
|
+
unsub();
|
|
69
|
+
trackUnsubscribe(name);
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const originalClearEvent = dispatcher.$clearEvent.bind(dispatcher);
|
|
75
|
+
dispatcher.$clearEvent = function (name) {
|
|
76
|
+
originalClearEvent(name);
|
|
77
|
+
if (subscriptionCounts.has(name)) {
|
|
78
|
+
subscriptionCounts.delete(name);
|
|
79
|
+
socket.emit("unsubscribe", name);
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const originalDestroy = dispatcher.destroy.bind(dispatcher);
|
|
84
|
+
dispatcher.destroy = function () {
|
|
85
|
+
subscriptionCounts.forEach((_, name) => socket.emit("unsubscribe", name));
|
|
86
|
+
subscriptionCounts.clear();
|
|
87
|
+
originalDestroy();
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
socket.onAny((name, payload) => {
|
|
91
|
+
const event = { id: payload.id, name, data: payload.data, type: payload.type };
|
|
92
|
+
dispatcher.emit(name, payload.data, event);
|
|
93
|
+
});
|
|
18
94
|
|
|
19
95
|
socket.on("disconnect", () => {
|
|
20
96
|
socket.disconnect();
|
|
@@ -22,6 +98,9 @@ module.exports = function SocketDispatcher(
|
|
|
22
98
|
});
|
|
23
99
|
|
|
24
100
|
socket.on("connect", () => {
|
|
101
|
+
subscriptionCounts.forEach((count, name) => {
|
|
102
|
+
if (count > 0) socket.emit("subscribe", name);
|
|
103
|
+
});
|
|
25
104
|
dispatcher.emit("connect");
|
|
26
105
|
});
|
|
27
106
|
|
|
@@ -46,7 +46,9 @@ describe("Client", () => {
|
|
|
46
46
|
.that.has.all.keys(
|
|
47
47
|
"emit",
|
|
48
48
|
"on",
|
|
49
|
+
"once",
|
|
49
50
|
"$clearEvent",
|
|
51
|
+
"destroy",
|
|
50
52
|
"resetConnection",
|
|
51
53
|
"disconnect",
|
|
52
54
|
"headers",
|
|
@@ -66,7 +68,9 @@ describe("Client", () => {
|
|
|
66
68
|
.that.has.all.keys(
|
|
67
69
|
"emit",
|
|
68
70
|
"on",
|
|
71
|
+
"once",
|
|
69
72
|
"$clearEvent",
|
|
73
|
+
"destroy",
|
|
70
74
|
"disconnect",
|
|
71
75
|
"headers",
|
|
72
76
|
"setHeaders",
|
|
@@ -11,6 +11,10 @@ const socket = WebSocket.of(route);
|
|
|
11
11
|
socket.on("connect", ({ id }) => {
|
|
12
12
|
console.log(`socket connected with id:${id}`);
|
|
13
13
|
});
|
|
14
|
+
socket.on("connection", (clientSocket) => {
|
|
15
|
+
clientSocket.on("subscribe", (name) => clientSocket.join(name));
|
|
16
|
+
clientSocket.on("unsubscribe", (name) => clientSocket.leave(name));
|
|
17
|
+
});
|
|
14
18
|
SocketServer.listen(port);
|
|
15
19
|
|
|
16
20
|
describe("SocketDispatcher", () => {
|
|
@@ -19,7 +23,7 @@ describe("SocketDispatcher", () => {
|
|
|
19
23
|
it("should return an EventDispatcher object with methods on and emit", async () => {
|
|
20
24
|
expect(dispatcher)
|
|
21
25
|
.to.be.an("object")
|
|
22
|
-
.that.has.all.keys("on", "emit", "$clearEvent", "disconnect")
|
|
26
|
+
.that.has.all.keys("on", "once", "emit", "$clearEvent", "destroy", "disconnect")
|
|
23
27
|
.that.respondsTo("on")
|
|
24
28
|
.that.respondsTo("emit")
|
|
25
29
|
.that.respondsTo("$clearEvent")
|
|
@@ -32,7 +36,7 @@ describe("SocketDispatcher", () => {
|
|
|
32
36
|
});
|
|
33
37
|
dispatcher.on("connect", () => console.log(`I'm all the way connected!`));
|
|
34
38
|
setTimeout(
|
|
35
|
-
() => socket.emit(
|
|
39
|
+
() => socket.to(eventName).emit(eventName, { id: "test-id", data: { testPassed: true }, type: "WebSocket" }),
|
|
36
40
|
500
|
|
37
41
|
);
|
|
38
42
|
});
|
|
@@ -44,7 +48,7 @@ describe("SocketDispatcher.apply()", () => {
|
|
|
44
48
|
it("should return an EventDispatcher object with methods on and emit", async () => {
|
|
45
49
|
expect(dispatcher)
|
|
46
50
|
.to.be.an("object")
|
|
47
|
-
.that.has.all.keys("on", "emit", "$clearEvent", "disconnect")
|
|
51
|
+
.that.has.all.keys("on", "once", "emit", "$clearEvent", "destroy", "disconnect")
|
|
48
52
|
.that.respondsTo("on")
|
|
49
53
|
.that.respondsTo("emit")
|
|
50
54
|
.that.respondsTo("$clearEvent")
|
|
@@ -53,14 +57,16 @@ describe("SocketDispatcher.apply()", () => {
|
|
|
53
57
|
it("Should be able to emit and handle events", (done) => {
|
|
54
58
|
dispatcher.on(eventName, (data, event) => {
|
|
55
59
|
expect(data).to.deep.equal({ testPassed: true });
|
|
56
|
-
expect(event).to.
|
|
57
|
-
|
|
60
|
+
expect(event).to.be.an("object").that.has.all.keys("id", "name", "data", "type");
|
|
61
|
+
expect(event.name).to.equal(eventName);
|
|
62
|
+
expect(event.data).to.deep.equal({ testPassed: true });
|
|
63
|
+
expect(event.type).to.equal("WebSocket");
|
|
58
64
|
console.log(`I'm all the way connected too!`);
|
|
59
65
|
done();
|
|
60
66
|
});
|
|
61
67
|
|
|
62
68
|
setTimeout(
|
|
63
|
-
() => socket.emit(
|
|
69
|
+
() => socket.to(eventName).emit(eventName, { id: "test-id", data: { testPassed: true }, type: "WebSocket" }),
|
|
64
70
|
500
|
|
65
71
|
);
|
|
66
72
|
});
|
|
@@ -1,49 +1,93 @@
|
|
|
1
|
+
"use strict";
|
|
1
2
|
const throttle = require("../../utils/throttle");
|
|
2
3
|
|
|
3
|
-
module.exports = function createDispatcher(
|
|
4
|
+
module.exports = function createDispatcher(_, systemContext) {
|
|
5
|
+
const events = new Map();
|
|
4
6
|
const Dispatcher = this || {};
|
|
5
7
|
|
|
6
|
-
Dispatcher.emit = (eventName, data, event)
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
);
|
|
8
|
+
Dispatcher.emit = function (eventName, data, event) {
|
|
9
|
+
const registry = events.get(eventName);
|
|
10
|
+
if (!registry) return Dispatcher;
|
|
11
|
+
for (const listener of registry.values()) {
|
|
12
|
+
listener(data, event);
|
|
13
|
+
}
|
|
11
14
|
return Dispatcher;
|
|
12
15
|
};
|
|
13
16
|
|
|
14
|
-
Dispatcher.on = (eventName, callback, { limit, interval } = {})
|
|
17
|
+
Dispatcher.on = function (eventName, callback, { limit, interval, eventId } = {}) {
|
|
15
18
|
if (typeof callback !== "function") return Dispatcher;
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
if (!events
|
|
19
|
-
|
|
20
|
-
if (
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
return
|
|
19
|
+
|
|
20
|
+
const key = eventId || Symbol();
|
|
21
|
+
if (!events.has(eventName)) events.set(eventName, new Map());
|
|
22
|
+
const registry = events.get(eventName);
|
|
23
|
+
if (registry.has(key)) registry.delete(key);
|
|
24
|
+
|
|
25
|
+
let fn = typeof interval === "number" ? throttle(callback, limit, interval) : callback;
|
|
26
|
+
if (systemContext) fn = fn.bind(systemContext);
|
|
27
|
+
registry.set(key, fn);
|
|
28
|
+
|
|
29
|
+
return function () {
|
|
30
|
+
const currentRegistry = events.get(eventName);
|
|
31
|
+
if (!currentRegistry) return;
|
|
32
|
+
currentRegistry.delete(key);
|
|
33
|
+
if (currentRegistry.size === 0) events.delete(eventName);
|
|
34
|
+
};
|
|
27
35
|
};
|
|
28
36
|
|
|
29
|
-
Dispatcher
|
|
30
|
-
if (
|
|
37
|
+
Dispatcher.once = function (eventName, callback, { limit, interval, eventId } = {}) {
|
|
38
|
+
if (typeof callback !== "function") return function () {};
|
|
39
|
+
|
|
40
|
+
const key = eventId || Symbol();
|
|
41
|
+
if (!events.has(eventName)) events.set(eventName, new Map());
|
|
42
|
+
const registry = events.get(eventName);
|
|
43
|
+
if (registry.has(key)) registry.delete(key);
|
|
44
|
+
|
|
45
|
+
const throttled =
|
|
46
|
+
typeof interval === "number" ? throttle(callback, limit, interval) : callback;
|
|
47
|
+
|
|
48
|
+
const boundFn = function (...args) {
|
|
49
|
+
registry.delete(key);
|
|
50
|
+
if (registry.size === 0) events.delete(eventName);
|
|
51
|
+
return throttled.apply(systemContext, args);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
registry.set(key, boundFn);
|
|
55
|
+
|
|
56
|
+
return function () {
|
|
57
|
+
const currentRegistry = events.get(eventName);
|
|
58
|
+
if (!currentRegistry) return;
|
|
59
|
+
currentRegistry.delete(key);
|
|
60
|
+
if (currentRegistry.size === 0) events.delete(eventName);
|
|
61
|
+
};
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
Dispatcher.$clearEvent = function (eventName, fn) {
|
|
65
|
+
if (!events.get(eventName)) return Dispatcher;
|
|
31
66
|
|
|
32
67
|
if (!fn) {
|
|
33
|
-
|
|
34
|
-
delete events[eventName];
|
|
68
|
+
events.delete(eventName);
|
|
35
69
|
} else if (typeof fn === "function") {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
70
|
+
const registry = events.get(eventName);
|
|
71
|
+
for (const [key, listener] of registry.entries()) {
|
|
72
|
+
if (listener.name === fn.name) {
|
|
73
|
+
registry.delete(key);
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (registry.size === 0) events.delete(eventName);
|
|
40
78
|
} else {
|
|
41
79
|
console.error(
|
|
42
|
-
"SystemLynxError: the second parameter of the Dispatcher.$clearEvent takes the original function
|
|
80
|
+
"SystemLynxError: the second parameter of the Dispatcher.$clearEvent takes the original function to the event"
|
|
43
81
|
);
|
|
44
82
|
}
|
|
45
83
|
|
|
46
84
|
return Dispatcher;
|
|
47
85
|
};
|
|
86
|
+
|
|
87
|
+
Dispatcher.destroy = function () {
|
|
88
|
+
events.clear();
|
|
89
|
+
return Dispatcher;
|
|
90
|
+
};
|
|
91
|
+
|
|
48
92
|
return Dispatcher;
|
|
49
93
|
};
|