nexus-fca 2.1.1 → 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
@@ -1,5 +1,20 @@
1
1
  # Changelog
2
2
 
3
+ ## [2.1.2] - Unreleased - CONTINUOUS IDLE RECOVERY
4
+ ### Added
5
+ - Soft-stale probing at 2 minutes idle (ping + conditional forced reconnect if no events within 5-8s)
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)`
10
+
11
+ ### Improved
12
+ - Faster recovery from silent idle states (previously required >5 min or external trigger)
13
+ - Reduced chance of appearing online but unresponsive after short inactivity
14
+ - Added keepalive foreground_state publishes each heartbeat
15
+
16
+ ---
17
+
3
18
  ## [2.1.1] - 2025-08-27 - ADVANCED SESSION STABILITY
4
19
  ### 🛠 Added
5
20
  - Adaptive safe session refresh interval (dynamic based on risk level)
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),
@@ -239,6 +268,26 @@ function buildAPI(globalOptions, html, jar) {
239
268
  api[v.replace(".js", "")] = require("./src/" + v)(defaultFuncs, api, ctx);
240
269
  });
241
270
  api.listen = api.listenMqtt;
271
+ // Safety wrapper: ensure every inbound MQTT event updates safety lastEvent timestamp
272
+ if (!api._safetyWrappedListen) {
273
+ const _origListen = api.listenMqtt;
274
+ api.listenMqtt = function(callback) {
275
+ const wrapped = (err, evt) => {
276
+ if (!err && evt) {
277
+ try { globalSafety.recordEvent(); } catch(_) {}
278
+ }
279
+ if (typeof callback === 'function') callback(err, evt);
280
+ };
281
+ const emitter = _origListen(wrapped);
282
+ // Redundant defensive hooks
283
+ try {
284
+ emitter.on('message', () => globalSafety.recordEvent());
285
+ emitter.on('error', () => globalSafety.recordEvent());
286
+ } catch(_) {}
287
+ return emitter;
288
+ };
289
+ api._safetyWrappedListen = true;
290
+ }
242
291
  setInterval(async () => {
243
292
  api
244
293
  .refreshFb_dtsg()
@@ -249,6 +298,59 @@ function buildAPI(globalOptions, html, jar) {
249
298
  console.error("An error occurred while refreshing fb_dtsg", err);
250
299
  });
251
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 ===
252
354
  return {
253
355
  ctx,
254
356
  defaultFuncs,
@@ -344,12 +446,25 @@ function loginHelper(appState, email, password, globalOptions, callback, prCallb
344
446
  if (!safetyStatus.safe) {
345
447
  logger(`⚠️ Login safety warning: ${safetyStatus.reason}`, 'warn');
346
448
  }
347
-
348
449
  logger('✅ Session authenticated successfully', 'info');
349
-
350
450
  // Initialize safety monitoring
351
451
  globalSafety.startMonitoring(ctx, api);
352
-
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 */ }
353
468
  callback(null, api);
354
469
  })
355
470
  .catch(e => {
@@ -873,6 +988,7 @@ class IntegratedNexusLoginSystem {
873
988
 
874
989
  // Integrated Nexus Login wrapper for easy usage
875
990
  async function integratedNexusLogin(credentials = null, options = {}) {
991
+ printFancyStartupBanner();
876
992
  const loginSystem = new IntegratedNexusLoginSystem(options);
877
993
 
878
994
  // Professional logging system
@@ -988,6 +1104,7 @@ async function integratedNexusLogin(credentials = null, options = {}) {
988
1104
  * - Appstate only: Uses existing session directly
989
1105
  */
990
1106
  async function login(loginData, options = {}, callback) {
1107
+ printFancyStartupBanner();
991
1108
  // Support multiple callback signatures
992
1109
  if (typeof options === 'function') {
993
1110
  callback = options;
@@ -66,6 +66,12 @@ class FacebookSafety {
66
66
  this._destroyed = false;
67
67
  this._postRefreshChecks = [];
68
68
  this._inFlightRefreshId = 0;
69
+ // New: probing guard to avoid overlapping soft-stale probes
70
+ this._probing = false;
71
+ // Ghost detection guard
72
+ this._ghostChecking = false;
73
+ // Periodic recycle timer
74
+ this._periodicRecycleTimer = null;
69
75
 
70
76
  this.initSafety();
71
77
  }
@@ -78,6 +84,7 @@ class FacebookSafety {
78
84
 
79
85
  // Setup session monitoring
80
86
  this.setupSessionMonitoring();
87
+ this._schedulePeriodicRecycle();
81
88
  }
82
89
 
83
90
  /**
@@ -243,18 +250,19 @@ class FacebookSafety {
243
250
  clearTimeout(this._safeRefreshTimer);
244
251
  this._safeRefreshTimer = null;
245
252
  }
246
- // 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)
247
255
  const schedule = () => {
248
256
  if (this._destroyed) return;
249
- // Adaptive interval: shorter if high risk (to revalidate), longer if stable
250
- const base = this.sessionMetrics.riskLevel === 'high' ? 25 : this.sessionMetrics.riskLevel === 'medium' ? 35 : 45; // minutes
251
- const baseInterval = base * 60 * 1000;
252
- const randomVariation = (Math.random() * 16 - 8) * 60 * 1000; // ±8 min
253
- 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);
254
262
  this._safeRefreshTimer = setTimeout(async () => {
255
263
  await this.refreshSafeSession();
256
264
  schedule();
257
- }, Math.max(10 * 60 * 1000, interval)); // never below 10 min
265
+ }, interval);
258
266
  };
259
267
  schedule();
260
268
  }
@@ -306,12 +314,30 @@ class FacebookSafety {
306
314
  async _ensureMqttAlive() {
307
315
  if (!this.api || this._destroyed) return;
308
316
  try {
317
+ const now = Date.now();
309
318
  const disconnected = !this.ctx || !this.ctx.mqttClient || !this.ctx.mqttClient.connected;
310
- const stale = Date.now() - this._lastEventTs > 5 * 60 * 1000; // >5 min no events
319
+ const idle = now - this._lastEventTs;
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;
311
323
  if (disconnected || stale) {
312
- await this._reconnectMqttWithBackoff(disconnected ? 'disconnected' : 'stale');
324
+ await this._reconnectMqttWithBackoff(disconnected ? 'disconnected' : 'hard-stale');
325
+ return;
326
+ }
327
+ if (softStale && !this._probing) {
328
+ this._probing = true;
329
+ const prevTs = this._lastEventTs;
330
+ try { if (this.ctx && this.ctx.mqttClient && this.ctx.mqttClient.connected && typeof this.ctx.mqttClient.ping === 'function') this.ctx.mqttClient.ping(); } catch(_) {}
331
+ setTimeout(() => {
332
+ if (this._destroyed) return;
333
+ if (this._lastEventTs <= prevTs) {
334
+ this._backoff.attempt = 0;
335
+ this._reconnectMqttWithBackoff('soft-stale');
336
+ }
337
+ this._probing = false;
338
+ }, 6000 + Math.random() * 2000); // 6-8s probe window (Stealth+Resilient)
313
339
  }
314
- } catch (_) { /* swallow */ }
340
+ } catch(_) {}
315
341
  }
316
342
 
317
343
  // Progressive backoff + jitter reconnect
@@ -320,38 +346,47 @@ class FacebookSafety {
320
346
  this._reconnecting = true;
321
347
  try {
322
348
  const now = Date.now();
323
- if (now < this._backoff.next) {
324
- return; // respect backoff window
325
- }
349
+ if (now < this._backoff.next) { return; }
326
350
  const attempt = ++this._backoff.attempt;
327
- const baseDelay = Math.min(30000, 1000 * Math.pow(2, Math.min(attempt, 5))); // cap 30s
328
- 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;
329
354
  const delay = baseDelay + jitter;
330
355
  this._backoff.next = now + delay;
331
356
  await new Promise(r => setTimeout(r, delay));
332
- // Graceful stop old listener
333
- if (this._activeListenerStop && typeof this._activeListenerStop === 'function') {
334
- try { this._activeListenerStop(); } catch (_) {}
335
- }
357
+ if (this._activeListenerStop && typeof this._activeListenerStop === 'function') { try { this._activeListenerStop(); } catch(_) {} }
336
358
  if (this.api && typeof this.api.listenMqtt === 'function' && !this._destroyed) {
337
- const stop = this.api.listenMqtt((err, event) => {
338
- if (!err && event) this.recordEvent();
339
- });
359
+ const stop = this.api.listenMqtt((err, event) => { if (!err && event) this.recordEvent(); });
340
360
  this._activeListenerStop = stop;
341
- if (attempt > 1) this.safetyEmit('mqttBackoff', { attempt, delay, reason });
342
- else this.safetyEmit('mqttReconnect', { success: true, reason });
361
+ this.safetyEmit('mqttReconnect', { success: true, reason, attempt, delay });
343
362
  }
344
- // Reset backoff on success detection soon after
345
363
  setTimeout(() => {
346
- if (this.ctx && this.ctx.mqttClient && this.ctx.mqttClient.connected) {
347
- this._backoff.attempt = 0;
348
- }
364
+ if (this.ctx && this.ctx.mqttClient && this.ctx.mqttClient.connected) { this._backoff.attempt = 0; }
349
365
  }, 5000);
350
- } catch (e) {
351
- this.safetyEmit('mqttReconnect', { success: false, error: e.message });
352
- } finally {
353
- this._reconnecting = false;
354
- }
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);
355
390
  }
356
391
 
357
392
  // Heartbeat ping & watchdog
@@ -359,26 +394,38 @@ class FacebookSafety {
359
394
  if (this._heartbeatTimer) clearInterval(this._heartbeatTimer);
360
395
  if (this._watchdogTimer) clearInterval(this._watchdogTimer);
361
396
  if (this._destroyed) return;
397
+ // Stealth profile heartbeat: 80–100s random
362
398
  this._heartbeatTimer = setInterval(() => {
363
399
  if (this._destroyed) return;
364
400
  try {
365
401
  if (this.ctx && this.ctx.mqttClient && this.ctx.mqttClient.connected) {
366
402
  if (this.ctx.mqttClient.ping) this.ctx.mqttClient.ping();
403
+ try { this.ctx.mqttClient.publish('/foreground_state', JSON.stringify({ foreground: true })); } catch(_) {}
367
404
  this.safetyEmit('heartbeat', { ts: Date.now() });
368
405
  }
369
- } catch (_) {}
370
- }, 60 * 1000 + Math.random() * 5000); // 60s ±5s
406
+ } catch(_) {}
407
+ }, (80 + Math.random()*20) * 1000);
371
408
  this._watchdogTimer = setInterval(() => {
372
409
  if (this._destroyed) return;
373
410
  const idle = Date.now() - this._lastEventTs;
374
- if (idle > 2 * 60 * 1000) { // 2 min no events -> soft check
375
- 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);
376
422
  }
377
- if (idle > 15 * 60 * 1000) { // 15 min -> force reconnect attempt ignoring backoff
378
- this._backoff.attempt = 0; // reset to allow immediate
423
+ // Hard watchdog escalate: 12m
424
+ if (idle > 12 * 60 * 1000) {
425
+ this._backoff.attempt = 0;
379
426
  this._ensureMqttAlive();
380
427
  }
381
- }, 30 * 1000); // watchdog every 30s
428
+ }, 35 * 1000); // slight change to avoid pattern
382
429
  }
383
430
 
384
431
  /**
@@ -503,7 +550,7 @@ class FacebookSafety {
503
550
  // Cleanup / destroy resources (to prevent dangling timers)
504
551
  destroy() {
505
552
  this._destroyed = true;
506
- const timers = [this._safeRefreshInterval, this._safeRefreshTimer, this._heartbeatTimer, this._watchdogTimer];
553
+ const timers = [this._safeRefreshInterval, this._safeRefreshTimer, this._heartbeatTimer, this._watchdogTimer, this._periodicRecycleTimer];
507
554
  timers.forEach(t => t && clearTimeout(t));
508
555
  if (this._activeListenerStop) {
509
556
  try { this._activeListenerStop(); } catch (_) {}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nexus-fca",
3
- "version": "2.1.1",
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 {