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 +15 -0
- package/index.js +121 -4
- package/lib/safety/FacebookSafety.js +88 -41
- package/package.json +1 -1
- package/src/listenMqtt.js +17 -2
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
|
-
//
|
|
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
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
const interval =
|
|
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
|
-
},
|
|
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
|
|
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
|
|
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
|
-
|
|
328
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
351
|
-
this.safetyEmit('mqttReconnect', { success: false, error: e.message });
|
|
352
|
-
} finally {
|
|
353
|
-
|
|
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
|
-
},
|
|
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
|
-
|
|
375
|
-
|
|
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
|
-
|
|
378
|
-
|
|
423
|
+
// Hard watchdog escalate: 12m
|
|
424
|
+
if (idle > 12 * 60 * 1000) {
|
|
425
|
+
this._backoff.attempt = 0;
|
|
379
426
|
this._ensureMqttAlive();
|
|
380
427
|
}
|
|
381
|
-
},
|
|
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.
|
|
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 {
|