ayman-fca 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.
Files changed (124) hide show
  1. package/README.md +81 -0
  2. package/func/checkUpdate.js +13 -0
  3. package/func/logAdapter.js +30 -0
  4. package/func/logger.js +66 -0
  5. package/index.d.ts +751 -0
  6. package/index.js +15 -0
  7. package/module/config.js +38 -0
  8. package/module/login.js +111 -0
  9. package/module/loginHelper.js +1296 -0
  10. package/module/options.js +37 -0
  11. package/package.json +78 -0
  12. package/src/api/action/addExternalModule.js +19 -0
  13. package/src/api/action/changeAvatar.js +137 -0
  14. package/src/api/action/changeBio.js +48 -0
  15. package/src/api/action/enableAutoSaveAppState.js +72 -0
  16. package/src/api/action/getCurrentUserID.js +11 -0
  17. package/src/api/action/handleFriendRequest.js +33 -0
  18. package/src/api/action/logout.js +76 -0
  19. package/src/api/action/refreshFb_dtsg.js +62 -0
  20. package/src/api/action/setPostReaction.js +106 -0
  21. package/src/api/action/story.js +118 -0
  22. package/src/api/action/unfriend.js +30 -0
  23. package/src/api/http/httpGet.js +28 -0
  24. package/src/api/http/httpPost.js +32 -0
  25. package/src/api/http/postFormData.js +23 -0
  26. package/src/api/messaging/J +1 -0
  27. package/src/api/messaging/addUserToGroup.js +70 -0
  28. package/src/api/messaging/changeAdminStatus.js +72 -0
  29. package/src/api/messaging/changeArchivedStatus.js +31 -0
  30. package/src/api/messaging/changeBlockedStatus.js +27 -0
  31. package/src/api/messaging/changeGroupImage.js +91 -0
  32. package/src/api/messaging/changeNickname.js +70 -0
  33. package/src/api/messaging/changeThreadColor.js +44 -0
  34. package/src/api/messaging/changeThreadEmoji.js +111 -0
  35. package/src/api/messaging/createNewGroup.js +50 -0
  36. package/src/api/messaging/createPoll.js +52 -0
  37. package/src/api/messaging/createThemeAI.js +98 -0
  38. package/src/api/messaging/deleteMessage.js +73 -0
  39. package/src/api/messaging/deleteThread.js +29 -0
  40. package/src/api/messaging/editMessage.js +67 -0
  41. package/src/api/messaging/forwardAttachment.js +55 -0
  42. package/src/api/messaging/forwardMessage.js +73 -0
  43. package/src/api/messaging/getEmojiUrl.js +29 -0
  44. package/src/api/messaging/getFriendsList.js +82 -0
  45. package/src/api/messaging/getMessage.js +829 -0
  46. package/src/api/messaging/getThemePictures.js +62 -0
  47. package/src/api/messaging/groupActions.js +119 -0
  48. package/src/api/messaging/handleMessageRequest.js +31 -0
  49. package/src/api/messaging/markAsDelivered.js +31 -0
  50. package/src/api/messaging/markAsRead.js +88 -0
  51. package/src/api/messaging/markAsReadAll.js +28 -0
  52. package/src/api/messaging/markAsSeen.js +30 -0
  53. package/src/api/messaging/muteThread.js +27 -0
  54. package/src/api/messaging/notes.js +101 -0
  55. package/src/api/messaging/removeUserFromGroup.js +51 -0
  56. package/src/api/messaging/resolvePhotoUrl.js +29 -0
  57. package/src/api/messaging/scheduler.js +100 -0
  58. package/src/api/messaging/searchForThread.js +32 -0
  59. package/src/api/messaging/sendMessage.js +270 -0
  60. package/src/api/messaging/sendTypingIndicator.js +62 -0
  61. package/src/api/messaging/setMessageReaction.js +91 -0
  62. package/src/api/messaging/setTitle.js +86 -0
  63. package/src/api/messaging/shareContact.js +47 -0
  64. package/src/api/messaging/threadColors.js +128 -0
  65. package/src/api/messaging/unsendMessage.js +73 -0
  66. package/src/api/messaging/uploadAttachment.js +492 -0
  67. package/src/api/socket/core/connectMqtt.js +259 -0
  68. package/src/api/socket/core/emitAuth.js +79 -0
  69. package/src/api/socket/core/getSeqID.js +170 -0
  70. package/src/api/socket/core/getTaskResponseData.js +27 -0
  71. package/src/api/socket/core/parseDelta.js +377 -0
  72. package/src/api/socket/detail/buildStream.js +215 -0
  73. package/src/api/socket/detail/constants.js +29 -0
  74. package/src/api/socket/listenMqtt.js +377 -0
  75. package/src/api/socket/middleware/index.js +80 -0
  76. package/src/api/threads/getThreadHistory.js +664 -0
  77. package/src/api/threads/getThreadInfo.js +296 -0
  78. package/src/api/threads/getThreadList.js +293 -0
  79. package/src/api/threads/getThreadPictures.js +43 -0
  80. package/src/api/user/J +1 -0
  81. package/src/api/user/getUserID.js +48 -0
  82. package/src/api/user/getUserInfo.js +402 -0
  83. package/src/api/user/getUserInfoV2.js +134 -0
  84. package/src/core/sendReqMqtt.js +69 -0
  85. package/src/database/helpers.js +36 -0
  86. package/src/database/models/index.js +55 -0
  87. package/src/database/models/thread.js +44 -0
  88. package/src/database/models/user.js +39 -0
  89. package/src/database/threadData.js +92 -0
  90. package/src/database/userData.js +88 -0
  91. package/src/remote/remoteClient.js +71 -0
  92. package/src/utils/broadcast.js +62 -0
  93. package/src/utils/client.js +10 -0
  94. package/src/utils/constants.js +53 -0
  95. package/src/utils/cookies.js +73 -0
  96. package/src/utils/format/attachment.js +357 -0
  97. package/src/utils/format/cookie.js +9 -0
  98. package/src/utils/format/date.js +50 -0
  99. package/src/utils/format/decode.js +44 -0
  100. package/src/utils/format/delta.js +194 -0
  101. package/src/utils/format/ids.js +64 -0
  102. package/src/utils/format/index.js +64 -0
  103. package/src/utils/format/message.js +88 -0
  104. package/src/utils/format/presence.js +132 -0
  105. package/src/utils/format/readTyp.js +44 -0
  106. package/src/utils/format/thread.js +42 -0
  107. package/src/utils/format/utils.js +141 -0
  108. package/src/utils/headers.js +96 -0
  109. package/src/utils/loginParser/autoLogin.js +125 -0
  110. package/src/utils/loginParser/helpers.js +43 -0
  111. package/src/utils/loginParser/index.js +10 -0
  112. package/src/utils/loginParser/parseAndCheckLogin.js +220 -0
  113. package/src/utils/loginParser/textUtils.js +28 -0
  114. package/src/utils/request/H +1 -0
  115. package/src/utils/request/client.js +33 -0
  116. package/src/utils/request/config.js +25 -0
  117. package/src/utils/request/defaults.js +40 -0
  118. package/src/utils/request/helpers.js +31 -0
  119. package/src/utils/request/index.js +12 -0
  120. package/src/utils/request/methods.js +92 -0
  121. package/src/utils/request/proxy.js +23 -0
  122. package/src/utils/request/retry.js +87 -0
  123. package/src/utils/request/sanitize.js +41 -0
  124. package/src/utils/sessionKeeper.js +275 -0
