toru-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 (113) hide show
  1. package/CHANGELOG.md +64 -0
  2. package/README.md +319 -0
  3. package/advancedSystem.js +95 -0
  4. package/build/messagix.dll +0 -0
  5. package/build/messagix.so +0 -0
  6. package/checkUpdate.js +37 -0
  7. package/config.json +45 -0
  8. package/e2ee.js +719 -0
  9. package/index.js +715 -0
  10. package/lib/index.mjs +1412 -0
  11. package/package.json +69 -0
  12. package/src/OldMessage.js +357 -0
  13. package/src/addExternalModule.js +25 -0
  14. package/src/addUserToGroup.js +117 -0
  15. package/src/changeAdminStatus.js +105 -0
  16. package/src/changeArchivedStatus.js +57 -0
  17. package/src/changeAvatar.js +138 -0
  18. package/src/changeAvatarV2.js +86 -0
  19. package/src/changeAvt.js +85 -0
  20. package/src/changeBio.js +78 -0
  21. package/src/changeBlockedStatus.js +51 -0
  22. package/src/changeBlockedStatusMqtt.js +80 -0
  23. package/src/changeCover.js +72 -0
  24. package/src/changeGroupImage.js +137 -0
  25. package/src/changeName.js +79 -0
  26. package/src/changeNickname.js +61 -0
  27. package/src/changeThreadColor.js +67 -0
  28. package/src/changeThreadEmoji.js +57 -0
  29. package/src/changeUsername.js +59 -0
  30. package/src/createCommentPost.js +230 -0
  31. package/src/createNewGroup.js +90 -0
  32. package/src/createPoll.js +73 -0
  33. package/src/createPost.js +276 -0
  34. package/src/data/getThreadInfo.json +1 -0
  35. package/src/deleteMessage.js +58 -0
  36. package/src/deleteThread.js +58 -0
  37. package/src/editMessage.js +81 -0
  38. package/src/editMessageOld.js +67 -0
  39. package/src/follow.js +74 -0
  40. package/src/forwardAttachment.js +62 -0
  41. package/src/friendList.js +104 -0
  42. package/src/getAccess.js +112 -0
  43. package/src/getAvatarUser.js +78 -0
  44. package/src/getBotInitialData.js +42 -0
  45. package/src/getCtx.js +5 -0
  46. package/src/getCurrentUserID.js +7 -0
  47. package/src/getEmojiUrl.js +29 -0
  48. package/src/getFriendsList.js +85 -0
  49. package/src/getMessage.js +849 -0
  50. package/src/getOptions.js +5 -0
  51. package/src/getRegion.js +7 -0
  52. package/src/getThreadHistory.js +682 -0
  53. package/src/getThreadHistoryDeprecated.js +71 -0
  54. package/src/getThreadInfo.js +232 -0
  55. package/src/getThreadInfoDeprecated.js +56 -0
  56. package/src/getThreadList.js +213 -0
  57. package/src/getThreadListDeprecated.js +46 -0
  58. package/src/getThreadPictures.js +59 -0
  59. package/src/getThreadTheme.js +81 -0
  60. package/src/getUID.js +119 -0
  61. package/src/getUserID.js +61 -0
  62. package/src/getUserInfo.js +66 -0
  63. package/src/handleFriendRequest.js +46 -0
  64. package/src/handleMessageRequest.js +47 -0
  65. package/src/httpGet.js +49 -0
  66. package/src/httpPost.js +48 -0
  67. package/src/httpPostFormData.js +70 -0
  68. package/src/listenMqtt.js +955 -0
  69. package/src/listenNotification.js +85 -0
  70. package/src/logout.js +19 -0
  71. package/src/markAsDelivered.js +47 -0
  72. package/src/markAsRead.js +72 -0
  73. package/src/markAsReadAll.js +40 -0
  74. package/src/markAsSeen.js +48 -0
  75. package/src/metaTheme.js +189 -0
  76. package/src/muteThread.js +45 -0
  77. package/src/note.js +228 -0
  78. package/src/pinMessage.js +59 -0
  79. package/src/refreshFb_dtsg.js +91 -0
  80. package/src/removeSuspiciousAccount.js +78 -0
  81. package/src/removeUserFromGroup.js +81 -0
  82. package/src/reply.js +442 -0
  83. package/src/resolvePhotoUrl.js +47 -0
  84. package/src/searchForThread.js +53 -0
  85. package/src/searchFriends.js +140 -0
  86. package/src/searchStickers.js +53 -0
  87. package/src/send.js +46 -0
  88. package/src/sendButtons.js +163 -0
  89. package/src/sendComment.js +159 -0
  90. package/src/sendFriendRequest.js +112 -0
  91. package/src/sendMessage.js +440 -0
  92. package/src/sendMessageMqtt.js +323 -0
  93. package/src/sendTypingIndicator.js +54 -0
  94. package/src/setActiveStatus.js +92 -0
  95. package/src/setMessageReaction.js +149 -0
  96. package/src/setMessageReactionMqtt.js +62 -0
  97. package/src/setPostReaction.js +114 -0
  98. package/src/setProfileGuard.js +44 -0
  99. package/src/setProfileLock.js +97 -0
  100. package/src/setStoryReaction.js +133 -0
  101. package/src/setStorySeen.js +108 -0
  102. package/src/setThreadTheme.js +102 -0
  103. package/src/setTitle.js +92 -0
  104. package/src/shareContact.js +110 -0
  105. package/src/shareLink.js +59 -0
  106. package/src/stopListenMqtt.js +37 -0
  107. package/src/storyManager.js +357 -0
  108. package/src/suggestFriend.js +132 -0
  109. package/src/threadColors.js +131 -0
  110. package/src/unfriend.js +54 -0
  111. package/src/unsendMessage.js +68 -0
  112. package/src/uploadAttachment.js +115 -0
  113. package/utils.js +3044 -0
