nexus-fca 2.1.2 → 2.1.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 CHANGED
@@ -4,10 +4,14 @@
4
4
  ### Added
5
5
  - Soft-stale probing at 2 minutes idle (ping + conditional forced reconnect if no events within 5-8s)
6
6
  - Wrapper around `listenMqtt` to automatically feed events into safety heartbeat (`recordEvent`) for precise idle detection
7
+ - Ghost connection detection (10m silent but socket connected triggers forced reconnect after probe)
8
+ - Periodic connection recycle every ~6h ±30m to prevent long-lived silent degradation
9
+ - Force reconnect API: `globalSafety.forceReconnect(tag)`
7
10
 
8
11
  ### Improved
9
12
  - Faster recovery from silent idle states (previously required >5 min or external trigger)
10
13
  - Reduced chance of appearing online but unresponsive after short inactivity
14
+ - Added keepalive foreground_state publishes each heartbeat
11
15
 
12
16
  ---
13
17
 
package/index.js CHANGED
@@ -10,6 +10,33 @@ const path = require('path');
10
10
  const models = require("./lib/database/models");
11
11
  const logger = require("./lib/logger");
12
12
  const { safeMode, ultraSafeMode, smartSafetyLimiter, isUserAllowed } = require('./utils'); // Enhanced safety system
13
+ // Minimal aesthetic banner system
14
+ let _fancyBannerPrinted = false;
15
+ const gradient = (() => { try { return require('gradient-string'); } catch(_) { return null; } })();
16
+ const pkgMeta = (() => { try { return require('./package.json'); } catch(_) { return { version: 'dev' }; } })();
17
+ function printFancyStartupBanner() {
18
+ if (_fancyBannerPrinted) return; _fancyBannerPrinted = true;
19
+ const lines = [
20
+ '╔══════════════════════════════════════════════════════╗',
21
+ '║ Nexus-FCA Secure Login ║',
22
+ '║ Advanced Stable Messenger Automation API ║',
23
+ '╚══════════════════════════════════════════════════════╝'
24
+ ];
25
+ if (gradient) console.log(gradient.cristal.multiline(lines.join('\n'))); else console.log(lines.join('\n'));
26
+ }
27
+ function printIdentityBanner(uid, name) {
28
+ const cleanName = name || 'Unknown';
29
+ const pad = (txt, len) => (txt.length < len ? txt + ' '.repeat(len - txt.length) : txt.substring(0, len));
30
+ const bodyLen = 54;
31
+ const line = (content) => `║ ${pad(content, bodyLen)} ║`;
32
+ const box = [
33
+ '╔════════════════ LOGGED IN IDENTITY ════════════════╗',
34
+ line(`UID : ${uid}`),
35
+ line(`Name: ${cleanName}`),
36
+ '╚════════════════════════════════════════════════════╝'
37
+ ];
38
+ if (gradient) console.log(gradient.atlas.multiline(box.join('\n'))); else console.log(box.join('\n'));
39
+ }
13
40
 
14
41
  // Enhanced imports - All new modules
15
42
  const { NexusClient } = require('./lib/compatibility/NexusClient');
@@ -208,7 +235,9 @@ function buildAPI(globalOptions, html, jar) {
208
235
  firstListen: true,
209
236
  fb_dtsg,
210
237
  wsReqNumber: 0,
211
- wsTaskNumber: 0
238
+ wsTaskNumber: 0,
239
+ // Provide safety module reference to lower layers (listenMqtt)
240
+ globalSafety
212
241
  };
213
242
  const api = {
214
243
  setOptions: setOptions.bind(null, globalOptions),
@@ -269,6 +298,59 @@ function buildAPI(globalOptions, html, jar) {
269
298
  console.error("An error occurred while refreshing fb_dtsg", err);
270
299
  });
271
300
  }, 1000 * 60 * 60 * 24);