@@ -0,0 +1,377 @@
1
+ "use strict";
2
+ /**
3
+ * Socket/MQTT entry: wires getSeqID -> listenMqtt, middleware, stopListening, and post guard.
4
+ * Exported as the function that attaches listenMqtt + middleware API to the login context.
5
+ */
6
+ const mqtt = require("mqtt");
7
+ const WebSocket = require("ws");
8
+ const HttpsProxyAgent = require("https-proxy-agent");
9
+ const EventEmitter = require("events");
10
+ const logger = require("../../../func/logger");
11
+ const { parseAndCheckLogin } = require("../../utils/client");
12
+ const { buildProxy, buildStream } = require("./detail/buildStream");
13
+ const { topics } = require("./detail/constants");
14
+ const createParseDelta = require("./core/parseDelta");
15
+ const createListenMqtt = require("./core/connectMqtt");
16
+ const createGetSeqID = require("./core/getSeqID");
17
+ const getTaskResponseData = require("./core/getTaskResponseData");
18
+ const createEmitAuth = require("./core/emitAuth");
19
+ const createMiddlewareSystem = require("./middleware");
20
+
21
+ const CYCLE_MS_DEFAULT = 60 * 60 * 1000;
22
+ const RECONNECT_DELAY_MS_DEFAULT = 2000;
23
+ const UNSUB_ALL_TIMEOUT_MS = 5000;
24
+
25
+ const parseDelta = createParseDelta({ parseAndCheckLogin });
26
+ const emitAuth = createEmitAuth({ logger });
27
+ const listenMqtt = createListenMqtt({ WebSocket, mqtt, HttpsProxyAgent, buildStream, buildProxy, topics, parseDelta, getTaskResponseData, logger, emitAuth });
28
+ const getSeqIDFactory = createGetSeqID({ parseAndCheckLogin, listenMqtt, logger, emitAuth });
29
+
30
+ const MQTT_DEFAULTS = { cycleMs: CYCLE_MS_DEFAULT, reconnectDelayMs: RECONNECT_DELAY_MS_DEFAULT, autoReconnect: true, reconnectAfterStop: false };
31
+ function mqttConf(ctx, overrides) {
32
+ ctx._mqttOpt = Object.assign({}, MQTT_DEFAULTS, ctx._mqttOpt || {}, overrides || {});
33
+ if (typeof ctx._mqttOpt.autoReconnect === "boolean") ctx.globalOptions.autoReconnect = ctx._mqttOpt.autoReconnect;
34
+ return ctx._mqttOpt;
35
+ }
36
+
37
+ module.exports = function (defaultFuncs, api, ctx, opts) {
38
+ const identity = function () { };
39
+ let globalCallback = identity;
40
+
41
+ // Initialize middleware system if not already initialized
42
+ if (!ctx._middleware) {
43
+ ctx._middleware = createMiddlewareSystem();
44
+ }
45
+ const middleware = ctx._middleware;
46
+
47
+ function installPostGuard() {
48
+ if (ctx._postGuarded) return defaultFuncs.post;
49
+ const rawPost = defaultFuncs.post && defaultFuncs.post.bind(defaultFuncs);
50
+ if (!rawPost) return defaultFuncs.post;
51
+
52
+ function postSafe(...args) {
53
+ return rawPost(...args).catch(err => {
54
+ const msg = (err && err.error) || (err && err.message) || String(err || "");
55
+ if (/Not logged in|blocked the login/i.test(msg)) {
56
+ emitAuth(
57
+ ctx,
58
+ api,
59
+ globalCallback,
60
+ /blocked/i.test(msg) ? "login_blocked" : "not_logged_in",
61
+ msg
62
+ );
63
+ }
64
+ throw err;
65
+ });
66
+ }
67
+ defaultFuncs.post = postSafe;
68
+ ctx._postGuarded = true;
69
+ return postSafe;
70
+ }
71
+
72
+ let conf = mqttConf(ctx, opts);
73
+
74
+ function getSeqIDWrapper() {
75
+ if (ctx._ending && !ctx._cycling) {
76
+ logger("mqtt getSeqID skipped - ending", "warn");
77
+ return Promise.resolve();
78
+ }
79
+ const form = {
80
+ av: ctx.globalOptions.pageID,
81
+ queries: JSON.stringify({
82
+ o0: {
83
+ doc_id: "3336396659757871",
84
+ query_params: {
85
+ limit: 1, before: null, tags: ["INBOX"],
86
+ includeDeliveryReceipts: false, includeSeqID: true
87
+ }
88
+ }
89
+ })
90
+ };
91
+ logger("mqtt getSeqID call", "info");
92
+ return getSeqIDFactory(defaultFuncs, api, ctx, globalCallback, form)
93
+ .then(() => {
94
+ logger("mqtt getSeqID done", "info");
95
+ ctx._cycling = false;
96
+ })
97
+ .catch(e => {
98
+ ctx._cycling = false;
99
+ const errMsg = e && e.message ? e.message : String(e || "Unknown error");
100
+ logger(`mqtt getSeqID error: ${errMsg}`, "error");
101
+ // Don't reconnect if we're ending
102
+ if (ctx._ending) return;
103
+ // Retry after delay if autoReconnect is enabled
104
+ if (ctx.globalOptions.autoReconnect) {
105
+ const d = conf.reconnectDelayMs;
106
+ logger(`mqtt getSeqID will retry in ${d}ms`, "warn");
107
+ setTimeout(() => {
108
+ if (!ctx._ending) getSeqIDWrapper();
109
+ }, d);
110
+ }
111
+ });
112
+ }
113
+
114
+ function isConnected() {
115
+ return !!(ctx.mqttClient && ctx.mqttClient.connected);
116
+ }
117
+
118
+ function unsubAll(cb) {
119
+ if (!isConnected()) {
120
+ if (cb) setTimeout(cb, 0);
121
+ return;
122
+ }
123
+ let pending = topics.length;
124
+ if (!pending) {
125
+ if (cb) setTimeout(cb, 0);
126
+ return;
127
+ }
128
+ let fired = false;
129
+ const timeout = setTimeout(() => {
130
+ if (!fired) {
131
+ fired = true;
132
+ logger("unsubAll timeout, proceeding anyway", "warn");
133
+ if (cb) cb();
134
+ }
135
+ }, UNSUB_ALL_TIMEOUT_MS);
136
+
137
+ topics.forEach(t => {
138
+ try {
139
+ ctx.mqttClient.unsubscribe(t, () => {
140
+ if (--pending === 0 && !fired) {
141
+ clearTimeout(timeout);
142
+ fired = true;
143
+ if (cb) cb();
144
+ }
145
+ });
146
+ } catch (err) {
147
+ logger(`unsubAll error for topic ${t}: ${err && err.message ? err.message : String(err)}`, "warn");
148
+ if (--pending === 0 && !fired) {
149
+ clearTimeout(timeout);
150
+ fired = true;
151
+ if (cb) cb();
152
+ }
153
+ }
154
+ });
155
+ }
156
+
157
+ function endQuietly(next) {
158
+ const finish = () => {
159
+ try {
160
+ if (ctx.mqttClient) {
161
+ ctx.mqttClient.removeAllListeners();
162
+ }
163
+ } catch (_) { }
164
+ ctx.mqttClient = undefined;
165
+ ctx.lastSeqId = null;
166
+ ctx.syncToken = undefined;
167
+ ctx.t_mqttCalled = false;
168
+ ctx._ending = false;
169
+ ctx._cycling = false;
170
+ if (ctx._reconnectTimer) {
171
+ clearTimeout(ctx._reconnectTimer);
172
+ ctx._reconnectTimer = null;
173
+ }
174
+ if (ctx._rTimeout) {
175
+ clearTimeout(ctx._rTimeout);
176
+ ctx._rTimeout = null;
177
+ }
178
+ // Clean up tasks Map to prevent memory leak
179
+ if (ctx.tasks && ctx.tasks instanceof Map) {
180
+ ctx.tasks.clear();
181
+ }
182
+ // Clean up userInfo intervals
183
+ if (ctx._userInfoIntervals && Array.isArray(ctx._userInfoIntervals)) {
184
+ ctx._userInfoIntervals.forEach(interval => {
185
+ try {
186
+ clearInterval(interval);
187
+ } catch (_) { }
188
+ });
189
+ ctx._userInfoIntervals = [];
190
+ }
191
+ // Clean up autoSave intervals
192
+ if (ctx._autoSaveInterval && Array.isArray(ctx._autoSaveInterval)) {
193
+ ctx._autoSaveInterval.forEach(interval => {
194
+ try {
195
+ clearInterval(interval);
196
+ } catch (_) { }
197
+ });
198
+ ctx._autoSaveInterval = [];
199
+ }
200
+ // Clean up scheduler
201
+ if (ctx._scheduler && typeof ctx._scheduler.destroy === "function") {
202
+ try {
203
+ ctx._scheduler.destroy();
204
+ } catch (_) { }
205
+ ctx._scheduler = undefined;
206
+ }
207
+ next && next();
208
+ };
209
+ try {
210
+ if (ctx.mqttClient) {
211
+ if (isConnected()) {
212
+ try {
213
+ ctx.mqttClient.publish("/browser_close", "{}", { qos: 0 });
214
+ } catch (_) { }
215
+ }
216
+ ctx.mqttClient.end(true, finish);
217
+ } else finish();
218
+ } catch (_) { finish(); }
219
+ }
220
+
221
+ function delayedReconnect() {
222
+ const d = conf.reconnectDelayMs;
223
+ logger(`mqtt reconnect in ${d}ms`, "info");
224
+ setTimeout(() => getSeqIDWrapper(), d);
225
+ }
226
+
227
+ function forceCycle() {
228
+ if (ctx._cycling) {
229
+ logger("mqtt force cycle already in progress", "warn");
230
+ return;
231
+ }
232
+ ctx._cycling = true;
233
+ ctx._ending = true;
234
+ logger("mqtt force cycle begin", "warn");
235
+ unsubAll(() => endQuietly(() => delayedReconnect()));
236
+ }
237
+
238
+ return function (callback) {
239
+ class MessageEmitter extends EventEmitter {
240
+ stopListening(callback2) {
241
+ const cb = callback2 || function () { };
242
+ logger("mqtt stop requested", "info");
243
+ globalCallback = identity;
244
+
245
+ if (ctx._autoCycleTimer) {
246
+ clearInterval(ctx._autoCycleTimer);
247
+ ctx._autoCycleTimer = null;
248
+ logger("mqtt auto-cycle cleared", "info");
249
+ }
250
+
251
+ if (ctx._reconnectTimer) {
252
+ clearTimeout(ctx._reconnectTimer);
253
+ ctx._reconnectTimer = null;
254
+ }
255
+
256
+ ctx._ending = true;
257
+ unsubAll(() => endQuietly(() => {
258
+ logger("mqtt stopped", "info");
259
+ cb();
260
+ conf = mqttConf(ctx, conf);
261
+ if (conf.reconnectAfterStop) delayedReconnect();
262
+ }));
263
+ }
264
+ async stopListeningAsync() {
265
+ return new Promise(resolve => { this.stopListening(resolve); });
266
+ }
267
+ }
268
+
269
+ const msgEmitter = new MessageEmitter();
270
+
271
+ // Original callback without middleware
272
+ const originalCallback = callback || function (error, message) {
273
+ if (error) { logger("mqtt emit error", "error"); return msgEmitter.emit("error", error); }
274
+ msgEmitter.emit("message", message);
275
+ };
276
+
277
+ // Only wrap callback with middleware if middleware exists
278
+ // If no middleware, use callback directly for better performance
279
+ if (middleware.count > 0) {
280
+ globalCallback = middleware.wrapCallback(originalCallback);
281
+ } else {
282
+ globalCallback = originalCallback;
283
+ }
284
+
285
+ conf = mqttConf(ctx, conf);
286
+
287
+ installPostGuard();
288
+
289
+ if (!ctx.firstListen) ctx.lastSeqId = null;
290
+ ctx.syncToken = undefined;
291
+ ctx.t_mqttCalled = false;
292
+
293
+ if (ctx._autoCycleTimer) { clearInterval(ctx._autoCycleTimer); ctx._autoCycleTimer = null; }
294
+ if (conf.cycleMs && conf.cycleMs > 0) {
295
+ ctx._autoCycleTimer = setInterval(forceCycle, conf.cycleMs);
296
+ logger(`mqtt auto-cycle enabled ${conf.cycleMs}ms`, "info");
297
+ } else {
298
+ logger("mqtt auto-cycle disabled", "info");
299
+ }
300
+
301
+ if (!ctx.firstListen || !ctx.lastSeqId) getSeqIDWrapper();
302
+ else {
303
+ logger("mqtt starting listenMqtt", "info");
304
+ listenMqtt(defaultFuncs, api, ctx, globalCallback);
305
+ }
306
+
307
+ api.stopListening = msgEmitter.stopListening;
308
+ api.stopListeningAsync = msgEmitter.stopListeningAsync;
309
+
310
+ // Store original callback for re-wrapping when middleware is added/removed
311
+ let currentOriginalCallback = originalCallback;
312
+ let currentGlobalCallback = globalCallback;
313
+
314
+ // Function to re-wrap callback when middleware changes
315
+ function rewrapCallbackIfNeeded() {
316
+ if (!ctx.mqttClient || ctx._ending) return; // Not listening or ending
317
+
318
+ const hasMiddleware = middleware.count > 0;
319
+ const isWrapped = currentGlobalCallback !== currentOriginalCallback;
320
+
321
+ // If middleware exists but callback is not wrapped, wrap it
322
+ if (hasMiddleware && !isWrapped) {
323
+ currentGlobalCallback = middleware.wrapCallback(currentOriginalCallback);
324
+ globalCallback = currentGlobalCallback;
325
+ logger("Middleware added - callback re-wrapped", "info");
326
+ }
327
+ // If no middleware but callback is wrapped, unwrap it
328
+ else if (!hasMiddleware && isWrapped) {
329
+ currentGlobalCallback = currentOriginalCallback;
330
+ globalCallback = currentGlobalCallback;
331
+ logger("All middleware removed - callback unwrapped", "info");
332
+ }
333
+ }
334
+
335
+ // Expose middleware API with re-wrapping support
336
+ api.useMiddleware = function (middlewareFn, fn) {
337
+ const result = middleware.use(middlewareFn, fn);
338
+ rewrapCallbackIfNeeded();
339
+ return result;
340
+ };
341
+ api.removeMiddleware = function (identifier) {
342
+ const result = middleware.remove(identifier);
343
+ rewrapCallbackIfNeeded();
344
+ return result;
345
+ };
346
+ api.clearMiddleware = function () {
347
+ const result = middleware.clear();
348
+ rewrapCallbackIfNeeded();
349
+ return result;
350
+ };
351
+ api.listMiddleware = function () {
352
+ return middleware.list();
353
+ };
354
+ api.setMiddlewareEnabled = function (name, enabled) {
355
+ const result = middleware.setEnabled(name, enabled);
356
+ rewrapCallbackIfNeeded();
357
+ return result;
358
+ };
359
+ // Avoid crashing on restart: defineProperty throws if already defined and non-configurable.
360
+ const existingMiddlewareCount = Object.getOwnPropertyDescriptor(api, "middlewareCount");
361
+ if (!existingMiddlewareCount) {
362
+ Object.defineProperty(api, "middlewareCount", {
363
+ configurable: true,
364
+ enumerable: false,
365
+ get: function () { return (ctx._middleware && ctx._middleware.count) || 0; }
366
+ });
367
+ } else if (existingMiddlewareCount.configurable) {
368
+ Object.defineProperty(api, "middlewareCount", {
369
+ configurable: true,
370
+ enumerable: existingMiddlewareCount.enumerable,
371
+ get: function () { return (ctx._middleware && ctx._middleware.count) || 0; }
372
+ });
373
+ }
374
+
375
+ return msgEmitter;
376
+ };
377
+ };
@@ -0,0 +1,80 @@
1
+ // ============================================================
2
+ // AYMAN-FCA v2.0 — Middleware System
3
+ // © 2025 Ayman. All Rights Reserved.
4
+ // ============================================================
5
+ "use strict";
6
+
7
+ const logger = require("../../../../func/logger");
8
+
9
+ module.exports = function createMiddlewareSystem() {
10
+ const stack = [];
11
+
12
+ function use(middleware, fn) {
13
+ let mFn, name;
14
+ if (typeof middleware === "string" && typeof fn === "function") {
15
+ name = middleware; mFn = fn;
16
+ } else if (typeof middleware === "function") {
17
+ mFn = middleware; name = `mw_${stack.length}`;
18
+ } else throw new Error("Middleware يجب أن يكون function أو (name, function)");
19
+
20
+ const wrapped = { name, fn: mFn, enabled: true };
21
+ stack.push(wrapped);
22
+
23
+ return function remove() {
24
+ const i = stack.indexOf(wrapped);
25
+ if (i !== -1) stack.splice(i, 1);
26
+ };
27
+ }
28
+
29
+ function remove(id) {
30
+ const i = typeof id === "string"
31
+ ? stack.findIndex(m => m.name === id)
32
+ : stack.findIndex(m => m.fn === id);
33
+ if (i !== -1) { stack.splice(i, 1); return true; }
34
+ return false;
35
+ }
36
+
37
+ function clear() { const n = stack.length; stack.length = 0; return n; }
38
+ function list() { return stack.filter(m => m.enabled).map(m => m.name); }
39
+
40
+ function setEnabled(name, enabled) {
41
+ const m = stack.find(m => m.name === name);
42
+ if (m) { m.enabled = enabled; return true; }
43
+ return false;
44
+ }
45
+
46
+ function process(event, finalCb) {
47
+ const enabled = stack.filter(m => m.enabled);
48
+ if (!enabled.length) return finalCb(null, event);
49
+ let idx = 0;
50
+ function next(err) {
51
+ if (err === false || err === null) return finalCb(null, null);
52
+ if (err && err !== true) return finalCb(err, null);
53
+ if (idx >= enabled.length) return finalCb(null, event);
54
+ const mw = enabled[idx++];
55
+ try {
56
+ const r = mw.fn(event, next);
57
+ if (r && typeof r.then === "function") r.then(() => next()).catch(e => next(e));
58
+ else if (r === false || r === null) finalCb(null, null);
59
+ } catch (e) { next(e); }
60
+ }
61
+ next();
62
+ }
63
+
64
+ function wrapCallback(cb) {
65
+ return function(err, event) {
66
+ if (err) return cb(err, null);
67
+ if (!event) return cb(null, null);
68
+ process(event, (mErr, processed) => {
69
+ if (mErr) return cb(mErr, null);
70
+ if (processed === null) return;
71
+ cb(null, processed);
72
+ });
73
+ };
74
+ }
75
+
76
+ return {
77
+ use, remove, clear, list, setEnabled, process, wrapCallback,
78
+ get count() { return stack.filter(m => m.enabled).length; }
79
+ };
80
+ };