@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.
- package/CHANGELOG.md +3 -0
- package/DOCS.md +501 -3
- package/index.d.ts +746 -700
- package/package.json +1 -1
- package/src/api/action/enableAutoSaveAppState.js +73 -0
- package/src/api/messaging/scheduler.js +264 -0
- package/src/api/socket/core/connectMqtt.js +17 -5
- package/src/api/socket/core/emitAuth.js +54 -0
- package/src/api/socket/listenMqtt.js +108 -1
- package/src/api/socket/middleware/index.js +216 -0
- package/src/api/users/getUserInfo.js +8 -1
- package/src/core/sendReqMqtt.js +47 -14
- package/src/utils/format.js +6 -2
|
@@ -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
|
-
|
|
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; });
|
package/src/core/sendReqMqtt.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
function
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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;
|
package/src/utils/format.js
CHANGED
|
@@ -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:
|
|
603
|
+
body: body,
|
|
604
|
+
args: args,
|
|
601
605
|
threadID: formatID(
|
|
602
606
|
(
|
|
603
607
|
originalMessage.thread_fbid || originalMessage.other_user_fbid
|