301
+ // === Group Queue (No Cooldown, Sequential per group) ===
302
+ (function initGroupQueue(){
303
+ const groupQueues = new Map(); // threadID -> { q: [], sending: false }
304
+ const isGroupThread = (tid) => typeof tid === 'string' && tid.length >= 15; // simple heuristic
305
+ const DIRECT_FN = api.sendMessage; // original
306
+
307
+ api.enableGroupQueue = function(enable=true){
308
+ globalOptions.groupQueueEnabled = !!enable;
309
+ };
310
+ api.setGroupQueueCapacity = function(n){ globalOptions.groupQueueMax = n; };
311
+ api.enableGroupQueue(true);
312
+ api.setGroupQueueCapacity(100); // allow up to 100 pending per group
313
+
314
+ api._sendMessageDirect = DIRECT_FN;
315
+ api.sendMessage = function(message, threadID, cb){
316
+ if(!globalOptions.groupQueueEnabled || !isGroupThread(threadID)) {
317
+ return api._sendMessageDirect(message, threadID, cb);
318
+ }
319
+ let entry = groupQueues.get(threadID);
320
+ if(!entry){ entry = { q: [], sending: false }; groupQueues.set(threadID, entry); }
321
+ if(entry.q.length >= (globalOptions.groupQueueMax||100)) {
322
+ // drop oldest (keep newest) to avoid unbounded growth
323
+ entry.q.shift();
324
+ }
325
+ entry.q.push({ message, threadID, cb });
326
+ processQueue(threadID, entry);
327
+ };
328
+
329
+ function processQueue(threadID, entry){
330
+ if(entry.sending) return;
331
+ if(!entry.q.length) return;
332
+ entry.sending = true;
333
+ const { message, threadID: tid, cb } = entry.q.shift();
334
+ api._sendMessageDirect(message, tid, function(err, res){
335
+ try { if(!err) globalSafety.recordEvent(); } catch(_) {}
336
+ if(typeof cb === 'function') cb(err, res);
337
+ entry.sending = false;
338
+ // Immediately process next (no cooldown) to keep strict sequence
339
+ setImmediate(()=>processQueue(threadID, entry));
340
+ });
341
+ }
342
+
343
+ api._flushGroupQueue = function(threadID){
344
+ const entry = groupQueues.get(threadID);
345
+ if(!entry) return;
346
+ while(entry.q.length) {
347
+ const item = entry.q.shift();
348
+ api._sendMessageDirect(item.message, item.threadID, item.cb);
349
+ }
350
+ entry.sending = false;
351
+ };
352
+ })();
353
+ // === End Group Queue ===
272
354
  return {
273
355
  ctx,
274
356
  defaultFuncs,
@@ -364,12 +446,25 @@ function loginHelper(appState, email, password, globalOptions, callback, prCallb
364
446
  if (!safetyStatus.safe) {
365
447
  logger(`⚠️ Login safety warning: ${safetyStatus.reason}`, 'warn');
366
448
  }
367
-
368
449
  logger('✅ Session authenticated successfully', 'info');
369
-
370
450
  // Initialize safety monitoring
371
451
  globalSafety.startMonitoring(ctx, api);
372
-
452
+ // Post-login identity banner
453
+ try {
454
+ const uid = api.getCurrentUserID && api.getCurrentUserID();
455
+ if (api.getUserInfo && uid) {
456
+ api.getUserInfo(uid, (err, info) => {
457
+ if (!err && info) {
458
+ const userObj = info[uid] || info; // depending on structure
459
+ printIdentityBanner(uid, userObj.name || userObj.firstName || userObj.fullName);
460
+ } else {
461
+ printIdentityBanner(uid || 'N/A');
462
+ }
463
+ });
464
+ } else {
465
+ printIdentityBanner(uid || 'N/A');
466
+ }
467
+ } catch(_) { /* ignore */ }
373
468
  callback(null, api);
374
469
  })