@@ -0,0 +1,955 @@
1
+ /* eslint-disable no-redeclare */
2
+ "use strict";
3
+ var utils = require("../utils");
4
+ var log = require("npmlog");
5
+ var mqtt = require('mqtt');
6
+ var WebSocket = require('ws');
7
+ var Transform = require('stream').Transform;
8
+ const EventEmitter = require('events');
9
+ var e2eeBridge = require("../e2ee");
10
+
11
+ // ─── ANSI colour helpers ───────────────────────────────────────────────────────
12
+ var C = {
13
+ reset: '\x1b[0m',
14
+ bold: '\x1b[1m',
15
+ dim: '\x1b[2m',
16
+ // foregrounds
17
+ black: '\x1b[30m',
18
+ red: '\x1b[31m',
19
+ green: '\x1b[32m',
20
+ yellow: '\x1b[33m',
21
+ blue: '\x1b[34m',
22
+ magenta: '\x1b[35m',
23
+ cyan: '\x1b[36m',
24
+ white: '\x1b[37m',
25
+ // bright foregrounds
26
+ bBlack: '\x1b[90m',
27
+ bRed: '\x1b[91m',
28
+ bGreen: '\x1b[92m',
29
+ bYellow: '\x1b[93m',
30
+ bBlue: '\x1b[94m',
31
+ bMagenta: '\x1b[95m',
32
+ bCyan: '\x1b[96m',
33
+ bWhite: '\x1b[97m',
34
+ // backgrounds
35
+ bgBlue: '\x1b[44m',
36
+ bgCyan: '\x1b[46m',
37
+ bgMagenta: '\x1b[45m',
38
+ bgGreen: '\x1b[42m',
39
+ bgBlack: '\x1b[40m',
40
+ };
41
+
42
+ // ─── MQTT Spinner ──────────────────────────────────────────────────────────────
43
+ var _mqttSpinner = null;
44
+
45
+ function startMqttSpinner(region) {
46
+ var frames = ['⠋','⠙','⠹','⠸','⠼','⠴','⠦','⠧','⠇','⠏'];
47
+ var fi = 0;
48
+ var regionStr = region ? (' ' + C.dim + C.bCyan + '[' + region.toUpperCase() + ']' + C.reset) : '';
49
+ process.stdout.write('\n');
50
+ _mqttSpinner = setInterval(function () {
51
+ var frame = frames[fi++ % frames.length];
52
+ process.stdout.write(
53
+ '\r ' +
54
+ C.bold + C.bCyan + frame + C.reset + ' ' +
55
+ C.cyan + 'alpha-fca' + C.reset + ' ' +
56
+ C.dim + 'connecting to MQTT' + C.reset +
57
+ regionStr +
58
+ C.dim + ' ...' + C.reset +
59
+ ' '
60
+ );
61
+ }, 80);
62
+ }
63
+
64
+ function stopMqttSpinner() {
65
+ if (_mqttSpinner) {
66
+ clearInterval(_mqttSpinner);
67
+ _mqttSpinner = null;
68
+ }
69
+ // erase the spinner line completely
70
+ process.stdout.write('\r\x1b[2K');
71
+ }
72
+
73
+ function printMqttBanner(region, autoReconnect) {
74
+ stopMqttSpinner();
75
+
76
+ var titleClr = C.bold + C.bGreen;
77
+ var labelClr = C.bold + C.bWhite;
78
+ var valClr = C.bYellow;
79
+ var accentClr = C.bold + C.bMagenta;
80
+ var urlClr = C.bBlue;
81
+ var rst = C.reset;
82
+
83
+ var regionVal = (region || '').toUpperCase();
84
+ var reconnTxt = autoReconnect ? 'Enabled (3s)' : 'Disabled';
85
+ var reconnClr = autoReconnect ? C.bGreen : C.bRed;
86
+ var reconnVal = reconnClr + reconnTxt + rst;
87
+
88
+ var rows = [
89
+ titleClr + ' ✅ alpha-fca MQTT Connected' + rst,
90
+ '',
91
+ labelClr + ' 📍 Region ' + rst + valClr + regionVal + rst,
92
+ labelClr + ' 🔄 Auto-reconnect ' + rst + reconnVal,
93
+ urlClr + ' 🌐 github.com/alpha-fca/alpha-fca' + rst,
94
+ '',
95
+ accentClr + ' 💎 alpha-fca v1.0.0' + rst
96
+ ];
97
+
98
+ process.stdout.write('\n');
99
+ rows.forEach(function (line) {
100
+ console.log(line);
101
+ });
102
+ process.stdout.write('\n');
103
+ }
104
+
105
+ /**
106
+ * Facebook sends non-standard MQTT packets where PUBACK/SUBACK have
107
+ * non-zero reserved flag bits (e.g. 0x4F instead of 0x40).
108
+ * mqtt-packet strictly rejects these. This transform stream patches
109
+ * the first byte of each MQTT frame to clear the lower nibble (flags),
110
+ * keeping only the packet type (upper nibble).
111
+ *
112
+ * MQTT fixed header: byte[0] = (type << 4) | flags
113
+ * For PUBACK (type=4): valid = 0x40, FB may send 0x4F → we clear to 0x40
114
+ */
115
+ function createMqttPatchStream() {
116
+ var buf = null;
117
+
118
+ // Walk frame by frame. For types that must have flags=0 per the MQTT spec
119
+ // (CONNACK=2, PUBACK=4, SUBACK=9, UNSUBACK=11, PINGRESP=13), clear the
120
+ // lower nibble that Facebook sets to non-zero values.
121
+ var stream = new Transform({
122
+ transform: function (chunk, encoding, callback) {
123
+ if (!Buffer.isBuffer(chunk)) chunk = Buffer.from(chunk, encoding);
124
+
125
+ // Prepend any leftover bytes from the previous chunk
126
+ var out;
127
+ if (buf) {
128
+ out = Buffer.concat([buf, chunk]);
129
+ buf = null;
130
+ } else {
131
+ out = Buffer.from(chunk);
132
+ }
133
+
134
+ var i = 0;
135
+ while (i < out.length) {
136
+ var b = out[i];
137
+ var type = (b >> 4) & 0x0F;
138
+ var flags = b & 0x0F;
139
+ // Types that MUST have flags=0:
140
+ if (flags !== 0 && (type === 4 || type === 9 || type === 11 || type === 13 || type === 2)) {
141
+ out[i] = (b & 0xF0); // clear lower nibble
142
+ }
143
+ // Skip past this frame: read the varint length
144
+ i++;
145
+ var multiplier = 1;
146
+ var frameLen = 0;
147
+ var lenOk = false;
148
+ while (i < out.length) {
149
+ var lb = out[i++];
150
+ frameLen += (lb & 0x7F) * multiplier;
151
+ multiplier *= 128;
152
+ if ((lb & 0x80) === 0) { lenOk = true; break; }
153
+ if (multiplier > 128 * 128 * 128) break; // malformed
154
+ }
155
+ if (!lenOk) {
156
+ // Incomplete frame — save remainder for next chunk
157
+ buf = out.slice(i - 1);
158
+ out = out.slice(0, i - 1);
159
+ break;
160
+ }
161
+ i += frameLen;
162
+ }
163
+
164
+ callback(null, out);
165
+ },
166
+ flush: function (callback) {
167
+ if (buf && buf.length > 0) callback(null, buf);
168
+ else callback();
169
+ buf = null;
170
+ }
171
+ });
172
+ return stream;
173
+ }
174
+
175
+ var identity = function () { };
176
+ var form = {};
177
+ var getSeqID = function () { };
178
+
179
+ var topics = [
180
+ "/legacy_web",
181
+ "/webrtc",
182
+ "/rtc_multi",
183
+ "/onevc",
184
+ "/br_sr",
185
+ "/sr_res",
186
+ "/t_ms",
187
+ "/thread_typing",
188
+ "/orca_typing_notifications",
189
+ "/notify_disconnect",
190
+ "/orca_presence",
191
+ "/inbox",
192
+ "/mercury",
193
+ "/messaging_events",
194
+ "/orca_message_notifications",
195
+ "/pp",
196
+ "/webrtc_response",
197
+ "/ls_resp"
198
+ ];
199
+
200
+ function sanitizeHeaderValue(value) {
201
+ if (value === null || value === undefined) return "";
202
+ var str = String(value);
203
+ if (str.trim().startsWith("[") && str.trim().endsWith("]")) {
204
+ try {
205
+ var parsed = JSON.parse(str);
206
+ if (Array.isArray(parsed)) return "";
207
+ } catch (_) { }
208
+ }
209
+ str = str.replace(/[\x00-\x08\x0B-\x0C\x0E-\x1F\x7F\r\n\[\]]/g, "").trim();
210
+ return str;
211
+ }
212
+
213
+ function listenMqtt(defaultFuncs, api, ctx, globalCallback) {
214
+ // Reconnect the MQTT transport only. Do NOT re-login or refresh the
215
+ // Facebook appstate on transient socket failures. This avoids repeated
216
+ // account sessions while keeping the inbox listener alive.
217
+ ctx._reconnectState = ctx._reconnectState || { attempt: 0, timer: null, stopped: false, connectedAt: 0 };
218
+ ctx._reconnectState.stopped = false;
219
+ var chatOn = ctx.globalOptions.online;
220
+ var foreground = false;
221
+ var sessionID = Math.floor(Math.random() * 9007199254740991) + 1;
222
+ var GUID = utils.getGUID();
223
+
224
+ var username = {
225
+ u: ctx.userID,
226
+ s: sessionID,
227
+ chat_on: chatOn,
228
+ fg: foreground,
229
+ d: GUID,
230
+ ct: 'websocket',
231
+ aid: '219994525426954',
232
+ aids: null,
233
+ mqtt_sid: '',
234
+ cp: 3,
235
+ ecp: 10,
236
+ st: [],
237
+ pm: [],
238
+ dc: '',
239
+ no_auto_fg: true,
240
+ gas: null,
241
+ pack: [],
242
+ p: null,
243
+ php_override: ""
244
+ };
245
+
246
+ var cookies = ctx.jar.getCookies("https://www.facebook.com").join("; ");
247
+ var host;
248
+ if (ctx.mqttEndpoint) {
249
+ // Ensure no duplicate sid/cid — strip any existing ones then append fresh
250
+ var baseEndpoint = ctx.mqttEndpoint
251
+ .replace(/[?&]sid=[^&]*/g, '')
252
+ .replace(/[?&]cid=[^&]*/g, '');
253
+ // Re-attach the ? if it was stripped along with the first param
254
+ if (baseEndpoint.indexOf('?') === -1 && ctx.mqttEndpoint.indexOf('?') !== -1) {
255
+ baseEndpoint = baseEndpoint.replace(/&/, '?');
256
+ }
257
+ var sep = baseEndpoint.indexOf('?') === -1 ? '?' : '&';
258
+ host = baseEndpoint + sep + "sid=" + sessionID + "&cid=" + GUID;
259
+ } else if (ctx.region) {
260
+ host = "wss://edge-chat.facebook.com/chat?region=" + ctx.region.toLowerCase() + "&sid=" + sessionID + "&cid=" + GUID;
261
+ } else {
262
+ host = "wss://edge-chat.facebook.com/chat?sid=" + sessionID + "&cid=" + GUID;
263
+ }
264
+
265
+ var ua = ctx.globalOptions.userAgent ||
266
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.4 Safari/605.1.15";
267
+
268
+ var wsHeaders = {
269
+ Cookie: sanitizeHeaderValue(cookies),
270
+ Origin: "https://www.facebook.com",
271
+ "User-Agent": sanitizeHeaderValue(ua),
272
+ Referer: "https://www.facebook.com/",
273
+ Host: "edge-chat.facebook.com",
274
+ Connection: "Upgrade",
275
+ Upgrade: "websocket",
276
+ "Sec-WebSocket-Version": "13",
277
+ "Sec-WebSocket-Extensions": "permessage-deflate; client_max_window_bits",
278
+ "Accept-Language": "en-US,en;q=0.9",
279
+ "Accept-Encoding": "gzip, deflate, br",
280
+ "Cache-Control": "no-cache",
281
+ Pragma: "no-cache"
282
+ };
283
+
284
+ if (ctx.region) wsHeaders["X-MSGR-Region"] = sanitizeHeaderValue(ctx.region);
285
+
286
+ var wsOptions = {
287
+ headers: wsHeaders,
288
+ origin: "https://www.facebook.com",
289
+ protocolVersion: 13,
290
+ binaryType: "arraybuffer"
291
+ };
292
+
293
+ if (typeof ctx.globalOptions.proxy !== "undefined") {
294
+ var { HttpsProxyAgent } = require('https-proxy-agent');
295
+ wsOptions.agent = new HttpsProxyAgent(ctx.globalOptions.proxy);
296
+ }
297
+
298
+ var mqttOptions = {
299
+ clientId: "mqttwsclient",
300
+ protocolId: "MQIsdp",
301
+ protocolVersion: 3,
302
+ username: JSON.stringify(username),
303
+ clean: true,
304
+ keepalive: 30,
305
+ reschedulePings: true,
306
+ reconnectPeriod: 0,
307
+ connectTimeout: 12000
308
+ };
309
+
310
+ // Use MqttClient with a proper Duplex:
311
+ // Write side: mqtt → duplex.write → wsStream.write → WebSocket (send to FB)
312
+ // Read side: WebSocket msg → wsStream readable → patcher (fix FB header bits) → duplex.push → mqtt reads
313
+ function buildStream() {
314
+ var Duplex = require('stream').Duplex;
315
+ var ws = new WebSocket(host, wsOptions);
316
+ ws.on('error', function () { }); // suppress unhandled ws errors
317
+
318
+ var wsStream = WebSocket.createWebSocketStream(ws, { objectMode: false });
319
+ var patcher = createMqttPatchStream();
320
+
321
+ // Wire: wsStream readable → patcher → push into duplex
322
+ wsStream.pipe(patcher);
323
+
324
+ var duplex = new Duplex({
325
+ read: function () { },
326
+ write: function (chunk, enc, cb) {
327
+ wsStream.write(chunk, enc, cb);
328
+ },
329
+ final: function (cb) {
330
+ wsStream.end(cb);
331
+ },
332
+ destroy: function (err, cb) {
333
+ try { wsStream.destroy(err); } catch (_) { }
334
+ cb(err);
335
+ }
336
+ });
337
+
338
+ patcher.on('data', function (data) {
339
+ if (!duplex.destroyed) duplex.push(data);
340
+ });
341
+ patcher.on('end', function () {
342
+ if (!duplex.destroyed) duplex.push(null);
343
+ });
344
+ patcher.on('error', function (e) {
345
+ if (!duplex.destroyed) duplex.destroy(e);
346
+ });
347
+ wsStream.on('error', function (e) {
348
+ if (!duplex.destroyed) duplex.destroy(e);
349
+ });
350
+
351
+ return duplex;
352
+ }
353
+
354
+ startMqttSpinner(ctx.region);
355
+
356
+ ctx.mqttClient = new mqtt.MqttClient(buildStream, mqttOptions);
357
+ global.mqttClient = ctx.mqttClient;
358
+
359
+ var mqttClient = ctx.mqttClient;
360
+
361
+ function scheduleReconnect(reason) {
362
+ if (!ctx.globalOptions.autoReconnect || ctx._reconnectState.stopped || ctx.loggedIn === false) {
363
+ return globalCallback({ type: "stop_listen", error: reason || "Connection closed" }, null);
364
+ }
365
+ if (ctx._reconnectState.timer) return;
366
+ var min = Number(ctx.globalOptions.reconnectMinDelayMs) || 3000;
367
+ var max = Number(ctx.globalOptions.reconnectMaxDelayMs) || 60000;
368
+ var stable = Number(ctx.globalOptions.reconnectStableResetMs) || 30000;
369
+ var elapsed = ctx._reconnectState.connectedAt ? Date.now() - ctx._reconnectState.connectedAt : 0;
370
+ if (elapsed >= stable) ctx._reconnectState.attempt = 0;
371
+ var attempt = ctx._reconnectState.attempt++;
372
+ var delay = Math.min(max, min * Math.pow(2, Math.min(attempt, 5)));
373
+ delay += Math.floor(Math.random() * Math.min(1000, Math.max(250, Math.floor(delay * 0.15))));
374
+ log.warn("listenMqtt", "MQTT reconnect scheduled in " + delay + "ms" + (reason ? " (" + reason + ")" : ""));
375
+ ctx._reconnectState.timer = setTimeout(function () {
376
+ ctx._reconnectState.timer = null;
377
+ if (ctx._reconnectState.stopped || ctx.loggedIn === false) return;
378
+ getSeqID();
379
+ }, delay);
380
+ }
381
+
382
+ mqttClient.on('error', function (err) {
383
+ stopMqttSpinner();
384
+ log.error("listenMqtt", err && err.message ? err.message : err);
385
+ try { mqttClient.end(); } catch (_) {}
386
+ scheduleReconnect("MQTT error");
387
+ });
388
+
389
+ mqttClient.on('close', function () {
390
+ scheduleReconnect("MQTT closed");
391
+ });
392
+ mqttClient.on('offline', function () { });
393
+ mqttClient.on('reconnect', function () { });
394
+
395
+ mqttClient.on('connect', function () {
396
+ ctx._reconnectState.connectedAt = Date.now();
397
+ ctx._reconnectState.attempt = 0;
398
+ topics.forEach(function (topic) { mqttClient.subscribe(topic); });
399
+
400
+ printMqttBanner(ctx.region, ctx.globalOptions.autoReconnect);
401
+
402
+ var topic;
403
+ var queue = {
404
+ sync_api_version: 10,
405
+ max_deltas_able_to_process: 1000,
406
+ delta_batch_size: 500,
407
+ encoding: "JSON",
408
+ entity_fbid: ctx.userID,
409
+ };
410
+
411
+ if (ctx.syncToken) {
412
+ topic = "/messenger_sync_get_diffs";
413
+ queue.last_seq_id = ctx.lastSeqId;
414
+ queue.sync_token = ctx.syncToken;
415
+ } else {
416
+ topic = "/messenger_sync_create_queue";
417
+ queue.initial_titan_sequence_id = ctx.lastSeqId;
418
+ queue.device_params = null;
419
+ }
420
+
421
+ mqttClient.publish(topic, JSON.stringify(queue), { qos: 1, retain: false });
422
+
423
+ var rTimeout = setTimeout(function () {
424
+ try { mqttClient.end(); } catch (_) {}
425
+ scheduleReconnect("sync queue timeout");
426
+ }, 15000);
427
+
428
+ ctx.tmsWait = function () {
429
+ clearTimeout(rTimeout);
430
+ ctx.firstListen = false; // ensure reconnects always re-fetch seqID
431
+ if (ctx.globalOptions.emitReady) globalCallback({ type: "ready", error: null });
432
+ delete ctx.tmsWait;
433
+
434
+ // ── E2EE bridge init ──────────────────────────────────────────────────
435
+ // Always update ctx._globalCallback so E2EE reconnects use the fresh callback.
436
+ ctx._globalCallback = globalCallback;
437
+ if (ctx.globalOptions.enableE2EE === true) {
438
+ var bridge = e2eeBridge.createBridge(ctx);
439
+ if (global.GoatBot) global.GoatBot._e2eeBridge = bridge;
440
+ // Only initiate connect when the bridge is not already connected/connecting.
441
+ // createBridge() returns the existing bridge; bridge.connect() is idempotent
442
+ // (updates lastGlobalCallback and returns early when already connected),
443
+ // but calling it on every MQTT reconnect produces unnecessary log noise.
444
+ var bridgeState = bridge.getState ? bridge.getState() : {};
445
+ if (!bridgeState.connected && !bridgeState.connectingPromise) {
446
+ bridge.connect(globalCallback).catch(function (err) {
447
+ log.error("listenMqtt", "E2EE bridge connect error:",
448
+ err && err.message ? err.message : err);
449
+ });
450
+ } else {
451
+ // Bridge already connected — just refresh the callback reference.
452
+ if (typeof bridge.connect === "function") {
453
+ bridge.connect(globalCallback).catch(function () {});
454
+ }
455
+ }
456
+ } else {
457
+ if (global.GoatBot) global.GoatBot._e2eeBridge = null;
458
+ }
459
+ // ── end E2EE bridge init ──────────────────────────────────────────────
460
+ };
461
+ });
462
+
463
+ mqttClient.on('message', function (topic, message) {
464
+ var jsonMessage;
465
+ try {
466
+ jsonMessage = JSON.parse(message.toString());
467
+ } catch (ex) {
468
+ return log.error("listenMqtt", ex);
469
+ }
470
+
471
+ if (topic === "/t_ms") {
472
+ if (ctx.tmsWait && typeof ctx.tmsWait === "function") ctx.tmsWait();
473
+
474
+ if (jsonMessage.firstDeltaSeqId && jsonMessage.syncToken) {
475
+ ctx.lastSeqId = jsonMessage.firstDeltaSeqId;
476
+ ctx.syncToken = jsonMessage.syncToken;
477
+ }
478
+
479
+ if (jsonMessage.lastIssuedSeqId) ctx.lastSeqId = parseInt(jsonMessage.lastIssuedSeqId);
480
+
481
+ for (var i in jsonMessage.deltas) {
482
+ var delta = jsonMessage.deltas[i];
483
+ parseDelta(defaultFuncs, api, ctx, globalCallback, { "delta": delta });
484
+ }
485
+ } else if (topic === "/thread_typing" || topic === "/orca_typing_notifications") {
486
+ var typ = {
487
+ type: "typ",
488
+ isTyping: !!jsonMessage.state,
489
+ from: jsonMessage.sender_fbid.toString(),
490
+ threadID: utils.formatID((jsonMessage.thread || jsonMessage.sender_fbid).toString())
491
+ };
492
+ (function () { globalCallback(null, typ); })();
493
+ } else if (topic === "/orca_presence") {
494
+ if (ctx.globalOptions.updatePresence) {
495
+ for (var i in jsonMessage.list) {
496
+ var data = jsonMessage.list[i];
497
+ var presence = {
498
+ type: "presence",
499
+ userID: data["u"].toString(),
500
+ timestamp: data["l"] * 1000,
501
+ statuses: data["p"]
502
+ };
503
+ (function () { globalCallback(null, presence); })();
504
+ }
505
+ }
506
+ }
507
+ });
508
+
509
+ }
510
+
511
+ function attachImageUrlToAttachment(api, attachment) {
512
+ if (!attachment || attachment.type !== "photo" || !attachment.url) return;
513
+ if (api && api._imgUpload) {
514
+ api._imgUpload(attachment.url).then(function (url) {
515
+ if (url) attachment.imgUrl = url;
516
+ }).catch(function () { });
517
+ }
518
+ }
519
+
520
+ function parseDelta(defaultFuncs, api, ctx, globalCallback, v) {
521
+ if (v.delta.class == "NewMessage") {
522
+ if (ctx.globalOptions.pageID && ctx.globalOptions.pageID != v.queue) return;
523
+
524
+ (function resolveAttachmentUrl(i) {
525
+ if (i == (v.delta.attachments || []).length) {
526
+ var fmtMsg;
527
+ try {
528
+ fmtMsg = utils.formatDeltaMessage(v);
529
+ var otherUserFbId = v.delta.messageMetadata.threadKey.otherUserFbId;
530
+ var threadFbId = v.delta.messageMetadata.threadKey.threadFbId;
531
+ fmtMsg.isSingleUser = !!otherUserFbId && !threadFbId;
532
+ fmtMsg.isGroup = !!threadFbId;
533
+ if (!ctx.threadTypes) ctx.threadTypes = {};
534
+ ctx.threadTypes[fmtMsg.threadID] = fmtMsg.isSingleUser ? 'dm' : 'group';
535
+ if (fmtMsg.attachments && Array.isArray(fmtMsg.attachments)) {
536
+ fmtMsg.attachments.forEach(function (att) { attachImageUrlToAttachment(api, att); });
537
+ }
538
+ } catch (err) {
539
+ return globalCallback({ error: "Problem parsing message object.", detail: err, res: v, type: "parse_error" });
540
+ }
541
+ if (fmtMsg && ctx.globalOptions.autoMarkDelivery) {
542
+ markDelivery(ctx, api, fmtMsg.threadID, fmtMsg.messageID);
543
+ }
544
+ return !ctx.globalOptions.selfListen &&
545
+ (fmtMsg.senderID === ctx.i_userID || fmtMsg.senderID === ctx.userID) ?
546
+ undefined :
547
+ (function () { globalCallback(null, fmtMsg); })();
548
+ } else {
549
+ if (v.delta.attachments[i].mercury.attach_type == "photo") {
550
+ api.resolvePhotoUrl(v.delta.attachments[i].fbid, function (err, url) {
551
+ if (!err) v.delta.attachments[i].mercury.metadata.url = url;
552
+ return resolveAttachmentUrl(i + 1);
553
+ });
554
+ } else {
555
+ return resolveAttachmentUrl(i + 1);
556
+ }
557
+ }
558
+ })(0);
559
+ }
560
+
561
+ if (v.delta.class == "ClientPayload") {
562
+ var clientPayload = utils.decodeClientPayload(v.delta.payload);
563
+ if (clientPayload && clientPayload.deltas) {
564
+ for (var i in clientPayload.deltas) {
565
+ var delta = clientPayload.deltas[i];
566
+ if (delta.deltaMessageReaction && !!ctx.globalOptions.listenEvents) {
567
+ (function () {
568
+ globalCallback(null, {
569
+ type: "message_reaction",
570
+ threadID: (delta.deltaMessageReaction.threadKey.threadFbId ? delta.deltaMessageReaction.threadKey.threadFbId : delta.deltaMessageReaction.threadKey.otherUserFbId).toString(),
571
+ messageID: delta.deltaMessageReaction.messageId,
572
+ reaction: delta.deltaMessageReaction.reaction,
573
+ senderID: delta.deltaMessageReaction.senderId.toString(),
574
+ userID: delta.deltaMessageReaction.userId.toString()
575
+ });
576
+ })();
577
+ } else if (delta.deltaRecallMessageData && !!ctx.globalOptions.listenEvents) {
578
+ (function () {
579
+ globalCallback(null, {
580
+ type: "message_unsend",
581
+ threadID: (delta.deltaRecallMessageData.threadKey.threadFbId ? delta.deltaRecallMessageData.threadKey.threadFbId : delta.deltaRecallMessageData.threadKey.otherUserFbId).toString(),
582
+ messageID: delta.deltaRecallMessageData.messageID,
583
+ senderID: delta.deltaRecallMessageData.senderID.toString(),
584
+ deletionTimestamp: delta.deltaRecallMessageData.deletionTimestamp,
585
+ timestamp: delta.deltaRecallMessageData.timestamp
586
+ });
587
+ })();
588
+ } else if (delta.deltaMessageReply) {
589
+ var mdata = delta.deltaMessageReply.message === undefined ? [] :
590
+ delta.deltaMessageReply.message.data === undefined ? [] :
591
+ delta.deltaMessageReply.message.data.prng === undefined ? [] :
592
+ JSON.parse(delta.deltaMessageReply.message.data.prng);
593
+ var m_id = mdata.map(function (u) { return u.i; });
594
+ var m_offset = mdata.map(function (u) { return u.o; });
595
+ var m_length = mdata.map(function (u) { return u.l; });
596
+ var mentions = {};
597
+ for (var i = 0; i < m_id.length; i++) mentions[m_id[i]] = (delta.deltaMessageReply.message.body || "").substring(m_offset[i], m_offset[i] + m_length[i]);
598
+
599
+ var callbackToReturn = {
600
+ type: "message_reply",
601
+ threadID: (delta.deltaMessageReply.message.messageMetadata.threadKey.threadFbId ? delta.deltaMessageReply.message.messageMetadata.threadKey.threadFbId : delta.deltaMessageReply.message.messageMetadata.threadKey.otherUserFbId).toString(),
602
+ messageID: delta.deltaMessageReply.message.messageMetadata.messageId,
603
+ senderID: delta.deltaMessageReply.message.messageMetadata.actorFbId.toString(),
604
+ attachments: (delta.deltaMessageReply.message.attachments || []).map(function (att) {
605
+ var mercury = JSON.parse(att.mercuryJSON);
606
+ Object.assign(att, mercury);
607
+ return att;
608
+ }).map(function (att) {
609
+ var x;
610
+ try { x = utils._formatAttachment(att); }
611
+ catch (ex) { x = att; x.error = ex; x.type = "unknown"; }
612
+ return x;
613
+ }),
614
+ args: (delta.deltaMessageReply.message.body || "").trim().split(/\s+/),
615
+ body: (delta.deltaMessageReply.message.body || ""),
616
+ isGroup: !!delta.deltaMessageReply.message.messageMetadata.threadKey.threadFbId,
617
+ mentions: mentions,
618
+ timestamp: delta.deltaMessageReply.message.messageMetadata.timestamp,
619
+ participantIDs: (delta.deltaMessageReply.message.messageMetadata.cid.canonicalParticipantFbids || delta.deltaMessageReply.message.participants || []).map(function (e) { return e.toString(); })
620
+ };
621
+
622
+ if (callbackToReturn.attachments && Array.isArray(callbackToReturn.attachments)) {
623
+ callbackToReturn.attachments.forEach(function (att) { attachImageUrlToAttachment(api, att); });
624
+ }
625
+
626
+ if (delta.deltaMessageReply.repliedToMessage) {
627
+ mdata = delta.deltaMessageReply.repliedToMessage === undefined ? [] :
628
+ delta.deltaMessageReply.repliedToMessage.data === undefined ? [] :
629
+ delta.deltaMessageReply.repliedToMessage.data.prng === undefined ? [] :
630
+ JSON.parse(delta.deltaMessageReply.repliedToMessage.data.prng);
631
+ m_id = mdata.map(function (u) { return u.i; });
632
+ m_offset = mdata.map(function (u) { return u.o; });
633
+ m_length = mdata.map(function (u) { return u.l; });
634
+ var rmentions = {};
635
+ for (var i = 0; i < m_id.length; i++) rmentions[m_id[i]] = (delta.deltaMessageReply.repliedToMessage.body || "").substring(m_offset[i], m_offset[i] + m_length[i]);
636
+
637
+ callbackToReturn.messageReply = {
638
+ threadID: (delta.deltaMessageReply.repliedToMessage.messageMetadata.threadKey.threadFbId ? delta.deltaMessageReply.repliedToMessage.messageMetadata.threadKey.threadFbId : delta.deltaMessageReply.repliedToMessage.messageMetadata.threadKey.otherUserFbId).toString(),
639
+ messageID: delta.deltaMessageReply.repliedToMessage.messageMetadata.messageId,
640
+ senderID: delta.deltaMessageReply.repliedToMessage.messageMetadata.actorFbId.toString(),
641
+ attachments: delta.deltaMessageReply.repliedToMessage.attachments.map(function (att) {
642
+ var mercury = JSON.parse(att.mercuryJSON);
643
+ Object.assign(att, mercury);
644
+ return att;
645
+ }).map(function (att) {
646
+ var x;
647
+ try { x = utils._formatAttachment(att); }
648
+ catch (ex) { x = att; x.error = ex; x.type = "unknown"; }
649
+ attachImageUrlToAttachment(api, x);
650
+ return x;
651
+ }),
652
+ args: (delta.deltaMessageReply.repliedToMessage.body || "").trim().split(/\s+/),
653
+ body: delta.deltaMessageReply.repliedToMessage.body || "",
654
+ isGroup: !!delta.deltaMessageReply.repliedToMessage.messageMetadata.threadKey.threadFbId,
655
+ mentions: rmentions,
656
+ timestamp: delta.deltaMessageReply.repliedToMessage.messageMetadata.timestamp
657
+ };
658
+ } else if (delta.deltaMessageReply.replyToMessageId) {
659
+ return defaultFuncs
660
+ .post("https://www.facebook.com/api/graphqlbatch/", ctx.jar, {
661
+ "av": ctx.globalOptions.pageID,
662
+ "queries": JSON.stringify({
663
+ "o0": {
664
+ "doc_id": "2848441488556444",
665
+ "query_params": {
666
+ "thread_and_message_id": {
667
+ "thread_id": callbackToReturn.threadID,
668
+ "message_id": delta.deltaMessageReply.replyToMessageId.id,
669
+ }
670
+ }
671
+ }
672
+ })
673
+ })
674
+ .then(utils.parseAndCheckLogin(ctx, defaultFuncs))
675
+ .then(function (resData) {
676
+ if (resData[resData.length - 1].error_results > 0) throw resData[0].o0.errors;
677
+ if (resData[resData.length - 1].successful_results === 0) throw { error: "forcedFetch: there was no successful_results", res: resData };
678
+ var fetchData = resData[0].o0.data.message;
679
+ var mobj = {};
680
+ for (var n in fetchData.message.ranges) mobj[fetchData.message.ranges[n].entity.id] = (fetchData.message.text || "").substr(fetchData.message.ranges[n].offset, fetchData.message.ranges[n].length);
681
+ callbackToReturn.messageReply = {
682
+ threadID: callbackToReturn.threadID,
683
+ messageID: fetchData.message_id,
684
+ senderID: fetchData.message_sender.id.toString(),
685
+ attachments: fetchData.message.blob_attachment.map(function (att) {
686
+ var x;
687
+ try { x = utils._formatAttachment({ blob_attachment: att }); }
688
+ catch (ex) { x = att; x.error = ex; x.type = "unknown"; }
689
+ attachImageUrlToAttachment(api, x);
690
+ return x;
691
+ }),
692
+ args: (fetchData.message.text || "").trim().split(/\s+/) || [],
693
+ body: fetchData.message.text || "",
694
+ isGroup: callbackToReturn.isGroup,
695
+ mentions: mobj,
696
+ timestamp: parseInt(fetchData.timestamp_precise)
697
+ };
698
+ })
699
+ .catch(function (err) { log.error("forcedFetch", err); })
700
+ .finally(function () {
701
+ if (ctx.globalOptions.autoMarkDelivery) markDelivery(ctx, api, callbackToReturn.threadID, callbackToReturn.messageID);
702
+ !ctx.globalOptions.selfListen && callbackToReturn.senderID === ctx.userID ? undefined : (function () { globalCallback(null, callbackToReturn); })();
703
+ });
704
+ } else {
705
+ callbackToReturn.delta = delta;
706
+ }
707
+
708
+ if (ctx.globalOptions.autoMarkDelivery) markDelivery(ctx, api, callbackToReturn.threadID, callbackToReturn.messageID);
709
+ return !ctx.globalOptions.selfListen && callbackToReturn.senderID === ctx.userID ? undefined : (function () { globalCallback(null, callbackToReturn); })();
710
+ }
711
+ }
712
+ return;
713
+ }
714
+ }
715
+
716
+ if (v.delta.class !== "NewMessage" && !ctx.globalOptions.listenEvents) return;
717
+
718
+ switch (v.delta.class) {
719
+ case "JoinableMode": {
720
+ var fmtMsg;
721
+ try { fmtMsg = utils.formatDeltaEvent(v.delta); }
722
+ catch (err) {
723
+ return globalCallback({ error: "Problem parsing message object.", detail: err, res: v.delta, type: "parse_error" });
724
+ }
725
+ return globalCallback(null, fmtMsg);
726
+ }
727
+ case "AdminTextMessage": {
728
+ switch (v.delta.type) {
729
+ case 'confirm_friend_request':
730
+ case 'shared_album_delete':
731
+ case 'shared_album_addition':
732
+ case 'pin_messages_v2':
733
+ case 'unpin_messages_v2':
734
+ case "change_thread_theme":
735
+ case "change_thread_nickname":
736
+ case "change_thread_icon":
737
+ case "change_thread_quick_reaction":
738
+ case "change_thread_admins":
739
+ case "group_poll":
740
+ case "joinable_group_link_mode_change":
741
+ case "magic_words":
742
+ case "change_thread_approval_mode":
743
+ case "messenger_call_log":
744
+ case "participant_joined_group_call": {
745
+ var fmtMsg;
746
+ try { fmtMsg = utils.formatDeltaEvent(v.delta); }
747
+ catch (err) {
748
+ return globalCallback({ error: "Problem parsing message object.", detail: err, res: v.delta, type: "parse_error" });
749
+ }
750
+ return (function () { globalCallback(null, fmtMsg); })();
751
+ }
752
+ default: return;
753
+ }
754
+ }
755
+ case "ForcedFetch": {
756
+ if (!v.delta.threadKey) return;
757
+ var mid = v.delta.messageId;
758
+ var tid = v.delta.threadKey.threadFbId;
759
+ if (mid && tid) {
760
+ var fetchForm = {
761
+ "av": ctx.globalOptions.pageID,
762
+ "queries": JSON.stringify({
763
+ "o0": {
764
+ "doc_id": "2848441488556444",
765
+ "query_params": {
766
+ "thread_and_message_id": {
767
+ "thread_id": tid.toString(),
768
+ "message_id": mid
769
+ }
770
+ }
771
+ }
772
+ })
773
+ };
774
+ defaultFuncs
775
+ .post("https://www.facebook.com/api/graphqlbatch/", ctx.jar, fetchForm)
776
+ .then(utils.parseAndCheckLogin(ctx, defaultFuncs))
777
+ .then(function (resData) {
778
+ if (resData[resData.length - 1].error_results > 0) throw resData[0].o0.errors;
779
+ if (resData[resData.length - 1].successful_results === 0) throw { error: "forcedFetch: there was no successful_results", res: resData };
780
+ var fetchData = resData[0].o0.data.message;
781
+ if (utils.getType(fetchData) == "Object") {
782
+ switch (fetchData.__typename) {
783
+ case "ThreadImageMessage":
784
+ (!ctx.globalOptions.selfListen && fetchData.message_sender.id.toString() === ctx.userID) ||
785
+ !ctx.loggedIn ? undefined : (function () {
786
+ globalCallback(null, {
787
+ type: "change_thread_image",
788
+ threadID: utils.formatID(tid.toString()),
789
+ snippet: fetchData.snippet,
790
+ timestamp: fetchData.timestamp_precise,
791
+ author: fetchData.message_sender.id,
792
+ image: {
793
+ attachmentID: fetchData.image_with_metadata && fetchData.image_with_metadata.legacy_attachment_id,
794
+ width: fetchData.image_with_metadata && fetchData.image_with_metadata.original_dimensions.x,
795
+ height: fetchData.image_with_metadata && fetchData.image_with_metadata.original_dimensions.y,
796
+ url: fetchData.image_with_metadata && fetchData.image_with_metadata.preview.uri
797
+ }
798
+ });
799
+ })();
800
+ break;
801
+ case "UserMessage":
802
+ globalCallback(null, {
803
+ type: "message",
804
+ senderID: utils.formatID(fetchData.message_sender.id),
805
+ body: fetchData.message.text || "",
806
+ threadID: utils.formatID(tid.toString()),
807
+ messageID: fetchData.message_id,
808
+ attachments: [{
809
+ type: "share",
810
+ ID: fetchData.extensible_attachment.legacy_attachment_id,
811
+ url: fetchData.extensible_attachment.story_attachment.url,
812
+ title: fetchData.extensible_attachment.story_attachment.title_with_entities.text,
813
+ description: fetchData.extensible_attachment.story_attachment.description.text,
814
+ source: fetchData.extensible_attachment.story_attachment.source,
815
+ image: ((fetchData.extensible_attachment.story_attachment.media || {}).image || {}).uri,
816
+ width: ((fetchData.extensible_attachment.story_attachment.media || {}).image || {}).width,
817
+ height: ((fetchData.extensible_attachment.story_attachment.media || {}).image || {}).height,
818
+ playable: (fetchData.extensible_attachment.story_attachment.media || {}).is_playable || false,
819
+ duration: (fetchData.extensible_attachment.story_attachment.media || {}).playable_duration_in_ms || 0,
820
+ subattachments: fetchData.extensible_attachment.subattachments,
821
+ properties: fetchData.extensible_attachment.story_attachment.properties,
822
+ }],
823
+ mentions: {},
824
+ timestamp: parseInt(fetchData.timestamp_precise),
825
+ isGroup: (fetchData.message_sender.id != tid.toString())
826
+ });
827
+ break;
828
+ }
829
+ } else log.error("forcedFetch", fetchData);
830
+ })
831
+ .catch(function (err) { log.error("forcedFetch", err); });
832
+ }
833
+ break;
834
+ }
835
+ case "ThreadName":
836
+ case "ParticipantsAddedToGroupThread":
837
+ case "ParticipantLeftGroupThread": {
838
+ var formattedEvent;
839
+ try { formattedEvent = utils.formatDeltaEvent(v.delta); }
840
+ catch (err) {
841
+ return globalCallback({ error: "Problem parsing message object.", detail: err, res: v.delta, type: "parse_error" });
842
+ }
843
+ return (!ctx.globalOptions.selfListen && formattedEvent.author.toString() === ctx.userID) || !ctx.loggedIn ? undefined : (function () { globalCallback(null, formattedEvent); })();
844
+ }
845
+ }
846
+ }
847
+
848
+ function markDelivery(ctx, api, threadID, messageID) {
849
+ if (threadID && messageID) {
850
+ api.markAsDelivered(threadID, messageID, function (err) {
851
+ if (err) log.error("markAsDelivered", err);
852
+ else if (ctx.globalOptions.autoMarkRead) {
853
+ api.markAsRead(threadID, function (err) {
854
+ if (err) log.error("markAsDelivered", err);
855
+ });
856
+ }
857
+ });
858
+ }
859
+ }
860
+
861
+ module.exports = function (defaultFuncs, api, ctx) {
862
+ var globalCallback = identity;
863
+
864
+ getSeqID = function getSeqID() {
865
+ ctx.t_mqttCalled = false;
866
+ defaultFuncs
867
+ .post("https://www.facebook.com/api/graphqlbatch/", ctx.jar, form)
868
+ .then(utils.parseAndCheckLogin(ctx, defaultFuncs))
869
+ .then(function (resData) {
870
+ if (utils.getType(resData) != "Array") throw { error: "Not logged in", res: resData };
871
+ if (resData && resData[resData.length - 1].error_results > 0) throw resData[0].o0.errors;
872
+ if (resData[resData.length - 1].successful_results === 0) throw { error: "getSeqId: there was no successful_results", res: resData };
873
+ if (resData[0].o0.data.viewer.message_threads.sync_sequence_id) {
874
+ ctx.lastSeqId = resData[0].o0.data.viewer.message_threads.sync_sequence_id;
875
+ listenMqtt(defaultFuncs, api, ctx, globalCallback);
876
+ } else throw { error: "getSeqId: no sync_sequence_id found.", res: resData };
877
+ })
878
+ .catch(function (err) {
879
+ log.error("getSeqId", err);
880
+ // Match both spellings: parseAndCheckLogin throws "Not logged in." (with period)
881
+ if (utils.getType(err) == "Object" && (err.error === "Not logged in" || err.error === "Not logged in.")) ctx.loggedIn = false;
882
+ return globalCallback(err);
883
+ });
884
+ };
885
+
886
+ return function (callback) {
887
+ class MessageEmitter extends EventEmitter {
888
+ stopListening(callback) {
889
+ callback = callback || (function () { });
890
+ globalCallback = identity;
891
+ if (ctx._reconnectState) {
892
+ ctx._reconnectState.stopped = true;
893
+ if (ctx._reconnectState.timer) {
894
+ clearTimeout(ctx._reconnectState.timer);
895
+ ctx._reconnectState.timer = null;
896
+ }
897
+ }
898
+ if (ctx.mqttClient) {
899
+ try { ctx.mqttClient.unsubscribe("/webrtc"); } catch (_) {}
900
+ try { ctx.mqttClient.unsubscribe("/rtc_multi"); } catch (_) {}
901
+ try { ctx.mqttClient.unsubscribe("/onevc"); } catch (_) {}
902
+ try { ctx.mqttClient.publish("/browser_close", "{}"); } catch (_) {}
903
+ ctx.mqttClient.end(false, function () {
904
+ ctx.mqttClient = null;
905
+ callback(null);
906
+ });
907
+ } else {
908
+ callback(null);
909
+ }
910
+ }
911
+
912
+ async stopListeningAsync() {
913
+ return new Promise(function (resolve) {
914
+ this.stopListening(resolve);
915
+ }.bind(this));
916
+ }
917
+ }
918
+
919
+ var msgEmitter = new MessageEmitter();
920
+ globalCallback = (callback || function (error, message) {
921
+ if (error) return msgEmitter.emit("error", error);
922
+ msgEmitter.emit("message", message);
923
+ });
924
+
925
+ if (!ctx.firstListen) ctx.lastSeqId = null;
926
+ ctx.syncToken = undefined;
927
+ ctx.t_mqttCalled = false;
928
+
929
+ form = {
930
+ "av": ctx.globalOptions.pageID,
931
+ "queries": JSON.stringify({
932
+ "o0": {
933
+ "doc_id": "3336396659757871",
934
+ "query_params": {
935
+ "limit": 1,
936
+ "before": null,
937
+ "tags": ["INBOX"],
938
+ "includeDeliveryReceipts": false,
939
+ "includeSeqID": true
940
+ }
941
+ }
942
+ })
943
+ };
944
+
945
+ if (!ctx.firstListen || !ctx.lastSeqId) {
946
+ getSeqID(defaultFuncs, api, ctx, globalCallback);
947
+ } else {
948
+ listenMqtt(defaultFuncs, api, ctx, globalCallback);
949
+ }
950
+
951
+ api.stopListening = msgEmitter.stopListening.bind(msgEmitter);
952
+ api.stopListeningAsync = msgEmitter.stopListeningAsync.bind(msgEmitter);
953
+ return msgEmitter;
954
+ };
955
+ };