nexus-fca 2.1.5 → 2.1.7

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,37 @@
1
1
  # Changelog
2
2
 
3
+ ## [2.1.7] - 2025-09-01 - Session Stability Patch
4
+ ### Added
5
+ - User-Agent continuity (anchored single UA for entire session via safety module; eliminates mid-session UA drift increasing 20–22h expiry risk).
6
+ - Exposed `setFixedUserAgent()` in `FacebookSafety` to allow explicit anchoring from credential phase.
7
+ - Mid-session lightweight token poke (6h ±40m) to keep session warm without full heavy refresh cycle.
8
+
9
+ ### Changed
10
+ - Removed legacy mobile agent override fallback in `loginHelper` that caused mixed UA fingerprints.
11
+ - All safe requests now inherit continuity-aware UA through `applySafeRequestOptions`.
12
+
13
+ ### Improved
14
+ - Extended resilience against 20–22h cookie invalidation observed with prior dual-phase UA pattern.
15
+ - Reduced unnecessary full refresh churn while preserving stealth (`safeRefresh` + light poke coexist).
16
+
17
+ ---
18
+
19
+ ## [2.1.6] - 2025-08-31 - Memory Guard & Queue Sweeping
20
+ ### Added
21
+ - Central lightweight memory guard sweeps: group queue pruning (idle >30m, overflow trim) and pendingEdits TTL sweeper (every 4m).
22
+ - Health metrics extended: memoryGuardRuns, memoryGuardActions, groupQueueDroppedMessages, groupQueueExpiredQueues, groupQueuePrunedThreads, pendingEditSweeps.
23
+ - API: `api.getMemoryMetrics()` returns focused memory-related counters.
24
+ - Typings updated (`EditOptions`, new API methods) in `index.d.ts`.
25
+
26
+ ### Improved
27
+ - Group queue now tracks `lastActive` and enforces idle purge + overflow protection with metrics.
28
+ - Pending edits TTL enforcement separated from resend watchdog for deterministic expiry.
29
+
30
+ ### Notes
31
+ - All guards are low-frequency, low-impact; no change to delivery reliability or safety – only prevention of unbounded growth.
32
+
33
+ ---
34
+
3
35
  ## [2.1.5] - 2025-08-28 - PendingEdits & ACK Metrics
4
36
  ### Added
5
37
  - PendingEdits buffer with cap (default 200) + TTL (5m) + resend attempts (2) + ACK timeout (12s).
package/README.md CHANGED
@@ -1,33 +1,75 @@
1
- # Nexus-FCA v2.1.5
1
+ # Nexus-FCA v2.1.7
2
2
 
3
- <!-- 2.1.5 Highlights (PendingEdits & ACK Metrics) -->
4
- > New in 2.1.5: PendingEdits buffer (cap + TTL + safe resend), edit ACK watchdog, p95 ACK latency & edit resend/failure metrics, configurable via `api.setEditOptions()`.
3
+ <!-- 2.1.7 Session Stability Patch -->
4
+ > New in 2.1.7: Session Stability Patch – anchored User-Agent continuity (eliminates 20–22h silent expiry pattern), lightweight mid‑session token poke (6h ±40m) + existing adaptive safeRefresh, retains ultra‑low ban profile.
5
+
6
+ <!-- 2.1.6 Memory Guard -->
7
+ > 2.1.6: Memory Guard & Queue Sweeping – bounded group queues, pending edit TTL sweeper, memory metrics exporter.
8
+
9
+ <!-- 2.1.5 PendingEdits -->
10
+ > 2.1.5: PendingEdits buffer (cap + TTL + safe resend), edit ACK watchdog, p95 ACK latency & edit resend/failure metrics, configurable via `api.setEditOptions()`.
5
11
 
6
12
  <p align="center">
7
13
  <!-- Preview image wrapped in link (corrected ibb.co domain) -->
8
14
  <a href="https://ibb.co/8ymR1tw"><img src="https://i.ibb.co/Sk61FGg/Dragon-Fruit-1.jpg" alt="Nexus-FCA Dragon Fruit" width="520" border="0" /></a>
9
15
  </p>
10
16
 