375
470
  .catch(e => {
@@ -893,6 +988,7 @@ class IntegratedNexusLoginSystem {
893
988
 
894
989
  // Integrated Nexus Login wrapper for easy usage
895
990
  async function integratedNexusLogin(credentials = null, options = {}) {
991
+ printFancyStartupBanner();
896
992
  const loginSystem = new IntegratedNexusLoginSystem(options);
897
993
 
898
994
  // Professional logging system
@@ -1008,6 +1104,7 @@ async function integratedNexusLogin(credentials = null, options = {}) {
1008
1104
  * - Appstate only: Uses existing session directly
1009
1105
  */
1010
1106
  async function login(loginData, options = {}, callback) {
1107
+ printFancyStartupBanner();
1011
1108
  // Support multiple callback signatures
1012
1109
  if (typeof options === 'function') {
1013
1110
  callback = options;
@@ -68,6 +68,10 @@ class FacebookSafety {
68
68
  this._inFlightRefreshId = 0;
69
69
  // New: probing guard to avoid overlapping soft-stale probes
70
70
  this._probing = false;
71
+ // Ghost detection guard
72
+ this._ghostChecking = false;
73
+ // Periodic recycle timer
74
+ this._periodicRecycleTimer = null;
71
75
 
72
76
  this.initSafety();
73
77
  }
@@ -80,6 +84,7 @@ class FacebookSafety {
80
84
 
81
85
  // Setup session monitoring
82
86
  this.setupSessionMonitoring();
87
+ this._schedulePeriodicRecycle();
83
88
  }
84
89
 
85
90
  /**
@@ -245,18 +250,19 @@ class FacebookSafety {
245
250
  clearTimeout(this._safeRefreshTimer);
246
251
  this._safeRefreshTimer = null;
247
252
  }
248
- // Use recursive timeout with randomization each cycle (more human-like)
253
+ // Stealth+Resilient profile refresh policy:
254
+ // risk low: 50-60m, medium: 40-50m, high: 25-35m (random inside band)
249
255
  const schedule = () => {
250
256
  if (this._destroyed) return;
251
- // Adaptive interval: shorter if high risk (to revalidate), longer if stable
252
- const base = this.sessionMetrics.riskLevel === 'high' ? 25 : this.sessionMetrics.riskLevel === 'medium' ? 35 : 45; // minutes
253
- const baseInterval = base * 60 * 1000;
254
- const randomVariation = (Math.random() * 16 - 8) * 60 * 1000; // ±8 min
255
- const interval = baseInterval + randomVariation;
257
+ let minM, maxM;
258
+ if (this.sessionMetrics.riskLevel === 'high') { minM = 25; maxM = 35; }
259
+ else if (this.sessionMetrics.riskLevel === 'medium') { minM = 40; maxM = 50; }
260
+ else { minM = 50; maxM = 60; }
261
+ const interval = (minM * 60 * 1000) + Math.random() * ((maxM - minM) * 60 * 1000);
256
262
  this._safeRefreshTimer = setTimeout(async () => {
257
263
  await this.refreshSafeSession();
258
264
  schedule();
259
- }, Math.max(10 * 60 * 1000, interval)); // never below 10 min
265
+ }, interval);
260
266
  };
261
267
  schedule();
262
268
  }
@@ -311,40 +317,27 @@ class FacebookSafety {
311
317
  const now = Date.now();
312
318
  const disconnected = !this.ctx || !this.ctx.mqttClient || !this.ctx.mqttClient.connected;
313
319
  const idle = now - this._lastEventTs;
314
- const softStale = idle > 2 * 60 * 1000; // >2 min no events
315
- const hardStale = idle > 5 * 60 * 1000; // >5 min no events (legacy threshold)
316
- const stale = hardStale; // backwards compat naming
317
-
318
- // If totally disconnected or hard stale -> reconnect immediately
320
+ const softStale = idle > (2.5 * 60 * 1000); // Stealth profile: 2m30s
321
+ const hardStale = idle > 8 * 60 * 1000; // escalate earlier than watchdog hard (8m)
322
+ const stale = hardStale;
319
323
  if (disconnected || stale) {
320
324
  await this._reconnectMqttWithBackoff(disconnected ? 'disconnected' : 'hard-stale');
321
325
  return;
322
326
  }
323
-
324
- // Soft-stale probing: connection claims to be open but no events for 2-5 minutes.
325
- // We issue a ping and if still no events after probe window, we force a reconnect.
326
327
  if (softStale && !this._probing) {
327
328
  this._probing = true;
328
329
  const prevTs = this._lastEventTs;
329
- try {
330
- if (this.ctx && this.ctx.mqttClient && this.ctx.mqttClient.connected) {
331
- if (typeof this.ctx.mqttClient.ping === 'function') {
332
- try { this.ctx.mqttClient.ping(); } catch(_) {}
333
- }
334
- }
335
- } catch(_) {}
330
+ try { if (this.ctx && this.ctx.mqttClient && this.ctx.mqttClient.connected && typeof this.ctx.mqttClient.ping === 'function') this.ctx.mqttClient.ping(); } catch(_) {}
336
331
  setTimeout(() => {
337
332
  if (this._destroyed) return;
338
- // If no new events arrived since probe start, treat as latent-dead connection
339
333
  if (this._lastEventTs <= prevTs) {
340
- // Reset backoff to allow immediate reconnect (latency sensitive)
341
334
  this._backoff.attempt = 0;
342
335
  this._reconnectMqttWithBackoff('soft-stale');
343
336
  }
344
337
  this._probing = false;
345
- }, 5000 + Math.random() * 3000); // 5-8s probe window
338
+ }, 6000 + Math.random() * 2000); // 6-8s probe window (Stealth+Resilient)
346
339
  }
347
- } catch (_) { /* swallow */ }
340
+ } catch(_) {}
348
341
  }
349
342
 
350
343
  // Progressive backoff + jitter reconnect
@@ -353,38 +346,47 @@ class FacebookSafety {
353
346
  this._reconnecting = true;
354
347
  try {
355
348
  const now = Date.now();
356
- if (now < this._backoff.next) {
357
- return; // respect backoff window
358
- }
349
+ if (now < this._backoff.next) { return; }
359
350
  const attempt = ++this._backoff.attempt;
360
- const baseDelay = Math.min(30000, 1000 * Math.pow(2, Math.min(attempt, 5))); // cap 30s
361
- const jitter = Math.random() * 400;
351
+ // Stealth backoff: 1.2s * 1.8^n capped ~20s, add jitter 0-500ms
352
+ const baseDelay = Math.min(20000, 1200 * Math.pow(1.8, Math.min(attempt, 6)));
353
+ const jitter = Math.random() * 500;
362
354
  const delay = baseDelay + jitter;
363
355
  this._backoff.next = now + delay;
364
356
  await new Promise(r => setTimeout(r, delay));
365
- // Graceful stop old listener
366
- if (this._activeListenerStop && typeof this._activeListenerStop === 'function') {
367
- try { this._activeListenerStop(); } catch (_) {}
368
- }
357
+ if (this._activeListenerStop && typeof this._activeListenerStop === 'function') { try { this._activeListenerStop(); } catch(_) {} }
369
358
  if (this.api && typeof this.api.listenMqtt === 'function' && !this._destroyed) {
370
- const stop = this.api.listenMqtt((err, event) => {
371
- if (!err && event) this.recordEvent();
372
- });
359
+ const stop = this.api.listenMqtt((err, event) => { if (!err && event) this.recordEvent(); });
373
360
  this._activeListenerStop = stop;
374
- if (attempt > 1) this.safetyEmit('mqttBackoff', { attempt, delay, reason });
375
- else this.safetyEmit('mqttReconnect', { success: true, reason });
361
+ this.safetyEmit('mqttReconnect', { success: true, reason, attempt, delay });
376
362
  }
377
- // Reset backoff on success detection soon after
378
363
  setTimeout(() => {
379
- if (this.ctx && this.ctx.mqttClient && this.ctx.mqttClient.connected) {
380
- this._backoff.attempt = 0;
381
- }
364
+ if (this.ctx && this.ctx.mqttClient && this.ctx.mqttClient.connected) { this._backoff.attempt = 0; }
382
365
  }, 5000);
383
- } catch (e) {
384
- this.safetyEmit('mqttReconnect', { success: false, error: e.message });
385
- } finally {
386
- this._reconnecting = false;
387
- }
366
+ } catch(e) {
367
+ this.safetyEmit('mqttReconnect', { success: false, error: e.message, reason });
368
+ } finally { this._reconnecting = false; }
369
+ }
370
+
371
+ // Public force reconnect (bypass backoff)
372
+ forceReconnect(tag = 'manual') {
373
+ if (this._destroyed) return;
374
+ this._backoff.attempt = 0;
375
+ return this._reconnectMqttWithBackoff('force-' + tag);
376
+ }
377
+
378
+ // Schedule periodic recycle (connection rejuvenation) every 6h ±30m jitter
379
+ _schedulePeriodicRecycle() {
380
+ if (this._periodicRecycleTimer) clearTimeout(this._periodicRecycleTimer);
381
+ if (this._destroyed) return;
382
+ const base = 6 * 60 * 60 * 1000; // 6h
383
+ const jitter = (Math.random() * 60 - 30) * 60 * 1000; // ±30m
384
+ const delay = base + jitter;
385
+ this._periodicRecycleTimer = setTimeout(() => {
386
+ if (this._destroyed) return;
387
+ this.forceReconnect('periodic');
388
+ this._schedulePeriodicRecycle();
389
+ }, delay);
388
390
  }
389
391
 
390
392
  // Heartbeat ping & watchdog
@@ -392,26 +394,38 @@ class FacebookSafety {
392
394
  if (this._heartbeatTimer) clearInterval(this._heartbeatTimer);
393
395
  if (this._watchdogTimer) clearInterval(this._watchdogTimer);
394
396
  if (this._destroyed) return;
397
+ // Stealth profile heartbeat: 80–100s random
395
398
  this._heartbeatTimer = setInterval(() => {
396
399
  if (this._destroyed) return;
397
400
  try {
398
401
  if (this.ctx && this.ctx.mqttClient && this.ctx.mqttClient.connected) {
399
402
  if (this.ctx.mqttClient.ping) this.ctx.mqttClient.ping();
403
+ try { this.ctx.mqttClient.publish('/foreground_state', JSON.stringify({ foreground: true })); } catch(_) {}
400
404
  this.safetyEmit('heartbeat', { ts: Date.now() });
401
405
  }
402
- } catch (_) {}
403
- }, 60 * 1000 + Math.random() * 5000); // 60s ±5s
406
+ } catch(_) {}
407
+ }, (80 + Math.random()*20) * 1000);
404
408
  this._watchdogTimer = setInterval(() => {
405
409
  if (this._destroyed) return;
406
410
  const idle = Date.now() - this._lastEventTs;
407
- if (idle > 2 * 60 * 1000) { // 2 min no events -> soft check
408
- this._ensureMqttAlive();
411
+ // Soft escalate already handled inside _ensureMqttAlive at 2m30s
412
+ // Ghost detection earlier: 9m
413
+ if (idle > 9 * 60 * 1000 && !this._ghostChecking && this.ctx && this.ctx.mqttClient && this.ctx.mqttClient.connected) {
414
+ this._ghostChecking = true;
415
+ const before = this._lastEventTs;
416
+ try { if (this.ctx.mqttClient.ping) this.ctx.mqttClient.ping(); } catch(_) {}
417
+ setTimeout(() => {
418
+ if (this._destroyed) return;
419
+ if (this._lastEventTs <= before) { this.forceReconnect('ghost'); }
420
+ setTimeout(() => { this._ghostChecking = false; }, 5 * 60 * 1000);
421
+ }, 6000 + Math.random()*2000);
409
422
  }
410
- if (idle > 15 * 60 * 1000) { // 15 min -> force reconnect attempt ignoring backoff
411
- this._backoff.attempt = 0; // reset to allow immediate
423
+ // Hard watchdog escalate: 12m
424
+ if (idle > 12 * 60 * 1000) {
425
+ this._backoff.attempt = 0;
412
426
  this._ensureMqttAlive();
413
427
  }
414
- }, 30 * 1000); // watchdog every 30s
428
+ }, 35 * 1000); // slight change to avoid pattern
415
429
  }
