@dongdev/fca-unofficial 3.0.0 → 3.0.3

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.
@@ -0,0 +1,216 @@
1
+ "use strict";
2
+ const logger = require("../../../func/logger");
3
+
4
+ /**
5
+ * Middleware system for filtering and processing events before they are emitted
6
+ *
7
+ * Middleware functions receive (event, next) where:
8
+ * - event: The event object (can be modified)
9
+ * - next: Function to call to continue to next middleware
10
+ * - next() - continue to next middleware
11
+ * - next(false) or next(null) - stop processing, don't emit event
12
+ * - next(error) - emit error instead
13
+ */
14
+ module.exports = function createMiddlewareSystem() {
15
+ const middlewareStack = [];
16
+
17
+ /**
18
+ * Add middleware to the stack
19
+ * @param {Function|string} middleware - Middleware function or name for named middleware
20
+ * @param {Function} [fn] - Middleware function (if first param is name)
21
+ * @returns {Function} Unsubscribe function
22
+ */
23
+ function use(middleware, fn) {
24
+ let middlewareFn, name;
25
+
26
+ if (typeof middleware === "string" && typeof fn === "function") {
27
+ name = middleware;
28
+ middlewareFn = fn;
29
+ } else if (typeof middleware === "function") {
30
+ middlewareFn = middleware;
31
+ name = `middleware_${middlewareStack.length}`;
32
+ } else {
33
+ throw new Error("Middleware must be a function or (name, function)");
34
+ }
35
+
36
+ const wrapped = {
37
+ name,
38
+ fn: middlewareFn,
39
+ enabled: true
40
+ };
41
+
42
+ middlewareStack.push(wrapped);
43
+ logger(`Middleware "${name}" added`, "info");
44
+
45
+ // Return unsubscribe function
46
+ return function remove() {
47
+ const index = middlewareStack.indexOf(wrapped);
48
+ if (index !== -1) {
49
+ middlewareStack.splice(index, 1);
50
+ logger(`Middleware "${name}" removed`, "info");
51
+ }
52
+ };
53
+ }
54
+
55
+ /**
56
+ * Remove middleware by name or function
57
+ * @param {string|Function} identifier - Name or function to remove
58
+ * @returns {boolean} True if removed
59
+ */
60
+ function remove(identifier) {
61
+ if (typeof identifier === "string") {
62
+ const index = middlewareStack.findIndex(m => m.name === identifier);
63
+ if (index !== -1) {
64
+ const removed = middlewareStack.splice(index, 1)[0];
65
+ logger(`Middleware "${removed.name}" removed`, "info");
66
+ return true;
67
+ }
68
+ return false;
69
+ } else if (typeof identifier === "function") {
70
+ const index = middlewareStack.findIndex(m => m.fn === identifier);
71
+ if (index !== -1) {
72
+ const removed = middlewareStack.splice(index, 1)[0];
73
+ logger(`Middleware "${removed.name}" removed`, "info");
74
+ return true;
75
+ }
76
+ return false;
77
+ }
78
+ return false;
79
+ }
80
+
81
+ /**
82
+ * Remove all middleware
83
+ */
84
+ function clear() {
85
+ const count = middlewareStack.length;
86
+ middlewareStack.length = 0;
87
+ logger(`All middleware cleared (${count} removed)`, "info");
88
+ }
89
+
90
+ /**
91
+ * Get list of middleware names
92
+ * @returns {string[]} Array of middleware names
93
+ */
94
+ function list() {
95
+ return middlewareStack.filter(m => m.enabled).map(m => m.name);
96
+ }
97
+
98
+ /**
99
+ * Enable/disable middleware by name
100
+ * @param {string} name - Middleware name
101
+ * @param {boolean} enabled - Enable or disable
102
+ * @returns {boolean} True if found and updated
103
+ */
104
+ function setEnabled(name, enabled) {
105
+ const middleware = middlewareStack.find(m => m.name === name);
106
+ if (middleware) {
107
+ middleware.enabled = enabled;
108
+ logger(`Middleware "${name}" ${enabled ? "enabled" : "disabled"}`, "info");
109
+ return true;
110
+ }
111
+ return false;
112
+ }
113
+
114
+ /**
115
+ * Process event through middleware stack
116
+ * @param {*} event - Event object
117
+ * @param {Function} finalCallback - Callback to call after all middleware
118
+ * @returns {Promise} Promise that resolves when processing is complete
119
+ */
120
+ function process(event, finalCallback) {
121
+ if (!middlewareStack.length) {
122
+ return finalCallback(null, event);
123
+ }
124
+
125
+ let index = 0;
126
+ const enabledMiddleware = middlewareStack.filter(m => m.enabled);
127
+
128
+ function next(err) {
129
+ // Error occurred, stop processing
130
+ if (err && err !== false && err !== null) {
131
+ return finalCallback(err, null);
132
+ }
133
+
134
+ // Explicitly stopped (next(false) or next(null))
135
+ if (err === false || err === null) {
136
+ return finalCallback(null, null); // null event means don't emit
137
+ }
138
+
139
+ // No more middleware, call final callback
140
+ if (index >= enabledMiddleware.length) {
141
+ return finalCallback(null, event);
142
+ }
143
+
144
+ // Get next middleware
145
+ const middleware = enabledMiddleware[index++];
146
+
147
+ try {
148
+ // Call middleware with event and next
149
+ const result = middleware.fn(event, next);
150
+
151
+ // If middleware returns a promise, handle it
152
+ if (result && typeof result.then === "function") {
153
+ result
154
+ .then(() => next())
155
+ .catch(err => next(err));
156
+ } else if (result === false || result === null) {
157
+ // Middleware returned false/null, stop processing
158
+ finalCallback(null, null);
159
+ }
160
+ // If middleware called next() synchronously, it will continue
161
+ } catch (err) {
162
+ logger(`Middleware "${middleware.name}" error: ${err && err.message ? err.message : String(err)}`, "error");
163
+ next(err);
164
+ }
165
+ }
166
+
167
+ // Start processing
168
+ next();
169
+ }
170
+
171
+ /**
172
+ * Wrap a callback with middleware processing
173
+ * @param {Function} callback - Original callback (err, event)
174
+ * @returns {Function} Wrapped callback
175
+ */
176
+ function wrapCallback(callback) {
177
+ return function wrappedCallback(err, event) {
178
+ if (err) {
179
+ // Errors bypass middleware
180
+ return callback(err, null);
181
+ }
182
+
183
+ if (!event) {
184
+ return callback(null, null);
185
+ }
186
+
187
+ // Process event through middleware
188
+ process(event, (middlewareErr, processedEvent) => {
189
+ if (middlewareErr) {
190
+ return callback(middlewareErr, null);
191
+ }
192
+
193
+ // If processedEvent is null, middleware blocked the event
194
+ if (processedEvent === null) {
195
+ return; // Don't emit
196
+ }
197
+
198
+ // Emit processed event
199
+ callback(null, processedEvent);
200
+ });
201
+ };
202
+ }
203
+
204
+ return {
205
+ use,
206
+ remove,
207
+ clear,
208
+ list,
209
+ setEnabled,
210
+ process,
211
+ wrapCallback,
212
+ get count() {
213
+ return middlewareStack.filter(m => m.enabled).length;
214
+ }
215
+ };
216
+ };
@@ -268,11 +268,18 @@ module.exports = function (defaultFuncs, api, ctx) {
268
268
  isProcessingQueue = false;
269
269
  }