11
- > Advanced, safe, modern Facebook Chat (Messenger) API with integrated secure login (ID / Password / 2FA), ultra‑low ban rate session management, MQTT listener, and TypeScript-ready developer experience.
17
+ > Advanced, safe, modern Facebook Chat (Messenger) API with integrated secure login (ID / Password / 2FA), ultra‑low ban rate session management, adaptive MQTT resilience, memory guard, and TypeScript-ready developer experience.
12
18
 
13
19
  ---
14
- ## ✨ Highlights
20
+ ## ✨ Highlights (Core Pillars)
15
21
  - 🔐 Integrated secure login system (username/password + TOTP 2FA) → auto appstate
16
- - 🛡️ Ultra-low ban rate design (human timing, safety limiter, risk heuristics)
17
- - 🔄 Resilient MQTT listener (improved session validation + graceful reconnect)
18
- - 🔁 Persistent device fingerprint (no random rotation fewer checkpoints)
19
- - 🧠 Smart session validation (multi-endpoint retry, reduced false logouts)
20
- - ⚙️ Zero-config appstate reuse & automatic backup/versioned snapshots
21
- - 🧩 Modular architecture (safety, performance, error, mqtt managers)
22
- - 🗂️ Rich feature docs in `/docs` (thread, message, reactions, attachments)
22
+ - 🛡️ Ultra-low ban rate design (human timing, safety limiter, anchored UA, risk heuristics)
23
+ - 🔄 Resilient MQTT listener (adaptive backoff + idle / ghost detection + periodic recycle)
24
+ - ♻️ Session continuity: anchored UA + adaptive safe refresh + lightweight mid-session poke
25
+ - 🧠 Smart session validation (lazy preflight, multi-endpoint retry, reduced false logouts)
26
+ - 📊 Live health & memory metrics (`api.getHealthMetrics()`, `api.getMemoryMetrics()`)
23
27
  - 🧾 Type definitions (`index.d.ts`) & modern Promise / callback API
28
+ - 🧩 Modular architecture (safety, performance, error, mqtt managers)
24
29
 
25
30
  ---
26
- ## 🚀 Install
27
- ```bash
28
- npm install nexus-fca
31
+ ## 🚀 Recent Stability Enhancements (2.1.7 / 2.1.6 / 2.1.5)
32
+ | Version | Focus | Key Additions |
33
+ |---------|-------|---------------|
34
+ | 2.1.7 | Session Longevity | UA continuity anchor, lightweight token poke, removal of mid-login UA drift |
35
+ | 2.1.6 | Memory Safety | Group queue idle purge + overflow trim, pendingEdits TTL sweeper, memory guard metrics |
36
+ | 2.1.5 | Edit Reliability | PendingEdits buffer (cap+TTL), ACK watchdog, resend limits, p95 ACK latency |
37
+
38
+ ### Why UA Continuity Matters
39
+ Previously, dual-phase login could swap user agents (mobile → desktop) causing server-side heuristic expiry near 20–22h. Anchoring a single UA eliminates the inconsistent device fingerprint pattern and extends stable runtime under identical safety posture.
40
+
41
+ ### Lightweight Mid-Session Poke
42
+ A subtle `fb_dtsg` refresh every ~6h ±40m (in addition to adaptive risk-based safeRefresh) keeps tokens warm without aggressive churn, lowering validation friction while avoiding noisy traffic patterns.
43
+
44
+ ---
45
+ ## 🧪 Key API Additions
46
+ ```js
47
+ api.setEditOptions({ maxPendingEdits, editTTLms, ackTimeoutMs, maxResendAttempts });
48
+ api.setBackoffOptions({ base, factor, max, jitter });
49
+ api.enableLazyPreflight(true); // Skip heavy validation if a recent good connect exists
50
+ api.getHealthMetrics(); // uptime, reconnect stats, ack latency, synthetic keepalives
51
+ api.getMemoryMetrics(); // queue depths, drops, guard run counters
52
+ ```
53
+
54
+ ---
55
+ ## 🔍 Monitoring Example
56
+ ```js
57
+ setInterval(() => {
58
+ const h = api.getHealthMetrics();
59
+ const m = api.getMemoryMetrics();
60
+ console.log('[HEALTH]', h?.status, 'acks', h?.ackCount, 'p95Ack', h?.p95AckLatencyMs);
61
+ console.log('[MEM]', m);
62
+ }, 60000);
29
63
  ```
30
64
 
65
+ ---
66
+ ## 🧷 Long Session Best Practices
67
+ 1. Use appstate login when possible (avoid frequent credential logins).
68
+ 2. Keep `persistent-device.json` – do not rotate unless forced.
69
+ 3. Avoid changing UA manually; continuity is automatic post‑2.1.7.
70
+ 4. Inspect health metrics before manually forcing reconnects.
71
+ 5. Let adaptive backoff handle transient network instability.
72
+
31
73
  ---
32
74
  ## ⚡ Quick Start (Appstate)
33
75
  ```js