416
430
 
417
431
  /**
@@ -536,7 +550,7 @@ class FacebookSafety {
536
550
  // Cleanup / destroy resources (to prevent dangling timers)
537
551
  destroy() {
538
552
  this._destroyed = true;
539
- const timers = [this._safeRefreshInterval, this._safeRefreshTimer, this._heartbeatTimer, this._watchdogTimer];
553
+ const timers = [this._safeRefreshInterval, this._safeRefreshTimer, this._heartbeatTimer, this._watchdogTimer, this._periodicRecycleTimer];
540
554
  timers.forEach(t => t && clearTimeout(t));
541
555
  if (this._activeListenerStop) {
542
556
  try { this._activeListenerStop(); } catch (_) {}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nexus-fca",
3
- "version": "2.1.2",
3
+ "version": "2.1.3",
4
4
  "description": "A modern, safe, and advanced Facebook Chat API for Node.js with fully integrated Nexus Login System. NPM-ready with ID/password/2FA support, ultra-low ban rate protection, and zero external dependencies.",
5
5
  "main": "index.js",
6
6
  "repository": {
package/src/listenMqtt.js CHANGED
@@ -260,6 +260,7 @@ function listenMqtt(defaultFuncs, api, ctx, globalCallback) {
260
260
  }
261
261
  });
262
262
  mqttClient.on("connect", function () {
263
+ if (ctx.globalSafety) { try { ctx.globalSafety.recordEvent(); } catch(_) {} }
263
264
  if (process.env.OnStatus === undefined) {
264
265
  logger("Nexus-FCA premium features works only with Nexus-Bot framework(Kidding)", "info");
265
266
  process.env.OnStatus = true;
@@ -311,6 +312,7 @@ function listenMqtt(defaultFuncs, api, ctx, globalCallback) {
311
312
  };
312
313
  });
313
314
  mqttClient.on("message", function (topic, message, _packet) {
315
+ if (ctx.globalSafety) { try { ctx.globalSafety.recordEvent(); } catch(_) {} }
314
316
  try {
315
317
  let jsonMessage = Buffer.isBuffer(message)
316
318
  ? Buffer.from(message).toString()
@@ -404,8 +406,21 @@ function listenMqtt(defaultFuncs, api, ctx, globalCallback) {
404
406
  return;
405
407
  }
406
408
  });
407
- mqttClient.on("close", function () {});
408
- mqttClient.on("disconnect", () => {});
409
+ mqttClient.on("close", function () { if (ctx.globalSafety) { try { ctx.globalSafety._ensureMqttAlive(); } catch(_) {} } });
410
+ mqttClient.on("disconnect", () => { if (ctx.globalSafety) { try { ctx.globalSafety._ensureMqttAlive(); } catch(_) {} } });
411
+ // Lightweight periodic synthetic event to prevent idle expiry if FB sends nothing
412
+ if (!ctx._syntheticKeepAliveInterval) {
413
+ ctx._syntheticKeepAliveInterval = setInterval(() => {
414
+ if (!ctx.mqttClient || !ctx.mqttClient.connected) return;
415
+ if (ctx.globalSafety) {
416
+ const idle = Date.now() - ctx.globalSafety._lastEventTs;
417
+ // Inject synthetic event every 70s if no real traffic -> keeps timers fresh
418
+ if (idle > 65 * 1000) {
419
+ ctx.globalSafety.recordEvent();
420
+ }
421
+ }
422
+ }, 30000);
423
+ }
409
424
  }
410
425
  function getTaskResponseData(taskType, payload) {
411
426
  try {