270
270
 
271
- setInterval(() => {
271
+ // Store interval reference for cleanup
272
+ const updateInterval = setInterval(() => {
272
273
  checkAndUpdateUsers();
273
274
  processQueue();
274
275
  }, 10000);
275
276
 
277
+ // Store interval in ctx for cleanup on logout/stop
278
+ if (!ctx._userInfoIntervals) {
279
+ ctx._userInfoIntervals = [];
280
+ }
281
+ ctx._userInfoIntervals.push(updateInterval);
282
+
276
283
  return function getUserInfo(idsOrId, callback) {
277
284
  let resolveFunc, rejectFunc;
278
285
  const returnPromise = new Promise((resolve, reject) => { resolveFunc = resolve; rejectFunc = reject; });
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
 
3
- function lsRequest(ctx, payload, options, callback) {
3
+ function sendReqMqtt(ctx, payload, options, callback) {
4
4
  return new Promise((resolve, reject) => {
5
5
  const cb = typeof options === "function" ? options : callback;
6
6
  const opts = typeof options === "object" && options ? options : {};
@@ -23,6 +23,25 @@ function lsRequest(ctx, payload, options, callback) {
23
23
  type: opts.type == null ? 3 : opts.type
24
24
  });
25
25
  let timer = null;
26
+ let cleaned = false;
27
+
28
+ // Cleanup function to ensure listeners and timers are always removed
29
+ const cleanup = () => {
30
+ if (cleaned) return;
31
+ cleaned = true;
32
+ try {
33
+ if (timer) {
34
+ clearTimeout(timer);
35
+ timer = null;
36
+ }
37
+ if (ctx.mqttClient && typeof ctx.mqttClient.removeListener === "function") {
38
+ ctx.mqttClient.removeListener("message", handleRes);
39
+ }
40
+ } catch (err) {
41
+ // Ignore cleanup errors
42
+ }
43
+ };
44
+
26
45
  const handleRes = (topic, message) => {
27
46
  if (topic !== respTopic) return;
28
47
  let msg;
@@ -33,8 +52,7 @@ function lsRequest(ctx, payload, options, callback) {
33
52
  }
34
53
  if (msg.request_id !== reqID) return;
35
54
  if (typeof opts.filter === "function" && !opts.filter(msg)) return;
36
- ctx.mqttClient.removeListener("message", handleRes);
37
- if (timer) clearTimeout(timer);
55
+ cleanup();
38
56
  try {
39
57
  msg.payload = typeof msg.payload === "string" ? JSON.parse(msg.payload) : msg.payload;
40
58
  } catch { }
@@ -42,22 +60,37 @@ function lsRequest(ctx, payload, options, callback) {
42
60
  if (cb) cb(null, out);
43
61
  resolve(out);
44
62
  };
45
- ctx.mqttClient.on("message", handleRes);
63
+
64
+ try {
65
+ ctx.mqttClient.on("message", handleRes);
66
+ } catch (err) {
67
+ cleanup();
68
+ const error = new Error("Failed to attach message listener");
69
+ if (cb) cb(error);
70
+ return reject(error);
71
+ }
72
+
46
73
  timer = setTimeout(() => {
47
- ctx.mqttClient.removeListener("message", handleRes);
74
+ cleanup();
48
75
  const err = new Error("MQTT response timeout");
49
76
  if (cb) cb(err);
50
77
  reject(err);
51
78
  }, timeoutMs);
52
- ctx.mqttClient.publish(reqTopic, form, { qos, retain }, (err) => {
53
- if (err) {
54
- if (timer) clearTimeout(timer);
55
- ctx.mqttClient.removeListener("message", handleRes);
56
- if (cb) cb(err);
57
- reject(err);
58
- }
59
- });
79
+
80
+ try {
81
+ ctx.mqttClient.publish(reqTopic, form, { qos, retain }, (err) => {
82
+ if (err) {
83
+ cleanup();
84
+ if (cb) cb(err);
85
+ reject(err);
86
+ }
87
+ });
88
+ } catch (err) {
89
+ cleanup();
90
+ if (cb) cb(err);
91
+ reject(err);
92
+ }
60
93
  });
61
- };
94
+ }
62
95
 
63
96
  module.exports = sendReqMqtt;
@@ -574,7 +574,8 @@ function formatDeltaMessage(m) {
574
574
  mentions: mentions,
575
575
  timestamp: md.timestamp,
576
576
  isGroup: !!md.threadKey.threadFbId,
577
- participantIDs: m.participants || []
577
+ participantIDs: (m.participants || []).map((/** @type {any} */ p) => formatID(p.toString())),
578
+ isUnread: md.isUnread !== undefined ? md.isUnread : false
578
579
  };
579
580
  }
580
581
 
@@ -585,6 +586,8 @@ function formatID(id) {
585
586
 
586
587
  function formatMessage(m) {
587
588
  var originalMessage = m.message ? m.message : m;
589
+ var body = originalMessage.body || "";
590
+ var args = body == "" ? [] : body.trim().split(/\s+/);
588
591
  var obj = {
589
592
  type: "message",
590
593
  senderName: originalMessage.sender_name,
@@ -597,7 +600,8 @@ function formatMessage(m) {
597
600
  return formatID(v.toString());
598
601
  })
599
602
  : [formatID(originalMessage.sender_fbid)],
600
- body: originalMessage.body || "",
603
+ body: body,
604
+ args: args,
601
605
  threadID: formatID(
602
606
  (
603
607
  originalMessage.thread_fbid || originalMessage.other_user_fbid