nexus-fca 3.1.5 → 3.1.6
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 +1 -1
- package/src/listenMqtt.js +161 -10
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nexus-fca",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.6",
|
|
4
4
|
"description": "Nexus-FCA 3.1 – THE BEST, SAFEST, MOST STABLE Facebook Messenger API! Email/password + appState login, proxy support (HTTP/HTTPS/SOCKS5), random user agent, proactive cookie refresh, MQTT stability, session protection, and TypeScript support.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"repository": {
|
package/src/listenMqtt.js
CHANGED
|
@@ -136,6 +136,126 @@ function resetBackoff(state){
|
|
|
136
136
|
state.current = 0;
|
|
137
137
|
state.lastResetTs = Date.now();
|
|
138
138
|
}
|
|
139
|
+
function getForegroundState(ctx){
|
|
140
|
+
if (ctx && ctx.globalOptions) {
|
|
141
|
+
if (ctx.globalOptions.foreground === false) return false;
|
|
142
|
+
if (ctx.globalOptions.online === false) return false;
|
|
143
|
+
}
|
|
144
|
+
return true;
|
|
145
|
+
}
|
|
146
|
+
function startForegroundRefresh(ctx){
|
|
147
|
+
if (ctx._foregroundRefreshInterval) {
|
|
148
|
+
clearInterval(ctx._foregroundRefreshInterval);
|
|
149
|
+
ctx._foregroundRefreshInterval = null;
|
|
150
|
+
}
|
|
151
|
+
const options = ctx.globalOptions || {};
|
|
152
|
+
if (options.foregroundRefreshEnabled === false) return;
|
|
153
|
+
const minutes = Number.isFinite(options.foregroundRefreshMinutes)
|
|
154
|
+
? Math.max(1, options.foregroundRefreshMinutes)
|
|
155
|
+
: 15;
|
|
156
|
+
ctx._foregroundRefreshInterval = setInterval(() => {
|
|
157
|
+
if (!ctx.mqttClient || !ctx.mqttClient.connected) return;
|
|
158
|
+
try {
|
|
159
|
+
const foreground = getForegroundState(ctx);
|
|
160
|
+
ctx.mqttClient.publish("/foreground_state", JSON.stringify({ foreground }), { qos: 0, retain: false });
|
|
161
|
+
ctx.mqttClient.publish(
|
|
162
|
+
"/set_client_settings",
|
|
163
|
+
JSON.stringify({ make_user_available_when_in_foreground: true }),
|
|
164
|
+
{ qos: 0, retain: false }
|
|
165
|
+
);
|
|
166
|
+
} catch (err) {
|
|
167
|
+
if (typeof log.verbose === 'function') {
|
|
168
|
+
log.verbose("listenMqtt", `Foreground refresh publish failed: ${err.message || err}`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}, minutes * 60 * 1000);
|
|
172
|
+
}
|
|
173
|
+
function stopForegroundRefresh(ctx){
|
|
174
|
+
if (ctx && ctx._foregroundRefreshInterval) {
|
|
175
|
+
clearInterval(ctx._foregroundRefreshInterval);
|
|
176
|
+
ctx._foregroundRefreshInterval = null;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
function getStormGuard(ctx){
|
|
180
|
+
if(!ctx._mqttStorm){
|
|
181
|
+
ctx._mqttStorm = {
|
|
182
|
+
events: [],
|
|
183
|
+
windowMs: 30 * 60 * 1000,
|
|
184
|
+
threshold: 12,
|
|
185
|
+
quietMs: 90 * 1000,
|
|
186
|
+
extraDelayMs: 120 * 1000,
|
|
187
|
+
quietUntil: 0,
|
|
188
|
+
active: false,
|
|
189
|
+
lastLogTs: 0
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
return ctx._mqttStorm;
|
|
193
|
+
}
|
|
194
|
+
function noteStormEvent(ctx){
|
|
195
|
+
const guard = getStormGuard(ctx);
|
|
196
|
+
const now = Date.now();
|
|
197
|
+
guard.events.push(now);
|
|
198
|
+
guard.events = guard.events.filter(ts => now - ts <= guard.windowMs);
|
|
199
|
+
if (guard.events.length >= guard.threshold) {
|
|
200
|
+
guard.active = true;
|
|
201
|
+
guard.quietUntil = now + guard.quietMs;
|
|
202
|
+
} else if (guard.active && now > guard.quietUntil) {
|
|
203
|
+
guard.active = false;
|
|
204
|
+
}
|
|
205
|
+
return guard;
|
|
206
|
+
}
|
|
207
|
+
function resetStormGuard(ctx){
|
|
208
|
+
if(ctx._mqttStorm){
|
|
209
|
+
ctx._mqttStorm.events = [];
|
|
210
|
+
ctx._mqttStorm.active = false;
|
|
211
|
+
ctx._mqttStorm.quietUntil = 0;
|
|
212
|
+
ctx._mqttStorm.lastLogTs = 0;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
function shouldLogStorm(guard){
|
|
216
|
+
if(!guard || !guard.active) return true;
|
|
217
|
+
const now = Date.now();
|
|
218
|
+
if(!guard.lastLogTs || now - guard.lastLogTs > guard.quietMs){
|
|
219
|
+
guard.lastLogTs = now;
|
|
220
|
+
return true;
|
|
221
|
+
}
|
|
222
|
+
return false;
|
|
223
|
+
}
|
|
224
|
+
async function runStormRecovery(ctx, api, defaultFuncs){
|
|
225
|
+
if(ctx._stormRecoveryRunning) return;
|
|
226
|
+
ctx._stormRecoveryRunning = true;
|
|
227
|
+
try {
|
|
228
|
+
log.warn('listenMqtt', 'Storm recovery triggered: validating session & refreshing tokens.');
|
|
229
|
+
try {
|
|
230
|
+
await utils.validateSession(ctx, defaultFuncs, { retries: 1, delayMs: 1000 });
|
|
231
|
+
} catch (err) {
|
|
232
|
+
log.warn('listenMqtt', `Storm validateSession failed: ${err.message || err}`);
|
|
233
|
+
}
|
|
234
|
+
if (api && typeof api.refreshFb_dtsg === 'function') {
|
|
235
|
+
try {
|
|
236
|
+
await api.refreshFb_dtsg();
|
|
237
|
+
} catch (err) {
|
|
238
|
+
log.warn('listenMqtt', `Storm fb_dtsg refresh failed: ${err.message || err}`);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
if (ctx.globalSafety && typeof ctx.globalSafety.refreshSafeSession === 'function') {
|
|
242
|
+
try {
|
|
243
|
+
await ctx.globalSafety.refreshSafeSession();
|
|
244
|
+
} catch (err) {
|
|
245
|
+
log.warn('listenMqtt', `Storm safe session refresh failed: ${err.message || err}`);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
} finally {
|
|
249
|
+
ctx._stormRecoveryRunning = false;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
function scheduleStormRecovery(ctx, api, defaultFuncs, guard){
|
|
253
|
+
if(!guard || !guard.active) return;
|
|
254
|
+
const now = Date.now();
|
|
255
|
+
if(ctx._nextStormRecovery && now < ctx._nextStormRecovery) return;
|
|
256
|
+
ctx._nextStormRecovery = now + guard.quietMs;
|
|
257
|
+
runStormRecovery(ctx, api, defaultFuncs);
|
|
258
|
+
}
|
|
139
259
|
// Build lazy preflight gating
|
|
140
260
|
function shouldRunPreflight(ctx){
|
|
141
261
|
if(ctx.globalOptions.disablePreflight) return false;
|
|
@@ -287,8 +407,8 @@ function listenMqtt(defaultFuncs, api, ctx, globalCallback) {
|
|
|
287
407
|
}
|
|
288
408
|
})();
|
|
289
409
|
}
|
|
290
|
-
const chatOn = ctx.globalOptions.online;
|
|
291
|
-
const foreground =
|
|
410
|
+
const chatOn = (ctx.globalOptions && ctx.globalOptions.online === false) ? false : true;
|
|
411
|
+
const foreground = getForegroundState(ctx);
|
|
292
412
|
const sessionID = Math.floor(Math.random() * Number.MAX_SAFE_INTEGER) + 1;
|
|
293
413
|
const GUID = utils.getGUID();
|
|
294
414
|
const username = {
|
|
@@ -418,17 +538,27 @@ function listenMqtt(defaultFuncs, api, ctx, globalCallback) {
|
|
|
418
538
|
mqttClient.on('close', function () {
|
|
419
539
|
ctx.health.onDisconnect();
|
|
420
540
|
if (ctx.health && typeof ctx.health.incFailure === 'function') { ctx.health.incFailure(); }
|
|
541
|
+
stopForegroundRefresh(ctx);
|
|
421
542
|
|
|
422
543
|
const duration = Date.now() - attemptStartTs;
|
|
423
544
|
const seconds = Math.floor(duration / 1000);
|
|
424
545
|
|
|
546
|
+
const shortWindowMs = 5 * 60 * 1000;
|
|
547
|
+
const guard = duration < shortWindowMs ? noteStormEvent(ctx) : getStormGuard(ctx);
|
|
548
|
+
const allowLog = shouldLogStorm(guard);
|
|
549
|
+
const stormSuffix = guard && guard.active ? ` [storm:${guard.events.length}/${Math.round(guard.windowMs/60000)}m]` : '';
|
|
550
|
+
if (guard && guard.active) {
|
|
551
|
+
scheduleStormRecovery(ctx, api, defaultFuncs, guard);
|
|
552
|
+
}
|
|
425
553
|
// Treat long-lived connections as normal lifecycle, keep logs calm
|
|
426
554
|
if (duration >= 30 * 60 * 1000) { // >= 30 minutes
|
|
427
|
-
log.info('listenMqtt', `MQTT connection closed after ${seconds}s (normal lifecycle). Reconnecting
|
|
555
|
+
if (allowLog) log.info('listenMqtt', `MQTT connection closed after ${seconds}s (normal lifecycle). Reconnecting...${stormSuffix}`);
|
|
428
556
|
} else if (duration >= 5 * 60 * 1000) { // 5-30 minutes
|
|
429
|
-
log.info('listenMqtt', `MQTT connection closed after ${seconds}s (remote close). Reconnecting with backoff
|
|
557
|
+
if (allowLog) log.info('listenMqtt', `MQTT connection closed after ${seconds}s (remote close). Reconnecting with backoff...${stormSuffix}`);
|
|
558
|
+
} else if (duration >= 60 * 1000) {
|
|
559
|
+
if (allowLog) log.info('listenMqtt', `MQTT connection closed after ${seconds}s (short remote close). Reconnecting with backoff...${stormSuffix}`);
|
|
430
560
|
} else {
|
|
431
|
-
log.warn('listenMqtt', `MQTT bridge socket closed quickly after ${duration}ms (attempt=${ctx._mqttDiag.attempts})
|
|
561
|
+
if (allowLog) log.warn('listenMqtt', `MQTT bridge socket closed quickly after ${duration}ms (attempt=${ctx._mqttDiag.attempts}).${stormSuffix}`);
|
|
432
562
|
}
|
|
433
563
|
|
|
434
564
|
if (!ctx.loggedIn) return; // avoid loops if logged out
|
|
@@ -439,6 +569,7 @@ function listenMqtt(defaultFuncs, api, ctx, globalCallback) {
|
|
|
439
569
|
if (duration >= resetThreshold) {
|
|
440
570
|
log.info('listenMqtt', `Resetting MQTT backoff after ${seconds}s healthy session.`);
|
|
441
571
|
resetBackoff(backoffState);
|
|
572
|
+
resetStormGuard(ctx);
|
|
442
573
|
reconnectReason = 'close-long';
|
|
443
574
|
}
|
|
444
575
|
scheduleAdaptiveReconnect(defaultFuncs, api, ctx, globalCallback, reconnectReason);
|
|
@@ -448,16 +579,26 @@ function listenMqtt(defaultFuncs, api, ctx, globalCallback) {
|
|
|
448
579
|
mqttClient.on('disconnect', function(){
|
|
449
580
|
ctx.health.onDisconnect();
|
|
450
581
|
if (ctx.health && typeof ctx.health.incFailure === 'function') { ctx.health.incFailure(); }
|
|
582
|
+
stopForegroundRefresh(ctx);
|
|
451
583
|
|
|
452
584
|
const duration = Date.now() - attemptStartTs;
|
|
453
585
|
const seconds = Math.floor(duration / 1000);
|
|
454
586
|
|
|
587
|
+
const shortWindowMs = 5 * 60 * 1000;
|
|
588
|
+
const guard = duration < shortWindowMs ? noteStormEvent(ctx) : getStormGuard(ctx);
|
|
589
|
+
const allowLog = shouldLogStorm(guard);
|
|
590
|
+
const stormSuffix = guard && guard.active ? ` [storm:${guard.events.length}/${Math.round(guard.windowMs/60000)}m]` : '';
|
|
591
|
+
if (guard && guard.active) {
|
|
592
|
+
scheduleStormRecovery(ctx, api, defaultFuncs, guard);
|
|
593
|
+
}
|
|
455
594
|
if (duration >= 30 * 60 * 1000) {
|
|
456
|
-
log.info('listenMqtt', `MQTT disconnected after ${seconds}s (normal lifecycle). Reconnecting
|
|
595
|
+
if (allowLog) log.info('listenMqtt', `MQTT disconnected after ${seconds}s (normal lifecycle). Reconnecting...${stormSuffix}`);
|
|
457
596
|
} else if (duration >= 5 * 60 * 1000) {
|
|
458
|
-
log.info('listenMqtt', `MQTT disconnected after ${seconds}s (remote close). Reconnecting with backoff
|
|
597
|
+
if (allowLog) log.info('listenMqtt', `MQTT disconnected after ${seconds}s (remote close). Reconnecting with backoff...${stormSuffix}`);
|
|
598
|
+
} else if (duration >= 60 * 1000) {
|
|
599
|
+
if (allowLog) log.info('listenMqtt', `MQTT disconnected after ${seconds}s (short remote close). Reconnecting with backoff...${stormSuffix}`);
|
|
459
600
|
} else {
|
|
460
|
-
log.warn('listenMqtt', `MQTT bridge disconnect event after ${duration}ms (attempt=${ctx._mqttDiag.attempts})
|
|
601
|
+
if (allowLog) log.warn('listenMqtt', `MQTT bridge disconnect event after ${duration}ms (attempt=${ctx._mqttDiag.attempts}).${stormSuffix}`);
|
|
461
602
|
}
|
|
462
603
|
|
|
463
604
|
if (!ctx.loggedIn) return;
|
|
@@ -468,6 +609,7 @@ function listenMqtt(defaultFuncs, api, ctx, globalCallback) {
|
|
|
468
609
|
if (duration >= resetThreshold) {
|
|
469
610
|
log.info('listenMqtt', `Resetting MQTT backoff after ${seconds}s healthy session.`);
|
|
470
611
|
resetBackoff(backoffState);
|
|
612
|
+
resetStormGuard(ctx);
|
|
471
613
|
reconnectReason = 'disconnect-long';
|
|
472
614
|
}
|
|
473
615
|
scheduleAdaptiveReconnect(defaultFuncs, api, ctx, globalCallback, reconnectReason);
|
|
@@ -476,6 +618,7 @@ function listenMqtt(defaultFuncs, api, ctx, globalCallback) {
|
|
|
476
618
|
|
|
477
619
|
mqttClient.on("connect", function () {
|
|
478
620
|
resetBackoff(backoff);
|
|
621
|
+
resetStormGuard(ctx);
|
|
479
622
|
backoff.consecutiveFails = 0; // Reset consecutive failures on successful connect
|
|
480
623
|
// Reset or wrap MQTT attempt counter so long-lived sessions don't look scary
|
|
481
624
|
ctx._mqttDiag = ctx._mqttDiag || {};
|
|
@@ -549,6 +692,7 @@ function listenMqtt(defaultFuncs, api, ctx, globalCallback) {
|
|
|
549
692
|
JSON.stringify({ make_user_available_when_in_foreground: true }),
|
|
550
693
|
{ qos: 1 }
|
|
551
694
|
);
|
|
695
|
+
startForegroundRefresh(ctx);
|
|
552
696
|
// Replace fixed rTimeout reconnect with health-driven logic
|
|
553
697
|
const rTimeout = setTimeout(function () {
|
|
554
698
|
ctx.health.onError('timeout_no_t_ms');
|
|
@@ -652,9 +796,16 @@ function listenMqtt(defaultFuncs, api, ctx, globalCallback) {
|
|
|
652
796
|
}
|
|
653
797
|
function scheduleAdaptiveReconnect(defaultFuncs, api, ctx, globalCallback, reason){
|
|
654
798
|
const state = getBackoffState(ctx);
|
|
655
|
-
const
|
|
799
|
+
const guard = getStormGuard(ctx);
|
|
800
|
+
let delay = computeNextDelay(state);
|
|
801
|
+
if (guard && guard.active) {
|
|
802
|
+
delay = Math.min(state.max, delay + guard.extraDelayMs);
|
|
803
|
+
}
|
|
656
804
|
ctx.health.onReconnectScheduled(delay);
|
|
657
|
-
const
|
|
805
|
+
const tags = [];
|
|
806
|
+
if (reason) tags.push(reason);
|
|
807
|
+
if (guard && guard.active) tags.push('storm');
|
|
808
|
+
const suffix = tags.length ? ` (${tags.join(', ')})` : '';
|
|
658
809
|
log.warn('listenMqtt', `Reconnecting in ${delay} ms (adaptive backoff)${suffix}`);
|
|
659
810
|
setTimeout(()=>listenMqtt(defaultFuncs, api, ctx, globalCallback), delay);
|
|
660
811
|
}
|