@@ -61,14 +103,17 @@ const login = require('nexus-fca');
61
103
  ```
62
104
 
63
105
  ---
64
- ## 🛡️ Safety Layer (v2.1.0 Improvements)
106
+ ## 🛡️ Safety Layer (Updated)
65
107
  | Feature | Benefit |
66
108
  |---------|---------|
67
- | Persistent device profile | Prevents repeated “new device” flags & locks |
68
- | Smarter session preflight | Eliminates noisy false `not_logged_in` errors |
69
- | Redirect & HTML detection | Accurate login checkpoint identification |
70
- | Controlled retries (5xx) | Backoff without hammering endpoints |
71
- | Human-like delays | Reduces automated pattern detection |
109
+ | Anchored User-Agent | Eliminates fingerprint drift (prevents 20–22h expiry) |
110
+ | Adaptive Safe Refresh | Risk‑sensitive token renewal bands |
111
+ | Lightweight Token Poke | Quiet longevity without churn |
112
+ | Idle / Ghost Detection | Auto probe + reconnect on silent stalls |
113
+ | Periodic Recycle | 6h ± jitter connection rejuvenation |
114
+ | Persistent Device Profile | Fewer checkpoints / trust continuity |
115
+ | Lazy Preflight | Skips heavy validation when recently healthy |
116
+ | Human-like Timing | Reduces automation signal surface |
72
117
 
73
118
  Disable preflight if needed:
74
119
  ```js
@@ -77,13 +122,14 @@ await login({ appState }, { disablePreflight: true });
77
122
 
78
123
  ---
79
124
  ## 🛰️ MQTT Listener Enhancements
80
- - Preflight now async & tolerant (second-stage check only logs failure)
81
- - Classified errors: `login_redirect`, `html_login_page`, `not_logged_in`
82
- - Automatic cookie/token refresh propagation
125
+ - Adaptive exponential backoff with jitter (caps 5m)
126
+ - Soft-stale probing (2m30s) + hard watchdog tiers
127
+ - Layered post-refresh health checks (1s / 10s / 30s) after token renewal
128
+ - Synthetic keepalives (randomized 55–75s) feeding metrics
83
129
 
84
130
  ---
85
131
  ## 📦 Example Echo Test
86
- `examples/echo-test.js` (already included):
132
+ `examples/echo-test.js`:
87
133
  ```bash
88
134
  node examples/echo-test.js
89
135
  ```
@@ -91,9 +137,9 @@ Provide `appstate.json` or set `EMAIL` / `PASSWORD` env variables.
91
137
 
92
138
  ---
93
139
  ## 🧠 Advanced Login Flow
94
- 1. New integrated system safely generates / refreshes cookies (if credentials supplied)
95
- 2. Legacy core consumes resulting appstate for stable API behavior
96
- 3. Optional persistent device JSON: `persistent-device.json`
140
+ 1. Integrated system safely generates / refreshes cookies (if credentials supplied)
141
+ 2. Core consumes resulting appstate for stable API behavior
142
+ 3. Persistent device JSON: `persistent-device.json`
97
143
 
98
144
  Persistent device toggle:
