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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. 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.5",
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 = false;
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 delay = computeNextDelay(state);
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 suffix = reason ? ` (${reason})` : '';
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
  }