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 +32 -0
- package/README.md +81 -34
- package/index.d.ts +5 -0
- package/index.js +68 -11
- package/lib/health/HealthMetrics.js +24 -2
- package/lib/safety/FacebookSafety.js +20 -2
- package/package.json +1 -1
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.
|
|
1
|
+
# Nexus-FCA v2.1.7
|
|
2
2
|
|
|
3
|
-
<!-- 2.1.
|
|
4
|
-
> New in 2.1.
|
|
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
|
|
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 (
|
|
18
|
-
-
|
|
19
|
-
- 🧠 Smart session validation (multi-endpoint retry, reduced false logouts)
|
|
20
|
-
-
|
|
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
|
-
## 🚀
|
|
27
|
-
|
|
28
|
-
|
|
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 (
|
|
106
|
+
## 🛡️ Safety Layer (Updated)
|
|
65
107
|
| Feature | Benefit |
|
|
66
108
|
|---------|---------|
|
|
67
|
-
|
|
|
68
|
-
|
|
|
69
|
-
|
|
|
70
|
-
|
|
|
71
|
-
|
|
|
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
|
-
-
|
|
81
|
-
-
|
|
82
|
-
-
|
|
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
|
|
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.
|
|
95
|
-
2.
|
|
96
|
-
3.
|
|
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.
|
|
223
|
+
## 🔁 Updating from 2.0.x → 2.1.x
|
|
178
224
|
| Change | Action |
|
|
179
225
|
|--------|--------|
|
|
180
|
-
|
|
|
181
|
-
|
|
|
182
|
-
|
|
|
183
|
-
|
|
|
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
|
-
//
|
|
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
|
|
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
|
-
|
|
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
|
-
*
|
|
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
|
|
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.
|
|
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": {
|