@lazyneoaz/nkxchat 1.0.3 → 1.0.4
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/package.json +2 -1
- package/src/apis/listenMqtt.js +88 -1
- package/src/apis/scheduler.js +129 -0
- package/src/apis/sendEffect.js +182 -8
- package/src/apis/sendMessage.js +147 -159
- package/src/apis/sendMessageMqtt.js +190 -174
- package/src/apis/sendTypingIndicator.js +68 -34
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lazyneoaz/nkxchat",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
4
4
|
"type": "commonjs",
|
|
5
5
|
"types": "src/types/index.d.ts",
|
|
6
6
|
"description": "Advanced Facebook Chat API client for building Messenger bots — real-time messaging, thread management, MQTT, and session stability.",
|
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
"nkxchat"
|
|
40
40
|
],
|
|
41
41
|
"dependencies": {
|
|
42
|
+
"@dongdev/fca-unofficial": "^4.0.3",
|
|
42
43
|
"axios": "^1.13.5",
|
|
43
44
|
"axios-cookiejar-support": "^4.0.7",
|
|
44
45
|
"bluebird": "^3.7.2",
|
package/src/apis/listenMqtt.js
CHANGED
|
@@ -593,9 +593,72 @@ function mqttConf(ctx, overrides) {
|
|
|
593
593
|
return ctx._mqttOpt;
|
|
594
594
|
}
|
|
595
595
|
|
|
596
|
+
function createMiddlewareSystem() {
|
|
597
|
+
const stack = [];
|
|
598
|
+
let nextId = 0;
|
|
599
|
+
function use(nameOrFn, fn) {
|
|
600
|
+
let name, middlewareFn;
|
|
601
|
+
if (typeof nameOrFn === "string" && typeof fn === "function") {
|
|
602
|
+
name = nameOrFn; middlewareFn = fn;
|
|
603
|
+
} else if (typeof nameOrFn === "function") {
|
|
604
|
+
middlewareFn = nameOrFn; name = `middleware_${nextId++}`;
|
|
605
|
+
} else throw new Error("Middleware must be a function or (name, function)");
|
|
606
|
+
const entry = { name, fn: middlewareFn, enabled: true };
|
|
607
|
+
stack.push(entry);
|
|
608
|
+
return function remove() {
|
|
609
|
+
const i = stack.indexOf(entry);
|
|
610
|
+
if (i !== -1) stack.splice(i, 1);
|
|
611
|
+
};
|
|
612
|
+
}
|
|
613
|
+
function remove(identifier) {
|
|
614
|
+
const i = typeof identifier === "string"
|
|
615
|
+
? stack.findIndex(e => e.name === identifier)
|
|
616
|
+
: stack.findIndex(e => e.fn === identifier);
|
|
617
|
+
if (i !== -1) { stack.splice(i, 1); return true; }
|
|
618
|
+
return false;
|
|
619
|
+
}
|
|
620
|
+
function clear() { stack.length = 0; }
|
|
621
|
+
function list() { return stack.filter(e => e.enabled).map(e => e.name); }
|
|
622
|
+
function setEnabled(name, enabled) {
|
|
623
|
+
const e = stack.find(e => e.name === name);
|
|
624
|
+
if (e) { e.enabled = enabled; return true; }
|
|
625
|
+
return false;
|
|
626
|
+
}
|
|
627
|
+
function process(event, finalCallback) {
|
|
628
|
+
const active = stack.filter(e => e.enabled);
|
|
629
|
+
if (!active.length) return finalCallback(null, event);
|
|
630
|
+
let idx = 0;
|
|
631
|
+
function next(err) {
|
|
632
|
+
if (err && err !== false && err !== null) return finalCallback(err, null);
|
|
633
|
+
if (err === false || err === null) return finalCallback(null, null);
|
|
634
|
+
if (idx >= active.length) return finalCallback(null, event);
|
|
635
|
+
const mw = active[idx++];
|
|
636
|
+
try {
|
|
637
|
+
const r = mw.fn(event, next);
|
|
638
|
+
if (r && typeof r.then === "function") r.then(() => next()).catch(e => next(e));
|
|
639
|
+
else if (r === false || r === null) finalCallback(null, null);
|
|
640
|
+
} catch (e) { next(e); }
|
|
641
|
+
}
|
|
642
|
+
next();
|
|
643
|
+
}
|
|
644
|
+
function wrapCallback(callback) {
|
|
645
|
+
return function(err, event) {
|
|
646
|
+
if (err) return callback(err, null);
|
|
647
|
+
if (!event) return callback(null, null);
|
|
648
|
+
process(event, (mwErr, processed) => {
|
|
649
|
+
if (mwErr) return callback(mwErr, null);
|
|
650
|
+
if (processed === null) return;
|
|
651
|
+
callback(null, processed);
|
|
652
|
+
});
|
|
653
|
+
};
|
|
654
|
+
}
|
|
655
|
+
return { use, remove, clear, list, setEnabled, process, wrapCallback, get count() { return stack.filter(e => e.enabled).length; } };
|
|
656
|
+
}
|
|
657
|
+
|
|
596
658
|
module.exports = (defaultFuncs, api, ctx, opts) => {
|
|
597
659
|
const identity = () => {};
|
|
598
660
|
let globalCallback = identity;
|
|
661
|
+
if (!ctx._middleware) ctx._middleware = createMiddlewareSystem();
|
|
599
662
|
|
|
600
663
|
function emitAuthError(reason, detail) {
|
|
601
664
|
try { if (ctx._autoCycleTimer) clearTimeout(ctx._autoCycleTimer); } catch (_) { }
|
|
@@ -930,7 +993,7 @@ module.exports = (defaultFuncs, api, ctx, opts) => {
|
|
|
930
993
|
|
|
931
994
|
const msgEmitter = new MessageEmitter();
|
|
932
995
|
|
|
933
|
-
|
|
996
|
+
const baseCallback = callback || function(error, message) {
|
|
934
997
|
if (error) {
|
|
935
998
|
utils.error("MQTT", "Emit error");
|
|
936
999
|
return msgEmitter.emit("error", error);
|
|
@@ -941,6 +1004,12 @@ module.exports = (defaultFuncs, api, ctx, opts) => {
|
|
|
941
1004
|
msgEmitter.emit("message", message);
|
|
942
1005
|
};
|
|
943
1006
|
|
|
1007
|
+
globalCallback = ctx._middleware && ctx._middleware.count
|
|
1008
|
+
? ctx._middleware.wrapCallback(baseCallback)
|
|
1009
|
+
: baseCallback;
|
|
1010
|
+
|
|
1011
|
+
ctx._emitter = msgEmitter;
|
|
1012
|
+
|
|
944
1013
|
ctx._listeningActive = true;
|
|
945
1014
|
ctx._lastListenCallback = callback || null;
|
|
946
1015
|
|
|
@@ -990,6 +1059,24 @@ module.exports = (defaultFuncs, api, ctx, opts) => {
|
|
|
990
1059
|
|
|
991
1060
|
api.stopListening = msgEmitter.stopListening;
|
|
992
1061
|
api.stopListeningAsync = msgEmitter.stopListeningAsync;
|
|
1062
|
+
|
|
1063
|
+
api.useMiddleware = function(nameOrFn, fn) {
|
|
1064
|
+
const remove = ctx._middleware.use(nameOrFn, fn);
|
|
1065
|
+
globalCallback = ctx._middleware.wrapCallback(baseCallback || identity);
|
|
1066
|
+
return remove;
|
|
1067
|
+
};
|
|
1068
|
+
api.removeMiddleware = function(identifier) {
|
|
1069
|
+
const ok = ctx._middleware.remove(identifier);
|
|
1070
|
+
if (!ctx._middleware.count) globalCallback = baseCallback || identity;
|
|
1071
|
+
return ok;
|
|
1072
|
+
};
|
|
1073
|
+
api.clearMiddleware = function() {
|
|
1074
|
+
ctx._middleware.clear();
|
|
1075
|
+
globalCallback = baseCallback || identity;
|
|
1076
|
+
};
|
|
1077
|
+
api.listMiddleware = function() { return ctx._middleware.list(); };
|
|
1078
|
+
api.setMiddlewareEnabled = function(name, enabled) { return ctx._middleware.setEnabled(name, enabled); };
|
|
1079
|
+
|
|
993
1080
|
return msgEmitter;
|
|
994
1081
|
};
|
|
995
1082
|
};
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
module.exports = function (defaultFuncs, api, ctx) {
|
|
4
|
+
const scheduledMessages = new Map();
|
|
5
|
+
let nextId = 1;
|
|
6
|
+
|
|
7
|
+
function toTimestamp(when) {
|
|
8
|
+
if (when instanceof Date) return when.getTime();
|
|
9
|
+
if (typeof when === "number") return when;
|
|
10
|
+
if (typeof when === "string") return new Date(when).getTime();
|
|
11
|
+
return NaN;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function scheduleMessage(message, threadID, when, options) {
|
|
15
|
+
options = options || {};
|
|
16
|
+
const timestamp = toTimestamp(when);
|
|
17
|
+
if (isNaN(timestamp)) throw new Error("Invalid 'when'. Must be Date, number (ms timestamp), or ISO string.");
|
|
18
|
+
const now = Date.now();
|
|
19
|
+
if (timestamp <= now) throw new Error("Scheduled time must be in the future.");
|
|
20
|
+
|
|
21
|
+
const id = `scheduled_${nextId++}_${now}`;
|
|
22
|
+
const delay = timestamp - now;
|
|
23
|
+
|
|
24
|
+
const scheduled = {
|
|
25
|
+
id,
|
|
26
|
+
message,
|
|
27
|
+
threadID,
|
|
28
|
+
timestamp,
|
|
29
|
+
createdAt: now,
|
|
30
|
+
options: {
|
|
31
|
+
replyMessageID: options.replyMessageID || null,
|
|
32
|
+
callback: options.callback || null,
|
|
33
|
+
},
|
|
34
|
+
cancelled: false,
|
|
35
|
+
timeout: null,
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
scheduled.timeout = setTimeout(() => {
|
|
39
|
+
if (scheduled.cancelled) return;
|
|
40
|
+
const sendFn = api.sendMessage || api.sendMessageMqtt;
|
|
41
|
+
if (!sendFn) return;
|
|
42
|
+
Promise.resolve(
|
|
43
|
+
sendFn(message, threadID, scheduled.options.callback || (() => {}), scheduled.options.replyMessageID)
|
|
44
|
+
).then(() => {
|
|
45
|
+
scheduledMessages.delete(id);
|
|
46
|
+
}).catch(() => {
|
|
47
|
+
scheduledMessages.delete(id);
|
|
48
|
+
});
|
|
49
|
+
}, delay);
|
|
50
|
+
|
|
51
|
+
scheduledMessages.set(id, scheduled);
|
|
52
|
+
return id;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function cancelScheduledMessage(id) {
|
|
56
|
+
const s = scheduledMessages.get(id);
|
|
57
|
+
if (!s || s.cancelled) return false;
|
|
58
|
+
clearTimeout(s.timeout);
|
|
59
|
+
s.cancelled = true;
|
|
60
|
+
scheduledMessages.delete(id);
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function getScheduledMessage(id) {
|
|
65
|
+
const s = scheduledMessages.get(id);
|
|
66
|
+
if (!s || s.cancelled) return null;
|
|
67
|
+
return {
|
|
68
|
+
id: s.id,
|
|
69
|
+
message: s.message,
|
|
70
|
+
threadID: s.threadID,
|
|
71
|
+
timestamp: s.timestamp,
|
|
72
|
+
createdAt: s.createdAt,
|
|
73
|
+
options: { ...s.options },
|
|
74
|
+
timeUntilSend: s.timestamp - Date.now(),
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function listScheduledMessages() {
|
|
79
|
+
const now = Date.now();
|
|
80
|
+
return Array.from(scheduledMessages.values())
|
|
81
|
+
.filter(s => !s.cancelled)
|
|
82
|
+
.map(s => ({
|
|
83
|
+
id: s.id,
|
|
84
|
+
message: s.message,
|
|
85
|
+
threadID: s.threadID,
|
|
86
|
+
timestamp: s.timestamp,
|
|
87
|
+
createdAt: s.createdAt,
|
|
88
|
+
options: { ...s.options },
|
|
89
|
+
timeUntilSend: s.timestamp - now,
|
|
90
|
+
}))
|
|
91
|
+
.sort((a, b) => a.timestamp - b.timestamp);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function cancelAllScheduledMessages() {
|
|
95
|
+
let count = 0;
|
|
96
|
+
for (const id of Array.from(scheduledMessages.keys())) {
|
|
97
|
+
if (cancelScheduledMessage(id)) count++;
|
|
98
|
+
}
|
|
99
|
+
return count;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function getScheduledCount() {
|
|
103
|
+
return scheduledMessages.size;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const cleanupInterval = setInterval(() => {
|
|
107
|
+
const now = Date.now();
|
|
108
|
+
for (const [id, s] of scheduledMessages.entries()) {
|
|
109
|
+
if (s.cancelled || s.timestamp < now) scheduledMessages.delete(id);
|
|
110
|
+
}
|
|
111
|
+
}, 5 * 60 * 1000);
|
|
112
|
+
|
|
113
|
+
function destroy() {
|
|
114
|
+
clearInterval(cleanupInterval);
|
|
115
|
+
return cancelAllScheduledMessages();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
ctx._scheduler = { destroy };
|
|
119
|
+
|
|
120
|
+
return {
|
|
121
|
+
scheduleMessage,
|
|
122
|
+
cancelScheduledMessage,
|
|
123
|
+
getScheduledMessage,
|
|
124
|
+
listScheduledMessages,
|
|
125
|
+
cancelAllScheduledMessages,
|
|
126
|
+
getScheduledCount,
|
|
127
|
+
destroy,
|
|
128
|
+
};
|
|
129
|
+
};
|
package/src/apis/sendEffect.js
CHANGED
|
@@ -40,6 +40,131 @@ function resolveEffect(name) {
|
|
|
40
40
|
return EFFECTS[key] ? EFFECTS[key].tag : name.toUpperCase();
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
function buildForm(effectTag, msgObj, threadID, ctx) {
|
|
44
|
+
const messageAndOTID = utils.generateOfflineThreadingID();
|
|
45
|
+
const timestamp = Date.now();
|
|
46
|
+
const messagingTag = 'fb.messaging.effects.' + effectTag;
|
|
47
|
+
const body = typeof msgObj === 'string' ? msgObj : (msgObj.body || '');
|
|
48
|
+
|
|
49
|
+
const form = {
|
|
50
|
+
client: 'mercury',
|
|
51
|
+
action_type: 'ma-type:user-generated-message',
|
|
52
|
+
author: 'fbid:' + ctx.userID,
|
|
53
|
+
timestamp,
|
|
54
|
+
timestamp_absolute: 'Today',
|
|
55
|
+
timestamp_relative: utils.generateTimestampRelative(),
|
|
56
|
+
timestamp_time_passed:'0',
|
|
57
|
+
is_unread: false,
|
|
58
|
+
is_cleared: false,
|
|
59
|
+
is_forward: false,
|
|
60
|
+
is_filtered_content: false,
|
|
61
|
+
is_filtered_content_bh: false,
|
|
62
|
+
is_filtered_content_account: false,
|
|
63
|
+
is_filtered_content_quasar: false,
|
|
64
|
+
is_filtered_content_invalid_app: false,
|
|
65
|
+
is_spoof_warning: false,
|
|
66
|
+
source: 'source:chat:web',
|
|
67
|
+
'source_tags[0]': 'source:chat',
|
|
68
|
+
body,
|
|
69
|
+
html_body: false,
|
|
70
|
+
ui_push_phase: 'V3',
|
|
71
|
+
status: '0',
|
|
72
|
+
offline_threading_id: messageAndOTID,
|
|
73
|
+
message_id: messageAndOTID,
|
|
74
|
+
threading_id: utils.generateThreadingID(ctx.clientID),
|
|
75
|
+
'ephemeral_ttl_mode:':'0',
|
|
76
|
+
manual_retry_cnt: '0',
|
|
77
|
+
has_attachment: !!(msgObj && msgObj.sticker),
|
|
78
|
+
signatureID: utils.getSignatureID(),
|
|
79
|
+
has_lightweight_action: true,
|
|
80
|
+
'lightweight_action_attached[message_id]': messageAndOTID,
|
|
81
|
+
'lightweight_action_attached[messaging_tag]': messagingTag,
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
if (msgObj && msgObj.sticker) {
|
|
85
|
+
form.sticker_id = msgObj.sticker;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (typeof msgObj === 'object' && msgObj.reply_to_message_id) {
|
|
89
|
+
form.replied_to_message_id = msgObj.reply_to_message_id;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const tid = String(threadID);
|
|
93
|
+
const isGroup = tid.length >= 16;
|
|
94
|
+
if (isGroup) {
|
|
95
|
+
form.thread_fbid = tid;
|
|
96
|
+
} else {
|
|
97
|
+
form['specific_to_list[0]'] = 'fbid:' + tid;
|
|
98
|
+
form['specific_to_list[1]'] = 'fbid:' + ctx.userID;
|
|
99
|
+
form.other_user_fbid = tid;
|
|
100
|
+
form.client_thread_id = 'root:' + messageAndOTID;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return { form, messageAndOTID, timestamp, messagingTag };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function buildMqttPayload(effectTag, msgObj, threadID, ctx) {
|
|
107
|
+
const otid = utils.generateOfflineThreadingID();
|
|
108
|
+
const epoch_id = utils.generateOfflineThreadingID();
|
|
109
|
+
const timestamp = Date.now();
|
|
110
|
+
const body = typeof msgObj === 'string' ? msgObj : (msgObj && msgObj.body || '');
|
|
111
|
+
const tid = String(threadID);
|
|
112
|
+
|
|
113
|
+
const sendPayload = {
|
|
114
|
+
thread_id: tid,
|
|
115
|
+
otid: otid.toString(),
|
|
116
|
+
source: 0,
|
|
117
|
+
send_type: 1,
|
|
118
|
+
sync_group: 1,
|
|
119
|
+
text: body,
|
|
120
|
+
initiating_source: 1,
|
|
121
|
+
skip_url_preview_gen: 0,
|
|
122
|
+
lightweight_action_attached: {
|
|
123
|
+
messaging_tag: 'fb.messaging.effects.' + effectTag,
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
if (typeof msgObj === 'object' && msgObj && msgObj.sticker) {
|
|
128
|
+
sendPayload.send_type = 2;
|
|
129
|
+
sendPayload.sticker_id = msgObj.sticker;
|
|
130
|
+
sendPayload.text = null;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const request_id = ++ctx.wsReqNumber;
|
|
134
|
+
const content = {
|
|
135
|
+
app_id: '2220391788200892',
|
|
136
|
+
payload: JSON.stringify({
|
|
137
|
+
tasks: [
|
|
138
|
+
{
|
|
139
|
+
label: '46',
|
|
140
|
+
payload: JSON.stringify(sendPayload),
|
|
141
|
+
queue_name: tid,
|
|
142
|
+
task_id: ++ctx.wsTaskNumber,
|
|
143
|
+
failure_count: null,
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
label: '21',
|
|
147
|
+
payload: JSON.stringify({
|
|
148
|
+
thread_id: tid,
|
|
149
|
+
last_read_watermark_ts: timestamp,
|
|
150
|
+
sync_group: 1,
|
|
151
|
+
}),
|
|
152
|
+
queue_name: tid,
|
|
153
|
+
task_id: ++ctx.wsTaskNumber,
|
|
154
|
+
failure_count: null,
|
|
155
|
+
},
|
|
156
|
+
],
|
|
157
|
+
epoch_id,
|
|
158
|
+
version_id: '6120284488008082',
|
|
159
|
+
data_trace_id: null,
|
|
160
|
+
}),
|
|
161
|
+
request_id,
|
|
162
|
+
type: 3,
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
return { content, request_id, otid, timestamp };
|
|
166
|
+
}
|
|
167
|
+
|
|
43
168
|
module.exports = function (defaultFuncs, api, ctx) {
|
|
44
169
|
/**
|
|
45
170
|
* Lists all available send effects.
|
|
@@ -89,19 +214,68 @@ module.exports = function (defaultFuncs, api, ctx) {
|
|
|
89
214
|
const effectTag = resolveEffect(effectName);
|
|
90
215
|
utils.log('sendEffect', `Effect "${effectTag}" → thread ${threadID}`);
|
|
91
216
|
|
|
92
|
-
//
|
|
93
|
-
|
|
94
|
-
|
|
217
|
+
// ── Try HTTP first ───────────────────────────────────────────────────
|
|
218
|
+
try {
|
|
219
|
+
const { form, messageAndOTID, timestamp } = buildForm(effectTag, message, threadID, ctx);
|
|
220
|
+
|
|
221
|
+
const resData = await defaultFuncs
|
|
222
|
+
.post('https://www.facebook.com/messaging/send/', ctx.jar, form, { ...ctx, requestThreadID: String(threadID) })
|
|
223
|
+
.then(utils.parseAndCheckLogin(ctx, defaultFuncs));
|
|
224
|
+
|
|
225
|
+
if (!resData) throw new Error('Empty response from messaging/send');
|
|
226
|
+
if (resData.error) throw new Error(JSON.stringify(resData));
|
|
227
|
+
|
|
228
|
+
const actions = (resData.payload && resData.payload.actions) || [];
|
|
229
|
+
const msgInfo = actions.reduce((p, v) => ({
|
|
230
|
+
threadID: v.thread_fbid || p.threadID,
|
|
231
|
+
messageID: v.message_id || p.messageID,
|
|
232
|
+
timestamp: v.timestamp || p.timestamp,
|
|
233
|
+
}), { threadID, messageID: messageAndOTID, timestamp });
|
|
234
|
+
|
|
235
|
+
return callback(null, { ...msgInfo, effect: effectTag, method: 'http' });
|
|
236
|
+
} catch (httpErr) {
|
|
237
|
+
utils.warn('sendEffect', `HTTP failed: ${httpErr.message}. Falling back to MQTT.`);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// ── MQTT fallback ────────────────────────────────────────────────────
|
|
95
241
|
if (!ctx.mqttClient) {
|
|
96
242
|
throw new Error('MQTT is not connected. Call api.listenMqtt() first.');
|
|
97
243
|
}
|
|
98
244
|
|
|
99
|
-
const
|
|
100
|
-
? { body: message, effect: effectTag }
|
|
101
|
-
: { ...message, effect: effectTag };
|
|
245
|
+
const { content, request_id, otid, timestamp } = buildMqttPayload(effectTag, message, threadID, ctx);
|
|
102
246
|
|
|
103
|
-
|
|
104
|
-
|
|
247
|
+
await new Promise((res, rej) => {
|
|
248
|
+
let done = false;
|
|
249
|
+
const timer = setTimeout(() => {
|
|
250
|
+
if (done) return;
|
|
251
|
+
done = true;
|
|
252
|
+
ctx.mqttClient.removeListener('message', onMsg);
|
|
253
|
+
rej(new Error('MQTT effect send timeout'));
|
|
254
|
+
}, 15000);
|
|
255
|
+
|
|
256
|
+
const onMsg = (topic, raw) => {
|
|
257
|
+
if (topic !== '/ls_resp') return;
|
|
258
|
+
let parsed;
|
|
259
|
+
try { parsed = JSON.parse(raw.toString()); parsed.payload = JSON.parse(parsed.payload); } catch { return; }
|
|
260
|
+
if (parsed.request_id !== request_id) return;
|
|
261
|
+
if (done) return;
|
|
262
|
+
done = true;
|
|
263
|
+
clearTimeout(timer);
|
|
264
|
+
ctx.mqttClient.removeListener('message', onMsg);
|
|
265
|
+
callback(null, { threadID, messageID: otid, timestamp, effect: effectTag, method: 'mqtt' });
|
|
266
|
+
res();
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
ctx.mqttClient.on('message', onMsg);
|
|
270
|
+
ctx.mqttClient.publish('/ls_req', JSON.stringify(content), { qos: 1, retain: false }, (err) => {
|
|
271
|
+
if (err && !done) {
|
|
272
|
+
done = true;
|
|
273
|
+
clearTimeout(timer);
|
|
274
|
+
ctx.mqttClient.removeListener('message', onMsg);
|
|
275
|
+
rej(err);
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
});
|
|
105
279
|
|
|
106
280
|
} catch (err) {
|
|
107
281
|
utils.error('sendEffect', err.message || err);
|