99
145
  ```js
@@ -174,15 +220,16 @@ const login = require('nexus-fca');
174
220
  - Examples: `/examples`
175
221
 
176
222
  ---
177
- ## 🔁 Updating from 2.0.x → 2.1.0
223
+ ## 🔁 Updating from 2.0.x → 2.1.x
178
224
  | Change | Action |
179
225
  |--------|--------|
180
- | Preflight errors | Noise reduced automatically |
181
- | Device rotation | Now persistent by default |
182
- | parseAndCheckLogin | Handles 3xx & HTML login responses |
183
- | Session validation | New `validateSession` helper |
226
+ | UA Continuity (2.1.7) | No action; auto applied |
227
+ | Memory Guard (2.1.6) | Inspect `api.getMemoryMetrics()` periodically |
228
+ | PendingEdits (2.1.5) | Tune via `api.setEditOptions()` if needed |
229
+ | Lazy Preflight | Optionally disable when embedding in other frameworks |
230
+ | Persistent Device | Keep file unless forced reset required |
184
231
 
185
- No breaking API changes.
232
+ No breaking API changes across 2.1.x line.
186
233
 
187
234
  ---
188
235
  ## ⚠️ Disclaimer
package/index.d.ts CHANGED
@@ -395,6 +395,11 @@ declare module 'nexus-fca' {
395
395
  setMessageReaction: (reaction: string, messageID: string, callback?: (err?: Error) => void, forceCustomReaction?: boolean) => Promise<void>,
396
396
  setMessageReactionMqtt: (reaction: string, messageID: string, threadID: string, callback?: (err?: Error) => void) => Promise<void>,
397
397
  setOptions: (options: Partial<IFCAU_Options>) => void,
398
+ setEditOptions: (opts: EditOptions) => void,
399
+ setBackoffOptions: (opts: { base?: number; max?: number; factor?: number; jitter?: number }) => void,
400
+ enableLazyPreflight: (enable?: boolean) => void,
401
+ getHealthMetrics: () => any,
402
+ getMemoryMetrics: () => { pendingEdits: number; pendingEditsDropped: number; pendingEditsExpired: number; outboundQueueDepth: number; groupQueueDroppedMessages: number; memoryGuardRuns: number; memoryGuardActions: number } | null,
398
403
  setTitle: (newTitle: string, threadID: string, callback?: (err?: Error) => void) => Promise<void>,
399
404
  setTheme: (themeID?: string, threadID: string, callback?: (err?: Error) => void) => Promise<void>,
400
405
  unsendMessage: (messageID: string, callback?: (err?: Error) => void) => Promise<void>,
package/index.js CHANGED
@@ -273,7 +273,20 @@ function buildAPI(globalOptions, html, jar) {
273
273
  getHealthMetrics: function(){ return ctx.health ? ctx.health.snapshot() : null; },
274
274
  enableLazyPreflight(enable=true){ ctx.globalOptions.disablePreflight = !enable; },
275
275
  setBackoffOptions(opts={}){ ctx.globalOptions.backoff = Object.assign(ctx.globalOptions.backoff||{}, opts); },
276
- setEditOptions(opts={}){ Object.assign(ctx.globalOptions.editSettings, opts); }
276
+ setEditOptions(opts={}){ Object.assign(ctx.globalOptions.editSettings, opts); },
277
+ getMemoryMetrics(){
278
+ if(!ctx.health) return null;
279
+ const snap = ctx.health.snapshot();
280
+ return {
281
+ pendingEdits: snap.pendingEdits,
282
+ pendingEditsDropped: snap.pendingEditsDropped,
283
+ pendingEditsExpired: snap.pendingEditsExpired,
284
+ outboundQueueDepth: snap.outboundQueueDepth,
285
+ groupQueueDroppedMessages: snap.groupQueueDroppedMessages,
286
+ memoryGuardRuns: snap.memoryGuardRuns,
287
+ memoryGuardActions: snap.memoryGuardActions
288
+ };
289
+ }
277
290
  };
278
291
  const defaultFuncs = utils.makeDefaults(html, i_userID || userID, ctx);
279
292
  require("fs")
@@ -315,7 +328,7 @@ function buildAPI(globalOptions, html, jar) {
315
328
  }, 1000 * 60 * 60 * 24);
316
329
  // === Group Queue (No Cooldown, Sequential per group) ===
317
330
  (function initGroupQueue(){
318
- const groupQueues = new Map(); // threadID -> { q: [], sending: false }
331
+ const groupQueues = new Map(); // threadID -> { q: [], sending: false, lastActive: number }
319
332
  const isGroupThread = (tid) => typeof tid === 'string' && tid.length >= 15; // simple heuristic
320
333
  const DIRECT_FN = api.sendMessage; // original
321
334
 
@@ -325,6 +338,8 @@ function buildAPI(globalOptions, html, jar) {
325
338
  api.setGroupQueueCapacity = function(n){ globalOptions.groupQueueMax = n; };
326
339
  api.enableGroupQueue(true);
327
340
  api.setGroupQueueCapacity(100); // allow up to 100 pending per group
341
+ // New: group queue retention policy
342
+ globalOptions.groupQueueIdleMs = 30*60*1000; // 30m idle purge
328
343
 
329
344
  api._sendMessageDirect = DIRECT_FN;
330
345
  api.sendMessage = function(message, threadID, cb){
@@ -332,10 +347,12 @@ function buildAPI(globalOptions, html, jar) {
332
347
  return api._sendMessageDirect(message, threadID, cb);
333
348
  }
334
349
  let entry = groupQueues.get(threadID);
335
- if(!entry){ entry = { q: [], sending: false }; groupQueues.set(threadID, entry); }
350
+ if(!entry){ entry = { q: [], sending: false, lastActive: Date.now() }; groupQueues.set(threadID, entry); }
351
+ entry.lastActive = Date.now();
336
352
  if(entry.q.length >= (globalOptions.groupQueueMax||100)) {
337
353
  // drop oldest (keep newest) to avoid unbounded growth
338
354
  entry.q.shift();
355
+ if(ctx.health) ctx.health.recordGroupQueuePrune(0,0,1);
339
356
  }
340
357
  entry.q.push({ message, threadID, cb });
341
358
  processQueue(threadID, entry);
@@ -364,6 +381,33 @@ function buildAPI(globalOptions, html, jar) {
364
381
  }
365
382
  entry.sending = false;
366
383
  };
384
+
385
+ // Memory guard sweeper (lightweight)
386
+ if(!globalOptions._groupQueueSweeper){
387
+ globalOptions._groupQueueSweeper = setInterval(()=>{
388
+ const now = Date.now();
389
+ let prunedThreads = 0; let expiredQueues = 0; let dropped = 0; let actions = 0;
390
+ for(const [tid, entry] of groupQueues.entries()){
391
+ // Idle purge
392
+ if(now - entry.lastActive > (globalOptions.groupQueueIdleMs||1800000) && !entry.sending){
393
+ if(entry.q.length){ dropped += entry.q.length; }
394
+ groupQueues.delete(tid); expiredQueues++; actions++;
395
+ continue;
396
+ }
397
+ // Hard cap queue length (just in case capacity changed lower)
398
+ const cap = globalOptions.groupQueueMax||100;
399
+ if(entry.q.length > cap){
400
+ const overflow = entry.q.length - cap;
401
+ entry.q.splice(0, overflow); // drop oldest overflow
402
+ dropped += overflow; actions++;
403
+ }
404
+ }
405
+ if((prunedThreads||expiredQueues||dropped) && ctx.health){
406
+ ctx.health.recordGroupQueuePrune(prunedThreads, expiredQueues, dropped);
407
+ ctx.health.recordMemoryGuardRun(actions);
408
+ }
409
+ }, 5*60*1000); // every 5 minutes
410
+ }
367
411
  })();
368
412
  // === End Group Queue ===
369
413
  return {
@@ -384,7 +428,8 @@ function loginHelper(appState, email, password, globalOptions, callback, prCallb
384
428
  return callback(new Error(`Login Safety Check Failed: ${safetyCheck.reason}`));
385
429
  }
386
430
 
387
- // Apply safe user agent from safety module
431
+ // Establish continuity user agent ONCE (credential/appstate phase)
432
+ if(!globalSafety._fixedUA){ globalSafety.setFixedUserAgent(globalSafety.getSafeUserAgent()); }
388
433
  globalOptions.userAgent = globalSafety.getSafeUserAgent();
389
434
 
390
435
  if (appState) {
@@ -404,7 +449,7 @@ function loginHelper(appState, email, password, globalOptions, callback, prCallb
404
449
  jar.setCookie(str, "http://" + c.domain);
405
450
  });
406
451
 
407
- // Apply safety headers and no delays for maximum safety
452
+ // Apply safety headers with continuity UA
408
453
  mainPromise = utils.get('https://www.facebook.com/', jar, null,
409
454
  globalSafety.applySafeRequestOptions(globalOptions), { noRef: true })
410
455
  .then(utils.saveCookies(jar));
@@ -419,7 +464,7 @@ function loginHelper(appState, email, password, globalOptions, callback, prCallb
419
464
  const reg = /<meta http-equiv="refresh" content="0;url=([^"]+)[^>]+>/;
420
465
  const redirect = reg.exec(res.body);
421
466
  if (redirect && redirect[1]) {
422
- return utils.get(redirect[1], jar, null, globalOptions).then(utils.saveCookies(jar));
467
+ return utils.get(redirect[1], jar, null, globalSafety.applySafeRequestOptions(globalOptions)).then(utils.saveCookies(jar));
423
468
  }
424
469
  return res;
425
470
  }
@@ -428,11 +473,7 @@ function loginHelper(appState, email, password, globalOptions, callback, prCallb
428
473
  mainPromise = mainPromise
429
474
  .then(handleRedirect)
430
475
  .then(res => {
431
- const mobileAgentRegex = /MPageLoadClientMetrics/gs;
432
- if (!mobileAgentRegex.test(res.body)) {
433
- globalOptions.userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36";
434
- return utils.get('https://www.facebook.com/', jar, null, globalOptions, { noRef: true }).then(utils.saveCookies(jar));
435
- }
476
+ // Remove UA override logic to maintain continuity (previous mobileAgentRegex swap)
436
477
  return res;
437
478
  })
438
479
  .then(handleRedirect)
@@ -464,6 +505,22 @@ function loginHelper(appState, email, password, globalOptions, callback, prCallb
464
505
  logger('✅ Session authenticated successfully', 'info');
465
506
  // Initialize safety monitoring
466
507
  globalSafety.startMonitoring(ctx, api);
508
+ // Schedule mid-session lightweight token poke (~ every 6h ±40m) to keep cookies warm
509
+ if(!globalOptions._lightRefreshTimer){
510
+ const scheduleLight = () => {
511
+ const base = 6 * 60 * 60 * 1000; // 6h
512
+ const jitter = (Math.random()*80 - 40) * 60 * 1000; // ±40m
513
+ globalOptions._lightRefreshTimer = setTimeout(async () => {
514
+ try {
515
+ if(api && typeof api.refreshFb_dtsg === 'function'){
516
+ await api.refreshFb_dtsg().catch(()=>{});
517
+ }
518
+ } catch(_) {}
519
+ scheduleLight();
520
+ }, base + jitter);
521
+ };
522
+ scheduleLight();
523
+ }
467
524
  // Post-login identity banner
468
525
  try {
469
526
  const uid = api.getCurrentUserID && api.getCurrentUserID();
@@ -1,5 +1,5 @@
1
1
  "use strict";
2
- // Lightweight health & metrics tracker for Nexus-FCA (extended Stage 2)
2
+ // Lightweight health & metrics tracker for Nexus-FCA (extended Stage 2 + Memory Guard)
3
3
  class HealthMetrics {
4
4
  constructor() {
5
5
  const now = Date.now();
@@ -26,6 +26,14 @@ class HealthMetrics {
26
26
  this.p95AckLatencyMs = null;
27
27
  this.editResends = 0;
28
28
  this.editFailed = 0;
29
+ // Memory / queue guard metrics (Stage 3)
30
+ this.memoryGuardRuns = 0;
31
+ this.memoryGuardLastRun = 0;
32
+ this.memoryGuardActions = 0;
33
+ this.groupQueuePrunedThreads = 0;
34
+ this.groupQueueExpiredQueues = 0;
35
+ this.groupQueueDroppedMessages = 0;
36
+ this.pendingEditSweeps = 0;
29
37
  }
30
38
  onConnect() { this.lastConnectTs = Date.now(); this.consecutiveFailures = 0; }
31
39
  onDisconnect() { this.lastDisconnectTs = Date.now(); }
@@ -64,9 +72,16 @@ class HealthMetrics {
64
72
  for(const [mid, val] of this.pendingEditMap.entries()){
65
73
  if(now - val.ts > ttlMs){ this.pendingEditMap.delete(mid); expired++; }
66
74
  }
67
- if(expired) { this.incPendingEditExpired(expired); }
75
+ if(expired) { this.incPendingEditExpired(expired); this.pendingEditSweeps++; }
68
76
  this.pendingEdits = this.pendingEditMap.size;
69
77
  }
78
+ // Memory guard helpers
79
+ recordMemoryGuardRun(actions=0){ this.memoryGuardRuns++; this.memoryGuardLastRun = Date.now(); this.memoryGuardActions += actions; }
80
+ recordGroupQueuePrune(threads, expiredQueues, droppedMsgs){
81
+ if(threads) this.groupQueuePrunedThreads += threads;
82
+ if(expiredQueues) this.groupQueueExpiredQueues += expiredQueues;
83
+ if(droppedMsgs) this.groupQueueDroppedMessages += droppedMsgs;
84
+ }
70
85
  snapshot(){
71
86
  const now = Date.now();
72
87
  const idleMs = now - (this.lastMessageTs || this.lastConnectTs || now);
@@ -92,6 +107,13 @@ class HealthMetrics {
92
107
  editFailed: this.editFailed,
93
108
  outboundQueueDepth: this.outboundQueueDepth,
94
109
  outboundQueueDropped: this.outboundQueueDropped,
110
+ memoryGuardRuns: this.memoryGuardRuns,
111
+ memoryGuardLastRun: this.memoryGuardLastRun,
112
+ memoryGuardActions: this.memoryGuardActions,
113
+ groupQueuePrunedThreads: this.groupQueuePrunedThreads,
114
+ groupQueueExpiredQueues: this.groupQueueExpiredQueues,
115
+ groupQueueDroppedMessages: this.groupQueueDroppedMessages,
116
+ pendingEditSweeps: this.pendingEditSweeps,
95
117
  healthy: this.isHealthy(idleMs)
96
118
  };
97
119
  }
@@ -18,6 +18,8 @@ class FacebookSafety {
18
18
  enableSafeDelays: true,
19
19
  bypassRegionLock: true,
20
20
  ultraLowBanMode: true,
21
+ // NEW: ensure a single stable UA across entire session lifecycle
22
+ enableUAContinuity: true,
21
23
  ...options
22
24
  };
23
25
 
@@ -29,6 +31,8 @@ class FacebookSafety {
29
31
  'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
30
32
  'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36'
31
33
  ];
34
+ // NEW: fixed user agent anchor (set once per session)
35
+ this._fixedUA = null;
32
36
 
33
37
  this.safeDomains = [
34
38
  'https://www.facebook.com',
@@ -88,9 +92,23 @@ class FacebookSafety {
88
92
  }
89
93
 
90
94
  /**
91
- * Get safe user agent that reduces detection risk
95
+ * Allow external code to explicitly anchor the session UA (e.g. carry over from credential phase)
96
+ */
97
+ setFixedUserAgent(ua){
98
+ if(!ua || typeof ua !== 'string') return;
99
+ this._fixedUA = ua;
100
+ }
101
+
102
+ /**
103
+ * Get safe user agent that reduces detection risk (now continuity‑aware)
92
104
  */
93
105
  getSafeUserAgent() {
106
+ if (this.options.enableUAContinuity) {
107
+ if (this._fixedUA) return this._fixedUA;
108
+ // choose once then cache
109
+ this._fixedUA = this.safeUserAgents[Math.floor(Math.random() * this.safeUserAgents.length)];
110
+ return this._fixedUA;
111
+ }
94
112
  return this.safeUserAgents[Math.floor(Math.random() * this.safeUserAgents.length)];
95
113
  }
96
114
 
@@ -146,7 +164,7 @@ class FacebookSafety {
146
164
  maxRedirects: 5
147
165
  };
148
166
 
149
- // Apply safe user agent
167
+ // Apply stable user agent (continuity aware)
150
168
  safeOptions.userAgent = this.getSafeUserAgent();
151
169
 
152
170
  return safeOptions;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nexus-fca",
3
- "version": "2.1.5",
3
+ "version": "2.1.7",
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": {