nexus-fca 2.1.8 → 3.0.0
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 +12 -193
- package/README.md +112 -170
- package/docs/account-safety.md +22 -1
- package/index.js +14 -15
- package/lib/health/HealthMetrics.js +12 -0
- package/lib/mqtt/MqttDiagnostics.js +20 -0
- package/lib/safety/FacebookSafety.js +177 -12
- package/lib/safety/FacebookSafetyManager.js +3 -2
- package/package.json +2 -2
- package/src/listenMqtt.js +26 -7
- package/src/markAsDelivered.js +54 -21
package/CHANGELOG.md
CHANGED
|
@@ -1,202 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
|
+
## [3.0.0] - 2025-09-11 - Advanced Core Release
|
|
2
3
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
- Safe session refresh interval widened to 3–5h (adaptive: 1–1.5h when risk=high) to reduce token churn and mitigate premature cookie expiry heuristics.
|
|
6
|
-
|
|
7
|
-
### Rationale
|
|
8
|
-
- Frequent refreshes can accelerate cookie rotation patterns → earlier invalidation.
|
|
9
|
-
- Longer, randomized window maintains stealth while heartbeats, ghost detection, and adaptive reconnect still ensure liveness.
|
|
10
|
-
|
|
11
|
-
### Notes
|
|
12
|
-
- Lightweight mid‑session fb_dtsg poke (≈6h ±40m) retained; heavy refresh cadence now sparser.
|
|
4
|
+
### Overview
|
|
5
|
+
Version 3.0.0 represents a pivotal milestone in the evolution of the Nexus-fCA platform, transitioning from the iterative stabilization efforts of the 2.1.x series to a unified, production-ready foundation. This release is the culmination of extensive engineering, rigorous validation, and a commitment to delivering enterprise-grade reliability. The legacy diagnostics harness has been formally retired, replaced by a standardized internal instrumentation framework that streamlines monitoring, troubleshooting, and ongoing maintenance. These enhancements collectively reinforce the platform’s robustness, scalability, and operational transparency.
|
|
13
6
|
|
|
14
|
-
---
|
|
15
|
-
|
|
16
|
-
## [2.1.7] - 2025-09-01 - Session Stability Patch
|
|
17
7
|
### Added
|
|
18
|
-
-
|
|
19
|
-
-
|
|
20
|
-
-
|
|
8
|
+
- **Delivery Receipt Health Metrics:** Introduced a comprehensive suite of metrics for delivery receipts, encompassing attempt counts, success rates, failure rates, timeout tracking, and adaptive disablement flags. These metrics provide granular visibility into delivery performance and facilitate proactive issue resolution.
|
|
9
|
+
- **Advanced Timeout Suppression Logic:** Implemented sophisticated suppression mechanisms for repeated delivery timeouts. This ensures consistent reply performance, mitigates the risk of systemic degradation, and enhances overall service reliability.
|
|
10
|
+
- **Internal Instrumentation Standardization:** All diagnostic and monitoring capabilities have been consolidated under a unified instrumentation framework, simplifying maintenance and enabling more effective root cause analysis.
|
|
21
11
|
|
|
22
12
|
### Changed
|
|
23
|
-
-
|
|
24
|
-
-
|
|
25
|
-
|
|
26
|
-
### Improved
|
|
27
|
-
- Extended resilience against 20–22h cookie invalidation observed with prior dual-phase UA pattern.
|
|
28
|
-
- Reduced unnecessary full refresh churn while preserving stealth (`safeRefresh` + light poke coexist).
|
|
29
|
-
|
|
30
|
-
---
|
|
31
|
-
|
|
32
|
-
## [2.1.6] - 2025-08-31 - Memory Guard & Queue Sweeping
|
|
33
|
-
### Added
|
|
34
|
-
- Central lightweight memory guard sweeps: group queue pruning (idle >30m, overflow trim) and pendingEdits TTL sweeper (every 4m).
|
|
35
|
-
- Health metrics extended: memoryGuardRuns, memoryGuardActions, groupQueueDroppedMessages, groupQueueExpiredQueues, groupQueuePrunedThreads, pendingEditSweeps.
|
|
36
|
-
- API: `api.getMemoryMetrics()` returns focused memory-related counters.
|
|
37
|
-
- Typings updated (`EditOptions`, new API methods) in `index.d.ts`.
|
|
38
|
-
|
|
39
|
-
### Improved
|
|
40
|
-
- Group queue now tracks `lastActive` and enforces idle purge + overflow protection with metrics.
|
|
41
|
-
- Pending edits TTL enforcement separated from resend watchdog for deterministic expiry.
|
|
42
|
-
|
|
43
|
-
### Notes
|
|
44
|
-
- All guards are low-frequency, low-impact; no change to delivery reliability or safety – only prevention of unbounded growth.
|
|
45
|
-
|
|
46
|
-
---
|
|
47
|
-
|
|
48
|
-
## [2.1.5] - 2025-08-28 - PendingEdits & ACK Metrics
|
|
49
|
-
### Added
|
|
50
|
-
- PendingEdits buffer with cap (default 200) + TTL (5m) + resend attempts (2) + ACK timeout (12s).
|
|
51
|
-
- Automatic edit resend watchdog with safe limits and metrics (editResends, editFailed, pendingEditsDropped, pendingEditsExpired).
|
|
52
|
-
- API: `api.setEditOptions({ maxPendingEdits, editTTLms, ackTimeoutMs, maxResendAttempts })`.
|
|
53
|
-
- Edit ACK integration: pending edit removed on ACK receipt.
|
|
54
|
-
- Health metrics: p95AckLatencyMs, editResends, editFailed.
|
|
55
|
-
|
|
56
|
-
### Improved
|
|
57
|
-
- Safer edit pipeline: prevents uncontrolled retries, bounds memory, tracks expirations.
|
|
58
|
-
- Enhanced HealthMetrics with percentile latency sample retention (50-sample window).
|
|
13
|
+
- **Documentation and Package Description:** The package description and README have undergone a comprehensive revision to align with professional standards and accurately reflect the platform’s positioning, capabilities, and intended use cases.
|
|
14
|
+
- **Semantic Versioning:** The major version increment to 3.0.0 signifies the platform’s maturity, stability, and readiness for mission-critical deployments. Importantly, there are no breaking changes to public APIs compared to the 2.1.x series, ensuring continuity for existing integrations.
|
|
59
15
|
|
|
60
16
|
### Notes
|
|
61
|
-
-
|
|
62
|
-
|
|
63
|
-
---
|
|
64
|
-
|
|
65
|
-
## [2.1.4] - Adaptive Backoff & Core Metrics
|
|
66
|
-
### Added
|
|
67
|
-
- Adaptive reconnect backoff with jitter (caps at 5 minutes) for safer, stealthier recovery loops.
|
|
68
|
-
- Lazy preflight session validation (skips heavy validation if a recent successful connect occurred) toggle via `api.enableLazyPreflight()`.
|
|
69
|
-
- Health metrics collector (uptime, idle time, reconnect counts, failures, message/ack counters, synthetic keepalives) accessible with `api.getHealthMetrics()` and included in `api.healthCheck()`.
|
|
70
|
-
- Randomized synthetic keepalive interval (55-75s) to reduce detection patterns.
|
|
71
|
-
- Backoff configuration hook: `api.setBackoffOptions()`.
|
|
72
|
-
|
|
73
|
-
### Improved
|
|
74
|
-
- Reduced noisy session validation on every `listenMqtt` invocation unless needed.
|
|
75
|
-
- More structured error classification feeding metrics (`session_invalid`, `timeout_no_t_ms`, `mqtt_error`, `message_parse`, `not_logged_in`).
|
|
76
|
-
|
|
77
|
-
### Planned (Next)
|
|
78
|
-
- ACK tracking refinement & resend logic.
|
|
79
|
-
- Pending edits buffer with TTL and cap.
|
|
80
|
-
- Durable outbound queue & health exporter.
|
|
81
|
-
|
|
82
|
-
---
|
|
83
|
-
|
|
84
|
-
## [2.1.2] - CONTINUOUS IDLE RECOVERY
|
|
85
|
-
### Added
|
|
86
|
-
- Soft-stale probing at 2 minutes idle (ping + conditional forced reconnect if no events within 5-8s)
|
|
87
|
-
- Wrapper around `listenMqtt` to automatically feed events into safety heartbeat (`recordEvent`) for precise idle detection
|
|
88
|
-
- Ghost connection detection (10m silent but socket connected triggers forced reconnect after probe)
|
|
89
|
-
- Periodic connection recycle every ~6h ±30m to prevent long-lived silent degradation
|
|
90
|
-
- Force reconnect API: `globalSafety.forceReconnect(tag)`
|
|
91
|
-
|
|
92
|
-
### Improved
|
|
93
|
-
- Faster recovery from silent idle states (previously required >5 min or external trigger)
|
|
94
|
-
- Reduced chance of appearing online but unresponsive after short inactivity
|
|
95
|
-
- Added keepalive foreground_state publishes each heartbeat
|
|
96
|
-
|
|
97
|
-
---
|
|
98
|
-
|
|
99
|
-
## [2.1.1] - 2025-08-27 - ADVANCED SESSION STABILITY
|
|
100
|
-
### 🛠 Added
|
|
101
|
-
- Adaptive safe session refresh interval (dynamic based on risk level)
|
|
102
|
-
- Heartbeat + watchdog timers to detect stale MQTT connections early
|
|
103
|
-
- Progressive backoff with jitter for MQTT reconnect attempts
|
|
104
|
-
- Layered post-refresh health checks (1s / 10s / 30s) to catch silent drops
|
|
105
|
-
- Abortable refresh with timeout safeguard (25s) to prevent hangs
|
|
106
|
-
- Automatic reconnection trigger if no events within thresholds (2m soft, 15m hard)
|
|
107
|
-
- `destroy()` method to cleanup timers/listeners (prevents memory leaks)
|
|
108
|
-
|
|
109
|
-
### 🔄 Changed
|
|
110
|
-
- Safe refresh now records in‑flight ID and supersedes outdated checks
|
|
111
|
-
- Reconnect logic centralized in `_reconnectMqttWithBackoff`
|
|
112
|
-
|
|
113
|
-
### ✅ Improved
|
|
114
|
-
- Stability after long runtimes / multiple token refresh cycles
|
|
115
|
-
- Reduced risk of listener not resuming after refresh
|
|
116
|
-
|
|
117
|
-
---
|
|
118
|
-
|
|
119
|
-
## [2.1.0] - 2025-08-20 - SESSION RELIABILITY & PROMISE LOGIN
|
|
120
|
-
### 🚀 Highlights
|
|
121
|
-
Stability-focused release improving long‑running bot sessions, reducing false `not_logged_in` events, and modernizing the login flow.
|
|
122
|
-
|
|
123
|
-
### Added
|
|
124
|
-
- ✅ Promise support for `login()` (dual callback + Promise API)
|
|
125
|
-
- 🆔 Persistent device fingerprint (saved to `persistent-device.json`) to reduce checkpoint / lock frequency
|
|
126
|
-
- 🛡️ New `validateSession()` multi-endpoint heuristic (lightweight, resilient preflight)
|
|
127
|
-
- ⚙️ New global option: `disablePreflight` (skip session validation if desired)
|
|
128
|
-
- 🔄 Structured error types from `parseAndCheckLogin` (`login_redirect`, `html_login_page`, `network_redirect`, etc.)
|
|
129
|
-
- 🧪 Example: `examples/echo-test.js` (Promise style, supports env credentials or appstate)
|
|
130
|
-
|
|
131
|
-
### Changed
|
|
132
|
-
- 🔁 `listenMqtt` now performs silent initial validation; only emits `not_logged_in` after a confirmatory retry
|
|
133
|
-
- 🧠 `parseAndCheckLogin` now robustly handles 3xx chains & HTML login fallback pages
|
|
134
|
-
- 🔐 Default behavior: device identity no longer rotates unless explicitly overridden
|
|
135
|
-
- 🧩 Refactored internal cookie & session utilities (centralized in `utils.js`)
|
|
136
|
-
- 📄 Rewritten documentation (README, DOCS, CHANGELOG) for concise modern onboarding
|
|
137
|
-
|
|
138
|
-
### Fixed
|
|
139
|
-
- ❌ Spurious `parseAndCheckLogin got status code: 302` fatal errors now classified & recovered when possible
|
|
140
|
-
- 💤 False negatives from legacy preflight removed (no premature `not_logged_in` during transient redirects)
|
|
141
|
-
- 🔄 Edge reconnect loop where MQTT closed before revalidation completed
|
|
142
|
-
|
|
143
|
-
### Migration Notes (2.0.x → 2.1.0)
|
|
144
|
-
- Existing code using callbacks continues to work. To use Promises: `const api = await login(opts);`
|
|
145
|
-
- If you previously depended on device rotation, disable persistent device via option (see README) or delete `persistent-device.json`.
|
|
146
|
-
- Remove any custom preflight hacks; built‑in `validateSession` supersedes them.
|
|
147
|
-
|
|
148
|
-
### Developer / Internal
|
|
149
|
-
- Centralized session validation pipeline
|
|
150
|
-
- Added granular error classification to aid future retry/backoff strategies
|
|
151
|
-
- Prepared foundation for upcoming metrics hooks in 2.2.x
|
|
152
|
-
|
|
153
|
-
---
|
|
154
|
-
|
|
155
|
-
## [2.0.5] - 2025-07-29 - FULLY INTEGRATED NPM EDITION
|
|
156
|
-
### 🎯 MAJOR: Full NPM Integration
|
|
157
|
-
- **✅ FULLY INTEGRATED**: Entire Nexus Login System now embedded directly in main `index.js`
|
|
158
|
-
- **📦 NPM COMPATIBLE**: Works perfectly when installed via `npm install nexus-fca` - no external folder dependencies
|
|
159
|
-
- **⚡ ZERO CONFIG**: Everything works out of the box - no separate folder setup required
|
|
160
|
-
- **🔄 SEAMLESS MIGRATION**: Existing code continues to work, new code benefits from integration
|
|
161
|
-
|
|
162
|
-
### Added
|
|
163
|
-
- 🎯 **Direct exports**: `nexusLogin` and `IntegratedNexusLoginSystem` available directly from main package
|
|
164
|
-
- 🧪 **Updated test files**: All test scripts now use integrated system (`require('nexus-fca')` instead of `./nexloginsystem`)
|
|
165
|
-
- 📖 **New documentation**: `npm-integration-guide.md` with complete NPM usage guide
|
|
166
|
-
- 🛠️ **NPM scripts**: Added `test:login`, `test:simple`, `test:2fa`, `test:all` for easy testing
|
|
167
|
-
- 📦 **Enhanced package.json**: Updated keywords, description, and version for NPM integration
|
|
168
|
-
|
|
169
|
-
### Changed
|
|
170
|
-
- 🏗️ **Architecture**: Moved entire Nexus Login System from external folder into main index.js (lines 372-860+)
|
|
171
|
-
- 📝 **Documentation**: Updated README.md to reflect NPM installation and integrated usage
|
|
172
|
-
- 🔧 **Test files**: Fixed all test imports to use main package instead of external folder
|
|
173
|
-
- 📦 **Package info**: Updated to v2.0.5 with new description highlighting NPM integration
|
|
174
|
-
|
|
175
|
-
### Fixed
|
|
176
|
-
- ❌ **NPM module errors**: Eliminated "Cannot find module './nexloginsystem'" when using as npm package
|
|
177
|
-
- 🔗 **Import paths**: All test files and examples now use correct import paths for npm usage
|
|
178
|
-
- 🎯 **Distribution**: Package now works identically whether used locally or installed via npm
|
|
179
|
-
|
|
180
|
-
## [2.0.4] - 2025-07-29
|
|
181
|
-
### Fixed
|
|
182
|
-
- 🐛 **Missing nexloginsystem folder**: Added `nexloginsystem/` to npm package files array to fix "Cannot find module './nexloginsystem'" error
|
|
183
|
-
- 🔄 **Legacy login fallback**: Added automatic fallback to appstate-only login when Nexus Login System is not available
|
|
184
|
-
- 🛡️ **Backward compatibility**: Enhanced compatibility for users using nexus-fca as npm dependency without full login system
|
|
185
|
-
|
|
186
|
-
### Changed
|
|
187
|
-
- Updated package.json to include nexloginsystem folder in published package
|
|
188
|
-
- Enhanced error handling with graceful fallback mechanisms
|
|
189
|
-
|
|
190
|
-
## [2.0.1] - 2025-07-28
|
|
191
|
-
### Added
|
|
192
|
-
- 🚀 **Nexus Login System**: Advanced, safe, and automatic Facebook login system added under `/nexloginsystem`.
|
|
193
|
-
- 🔐 **Appstate auto-generation**: Login with username/password/2FA, auto-save appstate, and seamless bot start.
|
|
194
|
-
- 🛡️ **Maximum safety**: Human-like device simulation, TOTP/2FA support, and advanced error handling.
|
|
195
|
-
- 📦 **Auto-backup & validation**: Appstate backup, validation, and lifecycle management.
|
|
196
|
-
- 📚 **Full documentation**: Usage, API, and safety docs in `/nexloginsystem/README.md`.
|
|
197
|
-
|
|
198
|
-
### Changed
|
|
199
|
-
- Updated main `README.md` with Nexus Login System quick start and features.
|
|
17
|
+
- **Upgrade Path:** Transitioning from 2.1.x to 3.0.0 is seamless. All documented interfaces remain unchanged, and the upgrade process requires no code modifications for existing consumers. The major version bump reflects strategic lifecycle consolidation and a renewed focus on long-term maintainability, rather than disruptive changes.
|
|
18
|
+
- **Lifecycle Consolidation:** This release unifies prior stabilization efforts, setting a new baseline for future enhancements and support. Users can expect ongoing improvements in reliability, observability, and operational efficiency.
|
|
200
19
|
|
|
201
|
-
###
|
|
202
|
-
|
|
20
|
+
### Historical Logs
|
|
21
|
+
Version logs for the 2.1.x series have been archived and are available upon request for reference and audit purposes.
|
package/README.md
CHANGED
|
@@ -1,77 +1,30 @@
|
|
|
1
|
-
# Nexus-FCA v2.1.7
|
|
2
|
-
|
|
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()`.
|
|
11
|
-
|
|
12
1
|
<p align="center">
|
|
13
|
-
|
|
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>
|
|
2
|
+
<img src="https://i.ibb.co/Sk61FGg/Dragon-Fruit-1.jpg" alt="Nexus-FCA" width="520" />
|
|
15
3
|
</p>
|
|
16
4
|
|
|
17
|
-
|
|
5
|
+
# Nexus-FCA v3.0.0 – Advanced Core Release
|
|
18
6
|
|
|
19
|
-
|
|
20
|
-
## ✨ Highlights (Core Pillars)
|
|
21
|
-
- 🔐 Integrated secure login system (username/password + TOTP 2FA) → auto appstate
|
|
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()`)
|
|
27
|
-
- 🧾 Type definitions (`index.d.ts`) & modern Promise / callback API
|
|
28
|
-
- 🧩 Modular architecture (safety, performance, error, mqtt managers)
|
|
7
|
+
Modern, safe, production‑ready Messenger (Facebook Chat) API layer with integrated secure login (credentials + 2FA), adaptive session & connection resilience, delivery reliability safeguards, memory protection, and rich runtime metrics. Promise + callback compatible, TypeScript typed, minimal friction.
|
|
29
8
|
|
|
30
9
|
---
|
|
31
|
-
##
|
|
32
|
-
|
|
|
33
|
-
|
|
34
|
-
|
|
|
35
|
-
|
|
|
36
|
-
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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.
|
|
10
|
+
## ✅ Core Value
|
|
11
|
+
| Pillar | What You Get |
|
|
12
|
+
|--------|--------------|
|
|
13
|
+
| Integrated Secure Login | Username / Password / TOTP 2FA → stable appstate generation & reuse |
|
|
14
|
+
| Session Resilience | Anchored User‑Agent continuity, adaptive safe refresh, lightweight token poke, periodic recycle |
|
|
15
|
+
| Connection Stability | Adaptive MQTT backoff, idle & ghost detection, layered post-refresh health probes, synthetic keepalives |
|
|
16
|
+
| Delivery Reliability | Multi-path message send fallback (MQTT → HTTP → direct) + delivery receipt timeout suppression |
|
|
17
|
+
| Memory Guard | Bounded queues, edit TTL sweeps, controlled resend limits |
|
|
18
|
+
| Observability | Health + memory + delivery metrics (`api.getHealthMetrics()`, `api.getMemoryMetrics()`) |
|
|
19
|
+
| Edit Safety | Pending edit buffer, ACK watchdog, p95 ACK latency tracking |
|
|
20
|
+
| Type Definitions | First-class `index.d.ts` with modern Promise signatures |
|
|
43
21
|
|
|
44
22
|
---
|
|
45
|
-
##
|
|
46
|
-
|
|
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
|
-
```
|
|
23
|
+
## 🔄 What Changed in 3.0.0
|
|
24
|
+
Major version signals maturity & consolidation. No breaking public API changes versus late 2.1.x – upgrade is drop‑in. Temporary diagnostic harness removed; internal instrumentation formalized. Delivery receipt timeouts now intelligently retried & optionally auto-suppressed to protect outbound responsiveness.
|
|
53
25
|
|
|
54
26
|
---
|
|
55
|
-
##
|
|
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);
|
|
63
|
-
```
|
|
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
|
-
|
|
73
|
-
---
|
|
74
|
-
## ⚡ Quick Start (Appstate)
|
|
27
|
+
## 🚀 Quick Start (Appstate Preferred)
|
|
75
28
|
```js
|
|
76
29
|
const login = require('nexus-fca');
|
|
77
30
|
|
|
@@ -85,15 +38,14 @@ const login = require('nexus-fca');
|
|
|
85
38
|
})();
|
|
86
39
|
```
|
|
87
40
|
|
|
88
|
-
|
|
41
|
+
### Credentials + 2FA Flow
|
|
89
42
|
```js
|
|
90
43
|
const login = require('nexus-fca');
|
|
91
|
-
|
|
92
44
|
(async () => {
|
|
93
45
|
const api = await login({
|
|
94
46
|
email: process.env.FB_EMAIL,
|
|
95
47
|
password: process.env.FB_PASS,
|
|
96
|
-
twofactor: process.env.FB_2FA_SECRET // optional
|
|
48
|
+
twofactor: process.env.FB_2FA_SECRET // optional TOTP secret
|
|
97
49
|
});
|
|
98
50
|
api.listen((err, msg) => {
|
|
99
51
|
if (err) return console.error(err);
|
|
@@ -103,142 +55,132 @@ const login = require('nexus-fca');
|
|
|
103
55
|
```
|
|
104
56
|
|
|
105
57
|
---
|
|
106
|
-
##
|
|
107
|
-
| Feature | Benefit |
|
|
108
|
-
|---------|---------|
|
|
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 |
|
|
117
|
-
|
|
118
|
-
Disable preflight if needed:
|
|
58
|
+
## 🧪 Key Runtime APIs
|
|
119
59
|
```js
|
|
120
|
-
|
|
60
|
+
api.setEditOptions({ maxPendingEdits, editTTLms, ackTimeoutMs, maxResendAttempts });
|
|
61
|
+
api.setBackoffOptions({ base, factor, max, jitter });
|
|
62
|
+
api.enableLazyPreflight(true); // Skip heavy validation if recent success
|
|
63
|
+
api.getHealthMetrics(); // uptime, reconnects, ack latency, delivery stats
|
|
64
|
+
api.getMemoryMetrics(); // queue sizes & guard counters
|
|
121
65
|
```
|
|
122
66
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
67
|
+
### Monitoring Snippet
|
|
68
|
+
```js
|
|
69
|
+
setInterval(() => {
|
|
70
|
+
const h = api.getHealthMetrics();
|
|
71
|
+
const m = api.getMemoryMetrics();
|
|
72
|
+
console.log('[HEALTH]', h?.status, 'acks', h?.ackCount, 'p95Ack', h?.p95AckLatencyMs);
|
|
73
|
+
console.log('[DELIVERY]', {
|
|
74
|
+
attempts: h?.deliveryAttempts,
|
|
75
|
+
success: h?.deliverySuccess,
|
|
76
|
+
failed: h?.deliveryFailed,
|
|
77
|
+
timeouts: h?.deliveryTimeouts,
|
|
78
|
+
disabledSince: h?.deliveryDisabledSince
|
|
79
|
+
});
|
|
80
|
+
console.log('[MEM]', m);
|
|
81
|
+
}, 60000);
|
|
135
82
|
```
|
|
136
|
-
Provide `appstate.json` or set `EMAIL` / `PASSWORD` env variables.
|
|
137
83
|
|
|
138
84
|
---
|
|
139
|
-
##
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
85
|
+
## 🛡️ Safety & Stability Architecture
|
|
86
|
+
| Layer | Mechanism | Purpose |
|
|
87
|
+
|-------|-----------|---------|
|
|
88
|
+
| UA Continuity | Single anchored fingerprint | Avoid heuristic expiry & drift |
|
|
89
|
+
| Adaptive Refresh | Risk-aware timing bands | Token longevity without bursts |
|
|
90
|
+
| Lightweight Poke | Subtle `fb_dtsg` renewal | Keeps session warm quietly |
|
|
91
|
+
| Collision Guard | 45m spacing window | Prevent clustered maintenance events |
|
|
92
|
+
| Idle / Ghost Probe | Timed silent detection | Force reconnect on stale sockets |
|
|
93
|
+
| Periodic Recycle | Randomized (~6h ±30m) | Pre-empt silent degradation |
|
|
94
|
+
| Backoff Strategy | Exponential + jitter | Graceful network recovery |
|
|
95
|
+
| Delivery Suppression | Disable after repeated timeouts | Preserve send latency |
|
|
143
96
|
|
|
144
|
-
|
|
97
|
+
Disable heavy preflight if embedding inside a framework already doing checks:
|
|
145
98
|
```js
|
|
146
|
-
|
|
147
|
-
new IntegratedNexusLoginSystem({ persistentDevice: true });
|
|
99
|
+
await login({ appState }, { disablePreflight: true });
|
|
148
100
|
```
|
|
149
101
|
|
|
150
102
|
---
|
|
151
|
-
##
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
```js
|
|
157
|
-
const login = require('nexus-fca');
|
|
158
|
-
(async () => {
|
|
159
|
-
const api = await login({ email: process.env.FB_EMAIL, password: process.env.FB_PASS, twofactor: process.env.FB_2FA });
|
|
160
|
-
const appState = api.getAppState();
|
|
161
|
-
require('fs').writeFileSync('./appstate.json', JSON.stringify(appState, null, 2));
|
|
162
|
-
console.log('Saved appstate.json');
|
|
163
|
-
})();
|
|
164
|
-
```
|
|
165
|
-
2. Configure GoatBot to use that `appstate.json` (no credential scraping needed).
|
|
166
|
-
3. Repeat only when session truly expires (persistent device reduces frequency).
|
|
167
|
-
|
|
168
|
-
### Option 2: Replace internal fb-chat-api
|
|
169
|
-
GoatBot has a local `fb-chat-api` folder. To leverage Nexus-FCA improvements globally:
|
|
170
|
-
1. Install Nexus-FCA inside GoatBot project:
|
|
171
|
-
```bash
|
|
172
|
-
npm install nexus-fca
|
|
173
|
-
```
|
|
174
|
-
2. Rename GoatBot’s original folder for backup:
|
|
175
|
-
```bash
|
|
176
|
-
mv fb-chat-api fb-chat-api.orig # (Windows: rename manually)
|
|
177
|
-
```
|
|
178
|
-
3. Create a shim folder `fb-chat-api/index.js` with:
|
|
179
|
-
```js
|
|
180
|
-
module.exports = require('nexus-fca');
|
|
181
|
-
```
|
|
182
|
-
4. Start GoatBot normally. All calls (`login`, `api.listen`, send methods) now use Nexus-FCA (Promise supported).
|
|
103
|
+
## 🛰️ MQTT Enhancements (Since 2.1.x)
|
|
104
|
+
- Adaptive reconnect curve (caps 5m)
|
|
105
|
+
- Layered post-refresh probes (1s / 10s / 30s)
|
|
106
|
+
- Synthetic randomized keepalives (55–75s)
|
|
107
|
+
- Structured error classification feeding metrics
|
|
183
108
|
|
|
184
|
-
|
|
185
|
-
|
|
109
|
+
---
|
|
110
|
+
## ✉️ Delivery Reliability
|
|
111
|
+
- Multi-path send fallback (MQTT publish → HTTP send → direct fallback)
|
|
112
|
+
- Per-attempt timeout & retry for message delivery receipts
|
|
113
|
+
- Automatic classification of transient timeouts (ETIMEDOUT / ECONNRESET / EAI_AGAIN)
|
|
114
|
+
- Adaptive suppression of delivery receipt calls when environment unstable (protects primary send throughput)
|
|
186
115
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
const login = require('nexus-fca');
|
|
195
|
-
const api = await login(loginData); // supports { appState } or { email, password, twofactor }
|
|
196
|
-
```
|
|
116
|
+
---
|
|
117
|
+
## 🧠 Long Session Best Practices
|
|
118
|
+
1. Prefer appstate reuse (minimal credential logins).
|
|
119
|
+
2. Preserve `persistent-device.json` (only delete if forced challenge).
|
|
120
|
+
3. Don’t manually rotate User-Agent – built-in continuity handles it.
|
|
121
|
+
4. Inspect metrics before forcing reconnect; let backoff work.
|
|
122
|
+
5. Keep dependencies updated; review CHANGELOG for operational notes.
|
|
197
123
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
124
|
+
---
|
|
125
|
+
## 🐐 Using with GoatBot V2 (Summary)
|
|
126
|
+
| Goal | Steps |
|
|
127
|
+
|------|-------|
|
|
128
|
+
| Generate appstate | Run credential login script → save `appstate.json` → configure GoatBot |
|
|
129
|
+
| Full replacement | Install `nexus-fca` → shim `fb-chat-api/index.js` exporting module |
|
|
130
|
+
| Direct require swap | Replace `require('fb-chat-api')` with `require('nexus-fca')` |
|
|
202
131
|
|
|
203
|
-
|
|
132
|
+
Minimal example:
|
|
204
133
|
```js
|
|
205
134
|
const login = require('nexus-fca');
|
|
206
135
|
(async () => {
|
|
207
136
|
const api = await login({ appState: require('./appstate.json') });
|
|
208
|
-
api.listen(
|
|
209
|
-
if (err) return console.error(
|
|
137
|
+
api.listen((err, event) => {
|
|
138
|
+
if (err) return console.error(err);
|
|
210
139
|
if (event.body === '!ping') api.sendMessage('pong', event.threadID);
|
|
211
140
|
});
|
|
212
141
|
})();
|
|
213
142
|
```
|
|
214
143
|
|
|
215
144
|
---
|
|
216
|
-
## 📚 Documentation
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
145
|
+
## 📚 Documentation Map
|
|
146
|
+
| Resource | Location |
|
|
147
|
+
|----------|----------|
|
|
148
|
+
| Full API Reference | `DOCS.md` |
|
|
149
|
+
| Feature Guides | `docs/*.md` |
|
|
150
|
+
| Safety Details | `docs/account-safety.md` |
|
|
151
|
+
| Examples | `examples/` |
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
## � Migrating 2.1.x → 3.0.0
|
|
155
|
+
| Area | Action Needed |
|
|
156
|
+
|------|---------------|
|
|
157
|
+
| Public API | None (fully compatible) |
|
|
158
|
+
| Diagnostics Harness | Removed (no action) |
|
|
159
|
+
| Delivery Metrics | Optionally surface in dashboards |
|
|
160
|
+
| Safety Manager (legacy) | Keep removed / unused |
|
|
221
161
|
|
|
222
162
|
---
|
|
223
|
-
##
|
|
224
|
-
|
|
|
225
|
-
|
|
226
|
-
|
|
|
227
|
-
|
|
|
228
|
-
|
|
|
229
|
-
|
|
|
230
|
-
|
|
|
163
|
+
## 🗂 Previous 2.1.x Highlights (Condensed)
|
|
164
|
+
| Version | Focus | Key Additions |
|
|
165
|
+
|---------|-------|---------------|
|
|
166
|
+
| 2.1.10 | Stabilization | Final 2.1.x meta adjustments |
|
|
167
|
+
| 2.1.8 | Safety Consolidation | Unified orchestrator, collision spacing, recycle suppression |
|
|
168
|
+
| 2.1.7 | Session Longevity | UA continuity, lightweight poke |
|
|
169
|
+
| 2.1.6 | Memory Guard | Queue pruning, edit TTL sweeps |
|
|
170
|
+
| 2.1.5 | Edit Reliability | PendingEdits buffer, ACK watchdog |
|
|
231
171
|
|
|
232
|
-
|
|
172
|
+
Full details remain in `CHANGELOG.md`.
|
|
233
173
|
|
|
234
174
|
---
|
|
235
175
|
## ⚠️ Disclaimer
|
|
236
|
-
|
|
176
|
+
Not affiliated with Facebook. Use responsibly and comply with platform terms & local laws.
|
|
237
177
|
|
|
238
178
|
---
|
|
239
|
-
## 🤝
|
|
240
|
-
PRs
|
|
179
|
+
## 🤝 Contributing
|
|
180
|
+
Focused PRs improving stability, safety heuristics, protocol coverage, or typings are welcome.
|
|
241
181
|
|
|
242
182
|
---
|
|
243
183
|
## 📜 License
|
|
244
|
-
|
|
184
|
+
[Team Nexus](https://www.facebook.com/profile.php?id=61572587854836)
|
|
185
|
+
|
|
186
|
+
MIT © 2025 Nexus (Team Nexus)
|
package/docs/account-safety.md
CHANGED
|
@@ -11,6 +11,8 @@ Nexus-FCA Ultra-Safe Edition is designed to minimize Facebook account ban, lock,
|
|
|
11
11
|
- **Proactive Safety Alerts:** If a risk is detected (lock, checkpoint, block), the bot will pause or stop to prevent further issues
|
|
12
12
|
- **Session & Token Management:** Automatic session validation and safe token refresh keep your login secure
|
|
13
13
|
- **Region & Connection Protection:** Advanced region bypass and safe reconnection logic avoid suspicious activity triggers
|
|
14
|
+
- **Unified Safety Orchestrator (2.1.8+):** Single scheduler coordinates safe refresh, light poke (~6h ±40m), and periodic recycle (~6h ±30m) with collision spacing (45m) to prevent clustered token actions
|
|
15
|
+
- **Ghost / Idle Detection:** Soft-stale probing (2m30s) and ghost recovery before full disconnect patterns emerge
|
|
14
16
|
|
|
15
17
|
---
|
|
16
18
|
|
|
@@ -24,6 +26,7 @@ Nexus-FCA Ultra-Safe Edition is designed to minimize Facebook account ban, lock,
|
|
|
24
26
|
- Always use cookies less than 7 days old for best results
|
|
25
27
|
- **Monitor Risk Level:**
|
|
26
28
|
- Listen for `riskLevelHigh`, `accountLocked`, `checkpointRequired` events and take action if triggered
|
|
29
|
+
- **Avoid Deprecated Manager:** Do not instantiate `FacebookSafetyManager` (legacy); consolidated `FacebookSafety` handles everything automatically.
|
|
27
30
|
|
|
28
31
|
---
|
|
29
32
|
|
|
@@ -34,6 +37,8 @@ Nexus-FCA Ultra-Safe Edition is designed to minimize Facebook account ban, lock,
|
|
|
34
37
|
- **Update your appstate.json regularly**
|
|
35
38
|
- **Monitor your Facebook account for security notifications**
|
|
36
39
|
- **If you see a checkpoint or lock, stop the bot and verify your account manually**
|
|
40
|
+
- **Do not schedule custom token poke timers; built‑in orchestrator already manages safe refresh cadence & spacing**
|
|
41
|
+
- **Keep `persistent-device.json` stable—don’t delete unless forced by actual session invalidation**
|
|
37
42
|
|
|
38
43
|
---
|
|
39
44
|
|
|
@@ -55,6 +60,13 @@ client.login({ appState: require('./appstate.json') });
|
|
|
55
60
|
- `checkpointRequired` — Facebook requires manual verification
|
|
56
61
|
- `riskLevelHigh` — High risk detected, bot will increase delays and reduce activity
|
|
57
62
|
- `sessionExpired` — Session expired, update your appstate.json
|
|
63
|
+
- `safeRefresh` — Safe token refresh attempted (payload includes success/failure, duration)
|
|
64
|
+
- `lightPoke` — Lightweight fb_dtsg keep-alive executed
|
|
65
|
+
- `mqttReconnect` — Reconnect cycle triggered (reason + attempt)
|
|
66
|
+
- `heartbeat` — Periodic keepalive ping succeeded
|
|
67
|
+
|
|
68
|
+
### Event Spacing (2.1.8+)
|
|
69
|
+
The orchestrator enforces a minimum spacing window (~45m) between maintenance actions (refresh, recycle, light poke). If a recycle is scheduled too soon after a refresh/poke it defers 20–30m automatically to avoid clustering patterns.
|
|
58
70
|
|
|
59
71
|
---
|
|
60
72
|
|
|
@@ -66,7 +78,16 @@ client.login({ appState: require('./appstate.json') });
|
|
|
66
78
|
> Stop the bot immediately, log in to Facebook manually, and follow the verification steps. Only restart the bot after your account is fully restored.
|
|
67
79
|
|
|
68
80
|
**Q: How often should I update my appstate.json?**
|
|
69
|
-
> At least once a week, or whenever you see a session expired or checkpoint event.
|
|
81
|
+
> At least once a week, or whenever you see a session expired or checkpoint event (less often if persistent device + long stable runs).
|
|
82
|
+
|
|
83
|
+
**Q: How often are tokens refreshed now?**
|
|
84
|
+
> Adaptive. Low risk uses multi‑hour windows; high risk shortens interval. Lightweight mid-session poke (~6h ±40m) and periodic recycle (~6h ±30m) are collision‑guarded with 45m spacing.
|
|
85
|
+
|
|
86
|
+
**Q: Do I need to keep my own refresh timers?**
|
|
87
|
+
> No. Remove custom refresh/poke loops—duplication increases clustering risk.
|
|
88
|
+
|
|
89
|
+
**Q: Is legacy `FacebookSafetyManager` still required?**
|
|
90
|
+
> No. It is deprecated and only logs a warning if used. Migrate entirely to the integrated safety layer (automatic on login).
|
|
70
91
|
|
|
71
92
|
---
|
|
72
93
|
|
package/index.js
CHANGED
|
@@ -271,6 +271,7 @@ function buildAPI(globalOptions, html, jar) {
|
|
|
271
271
|
});
|
|
272
272
|
},
|
|
273
273
|
getHealthMetrics: function(){ return ctx.health ? ctx.health.snapshot() : null; },
|
|
274
|
+
getMqttDiagnostics: function(){ return ctx.getMqttDiagnostics ? ctx.getMqttDiagnostics() : (ctx._mqttDiag || null); },
|
|
274
275
|
enableLazyPreflight(enable=true){ ctx.globalOptions.disablePreflight = !enable; },
|
|
275
276
|
setBackoffOptions(opts={}){ ctx.globalOptions.backoff = Object.assign(ctx.globalOptions.backoff||{}, opts); },
|
|
276
277
|
setEditOptions(opts={}){ Object.assign(ctx.globalOptions.editSettings, opts); },
|
|
@@ -296,6 +297,15 @@ function buildAPI(globalOptions, html, jar) {
|
|
|
296
297
|
api[v.replace(".js", "")] = require("./src/" + v)(defaultFuncs, api, ctx);
|
|
297
298
|
});
|
|
298
299
|
api.listen = api.listenMqtt;
|
|
300
|
+
// Adaptive outbound pacing wrapper (dynamic risk + post-maintenance window)
|
|
301
|
+
if (!api._adaptivePacingWrapped && typeof api.sendMessage === 'function') {
|
|
302
|
+
const _origSend = api.sendMessage;
|
|
303
|
+
api.sendMessage = async function(message, threadID, callback){
|
|
304
|
+
try { if (globalSafety && typeof globalSafety.applyAdaptiveSendDelay === 'function') await globalSafety.applyAdaptiveSendDelay(); } catch(_) {}
|
|
305
|
+
return _origSend(message, threadID, callback);
|
|
306
|
+
};
|
|
307
|
+
api._adaptivePacingWrapped = true;
|
|
308
|
+
}
|
|
299
309
|
// Safety wrapper: ensure every inbound MQTT event updates safety lastEvent timestamp
|
|
300
310
|
if (!api._safetyWrappedListen) {
|
|
301
311
|
const _origListen = api.listenMqtt;
|
|
@@ -505,21 +515,10 @@ function loginHelper(appState, email, password, globalOptions, callback, prCallb
|
|
|
505
515
|
logger('✅ Session authenticated successfully', 'info');
|
|
506
516
|
// Initialize safety monitoring
|
|
507
517
|
globalSafety.startMonitoring(ctx, api);
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
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();
|
|
518
|
+
try { globalSafety.startDynamicSystems(); } catch(_) {}
|
|
519
|
+
// Consolidated: delegate light poke to unified safety module (prevents duplicate refresh scheduling)
|
|
520
|
+
if (globalSafety && typeof globalSafety.scheduleLightPoke === 'function') {
|
|
521
|
+
globalSafety.scheduleLightPoke();
|
|
523
522
|
}
|
|
524
523
|
// Post-login identity banner
|
|
525
524
|
try {
|
|
@@ -34,6 +34,12 @@ class HealthMetrics {
|
|
|
34
34
|
this.groupQueueExpiredQueues = 0;
|
|
35
35
|
this.groupQueueDroppedMessages = 0;
|
|
36
36
|
this.pendingEditSweeps = 0;
|
|
37
|
+
// Delivery receipt metrics
|
|
38
|
+
this.deliveryAttempts = 0;
|
|
39
|
+
this.deliverySuccess = 0;
|
|
40
|
+
this.deliveryFailed = 0;
|
|
41
|
+
this.deliveryTimeouts = 0;
|
|
42
|
+
this.deliveryDisabledSince = 0; // timestamp if adaptive disable engaged
|
|
37
43
|
}
|
|
38
44
|
onConnect() { this.lastConnectTs = Date.now(); this.consecutiveFailures = 0; }
|
|
39
45
|
onDisconnect() { this.lastDisconnectTs = Date.now(); }
|
|
@@ -57,6 +63,7 @@ class HealthMetrics {
|
|
|
57
63
|
this.p95AckLatencyMs = sorted[idx];
|
|
58
64
|
}
|
|
59
65
|
onError(type){ this.lastErrorTs = Date.now(); this.lastErrorType = type || 'unknown'; }
|
|
66
|
+
incFailure(){ this.consecutiveFailures++; }
|
|
60
67
|
onReconnectScheduled(delay){ this.reconnects++; this.currentBackoffDelay = delay; if(delay > (this.maxObservedBackoff||0)) this.maxObservedBackoff = delay; }
|
|
61
68
|
trackOutbound(depth){ this.outboundQueueDepth = depth; }
|
|
62
69
|
incOutboundDropped(){ this.outboundQueueDropped++; }
|
|
@@ -114,6 +121,11 @@ class HealthMetrics {
|
|
|
114
121
|
groupQueueExpiredQueues: this.groupQueueExpiredQueues,
|
|
115
122
|
groupQueueDroppedMessages: this.groupQueueDroppedMessages,
|
|
116
123
|
pendingEditSweeps: this.pendingEditSweeps,
|
|
124
|
+
deliveryAttempts: this.deliveryAttempts,
|
|
125
|
+
deliverySuccess: this.deliverySuccess,
|
|
126
|
+
deliveryFailed: this.deliveryFailed,
|
|
127
|
+
deliveryTimeouts: this.deliveryTimeouts,
|
|
128
|
+
deliveryDisabled: !!this.deliveryDisabledSince,
|
|
117
129
|
healthy: this.isHealthy(idleMs)
|
|
118
130
|
};
|
|
119
131
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Lightweight diagnostics helper for MQTT/WebSocket connection lifecycle.
|
|
3
|
+
// Captures low-level events so we can understand repeated reconnect loops.
|
|
4
|
+
module.exports = function attachMqttDiagnostics(ws, ctx, log){
|
|
5
|
+
if(!ws || typeof ws.on !== 'function') return;
|
|
6
|
+
const diag = ctx._mqttDiag = ctx._mqttDiag || { attempts:0, events:[] };
|
|
7
|
+
function push(evt){
|
|
8
|
+
try {
|
|
9
|
+
diag.events.push({ t: Date.now(), ...evt });
|
|
10
|
+
if(diag.events.length > 50) diag.events.shift();
|
|
11
|
+
} catch(_) {}
|
|
12
|
+
}
|
|
13
|
+
ws.on('upgrade', (res)=>{ push({ type:'upgrade', status: res.statusCode, headers: safeHeaders(res.headers) }); });
|
|
14
|
+
ws.on('unexpected-response', (req, res)=>{ push({ type:'unexpected_response', status: res && res.statusCode, headers: res && safeHeaders(res.headers) }); });
|
|
15
|
+
ws.on('close', (code, reason)=>{ push({ type:'close', code, reason: reason && reason.toString() }); });
|
|
16
|
+
ws.on('error', (err)=>{ push({ type:'error', message: err && (err.message||err.code||'').toString(), code: err && err.code }); });
|
|
17
|
+
function safeHeaders(h){ if(!h) return {}; const out={}; for(const k of Object.keys(h)){ if(k.startsWith('cookie')) continue; out[k]=h[k]; } return out; }
|
|
18
|
+
// expose a snapshot method
|
|
19
|
+
ctx.getMqttDiagnostics = () => ({ attempts: diag.attempts, recent: [...diag.events] });
|
|
20
|
+
};
|
|
@@ -76,6 +76,17 @@ class FacebookSafety {
|
|
|
76
76
|
this._ghostChecking = false;
|
|
77
77
|
// Periodic recycle timer
|
|
78
78
|
this._periodicRecycleTimer = null;
|
|
79
|
+
// Consolidation additions
|
|
80
|
+
this._lastRefreshTs = 0; // track last successful refresh-like action
|
|
81
|
+
this._lastRecycleTs = 0;
|
|
82
|
+
this._lastLightPokeTs = 0;
|
|
83
|
+
this._timerRegistry = new Set();
|
|
84
|
+
this._minSpacingMs = 45 * 60 * 1000; // 45m guard between heavy/light actions
|
|
85
|
+
// Adaptive pacing + dynamic tuning additions
|
|
86
|
+
this._lastHeavyMaintenanceTs = 0; // last refresh OR successful reconnect
|
|
87
|
+
this._adaptivePacingWindowMs = 2 * 60 * 1000; // apply outbound pacing first 2m after heavy maintenance
|
|
88
|
+
this._dynamicHeartbeatTimer = null; // replaces fixed interval heartbeat for risk-tier tuning
|
|
89
|
+
this._riskLast = 'low';
|
|
79
90
|
|
|
80
91
|
this.initSafety();
|
|
81
92
|
}
|
|
@@ -282,10 +293,12 @@ class FacebookSafety {
|
|
|
282
293
|
maxMs = 5 * 60 * 60 * 1000; // 5h
|
|
283
294
|
}
|
|
284
295
|
const interval = minMs + Math.random() * (maxMs - minMs);
|
|
285
|
-
|
|
296
|
+
const t = setTimeout(async () => {
|
|
286
297
|
await this.refreshSafeSession();
|
|
287
298
|
schedule();
|
|
288
299
|
}, interval);
|
|
300
|
+
this._registerTimer(t);
|
|
301
|
+
this._safeRefreshTimer = t;
|
|
289
302
|
};
|
|
290
303
|
schedule();
|
|
291
304
|
}
|
|
@@ -305,13 +318,17 @@ class FacebookSafety {
|
|
|
305
318
|
updateRiskLevel() {
|
|
306
319
|
const timeSinceLastActivity = Date.now() - this.sessionMetrics.lastActivity;
|
|
307
320
|
const errorRate = this.sessionMetrics.errorCount / Math.max(1, this.sessionMetrics.requestCount);
|
|
308
|
-
|
|
321
|
+
let next;
|
|
309
322
|
if (errorRate > 0.3 || timeSinceLastActivity < 1000) {
|
|
310
|
-
|
|
323
|
+
next = 'high';
|
|
311
324
|
} else if (errorRate > 0.1 || timeSinceLastActivity < 5000) {
|
|
312
|
-
|
|
325
|
+
next = 'medium';
|
|
313
326
|
} else {
|
|
314
|
-
|
|
327
|
+
next = 'low';
|
|
328
|
+
}
|
|
329
|
+
if (next !== this.sessionMetrics.riskLevel) {
|
|
330
|
+
this.sessionMetrics.riskLevel = next;
|
|
331
|
+
this._onRiskLevelChanged(next);
|
|
315
332
|
}
|
|
316
333
|
}
|
|
317
334
|
|
|
@@ -371,10 +388,7 @@ class FacebookSafety {
|
|
|
371
388
|
const now = Date.now();
|
|
372
389
|
if (now < this._backoff.next) { return; }
|
|
373
390
|
const attempt = ++this._backoff.attempt;
|
|
374
|
-
|
|
375
|
-
const baseDelay = Math.min(20000, 1200 * Math.pow(1.8, Math.min(attempt, 6)));
|
|
376
|
-
const jitter = Math.random() * 500;
|
|
377
|
-
const delay = baseDelay + jitter;
|
|
391
|
+
const delay = this._computeBackoffDelay(attempt);
|
|
378
392
|
this._backoff.next = now + delay;
|
|
379
393
|
await new Promise(r => setTimeout(r, delay));
|
|
380
394
|
if (this._activeListenerStop && typeof this._activeListenerStop === 'function') { try { this._activeListenerStop(); } catch(_) {} }
|
|
@@ -382,6 +396,7 @@ class FacebookSafety {
|
|
|
382
396
|
const stop = this.api.listenMqtt((err, event) => { if (!err && event) this.recordEvent(); });
|
|
383
397
|
this._activeListenerStop = stop;
|
|
384
398
|
this.safetyEmit('mqttReconnect', { success: true, reason, attempt, delay });
|
|
399
|
+
this._markHeavyMaintenance();
|
|
385
400
|
}
|
|
386
401
|
setTimeout(() => {
|
|
387
402
|
if (this.ctx && this.ctx.mqttClient && this.ctx.mqttClient.connected) { this._backoff.attempt = 0; }
|
|
@@ -405,11 +420,22 @@ class FacebookSafety {
|
|
|
405
420
|
const base = 6 * 60 * 60 * 1000; // 6h
|
|
406
421
|
const jitter = (Math.random() * 60 - 30) * 60 * 1000; // ±30m
|
|
407
422
|
const delay = base + jitter;
|
|
408
|
-
|
|
423
|
+
const t = setTimeout(() => {
|
|
409
424
|
if (this._destroyed) return;
|
|
425
|
+
// Suppress recycle if a refresh/poke just happened inside spacing window
|
|
426
|
+
if (Date.now() - this._lastRefreshTs < this._minSpacingMs) {
|
|
427
|
+
// reschedule shorter backoff (add 20m) to avoid clustering
|
|
428
|
+
const defer = 20 * 60 * 1000 + Math.random() * 10 * 60 * 1000; // 20–30m
|
|
429
|
+
const dt = setTimeout(()=> this._schedulePeriodicRecycle(), defer);
|
|
430
|
+
this._registerTimer(dt);
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
this._lastRecycleTs = Date.now();
|
|
410
434
|
this.forceReconnect('periodic');
|
|
411
435
|
this._schedulePeriodicRecycle();
|
|
412
436
|
}, delay);
|
|
437
|
+
this._registerTimer(t);
|
|
438
|
+
this._periodicRecycleTimer = t;
|
|
413
439
|
}
|
|
414
440
|
|
|
415
441
|
// Heartbeat ping & watchdog
|
|
@@ -504,6 +530,10 @@ class FacebookSafety {
|
|
|
504
530
|
async refreshSafeSession() {
|
|
505
531
|
// Improved safe session refresh implementation
|
|
506
532
|
if (this._refreshing) return; // prevent concurrent refreshes
|
|
533
|
+
// Collision guard – skip if a refresh/poke happened very recently
|
|
534
|
+
if (Date.now() - this._lastRefreshTs < this._minSpacingMs / 2) {
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
507
537
|
this._refreshing = true;
|
|
508
538
|
const refreshId = ++this._inFlightRefreshId;
|
|
509
539
|
const startedAt = Date.now();
|
|
@@ -532,6 +562,8 @@ class FacebookSafety {
|
|
|
532
562
|
durationMs: Date.now() - startedAt,
|
|
533
563
|
message: 'Session tokens refreshed'
|
|
534
564
|
});
|
|
565
|
+
this._lastRefreshTs = Date.now();
|
|
566
|
+
this._markHeavyMaintenance();
|
|
535
567
|
// Immediate MQTT health ensure
|
|
536
568
|
await this._ensureMqttAlive();
|
|
537
569
|
// Schedule layered post-refresh checks (1s, 10s, 30s) to catch silent drops
|
|
@@ -570,11 +602,51 @@ class FacebookSafety {
|
|
|
570
602
|
}
|
|
571
603
|
}
|
|
572
604
|
|
|
605
|
+
/**
|
|
606
|
+
* Lightweight poke (fb_dtsg refresh only) integrated to remove duplicate logic in index.js
|
|
607
|
+
*/
|
|
608
|
+
scheduleLightPoke() {
|
|
609
|
+
if (this._lightPokeTimer || this._destroyed) return;
|
|
610
|
+
const base = 6 * 60 * 60 * 1000; // 6h
|
|
611
|
+
const jitter = (Math.random()*80 - 40) * 60 * 1000; // ±40m
|
|
612
|
+
const schedule = () => {
|
|
613
|
+
if (this._destroyed) return;
|
|
614
|
+
const t = setTimeout(async () => {
|
|
615
|
+
if (this._destroyed) return;
|
|
616
|
+
// Respect spacing: skip if recent heavy refresh
|
|
617
|
+
if (Date.now() - this._lastRefreshTs < this._minSpacingMs / 2) {
|
|
618
|
+
schedule();
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
621
|
+
try {
|
|
622
|
+
if (this.api && typeof this.api.refreshFb_dtsg === 'function') {
|
|
623
|
+
await this.api.refreshFb_dtsg().catch(()=>{});
|
|
624
|
+
this._lastRefreshTs = Date.now();
|
|
625
|
+
this._lastLightPokeTs = Date.now();
|
|
626
|
+
this.safetyEmit('lightPoke', { ts: Date.now() });
|
|
627
|
+
}
|
|
628
|
+
} catch(_) {}
|
|
629
|
+
schedule();
|
|
630
|
+
}, base + (Math.random()*80 - 40) * 60 * 1000);
|
|
631
|
+
this._registerTimer(t);
|
|
632
|
+
this._lightPokeTimer = t;
|
|
633
|
+
};
|
|
634
|
+
schedule();
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
_registerTimer(t){
|
|
638
|
+
if (!t) return;
|
|
639
|
+
this._timerRegistry.add(t);
|
|
640
|
+
}
|
|
641
|
+
|
|
573
642
|
// Cleanup / destroy resources (to prevent dangling timers)
|
|
574
643
|
destroy() {
|
|
575
644
|
this._destroyed = true;
|
|
576
|
-
const timers = [this._safeRefreshInterval, this._safeRefreshTimer, this._heartbeatTimer, this._watchdogTimer, this._periodicRecycleTimer];
|
|
645
|
+
const timers = [this._safeRefreshInterval, this._safeRefreshTimer, this._heartbeatTimer, this._watchdogTimer, this._periodicRecycleTimer, this._lightPokeTimer];
|
|
577
646
|
timers.forEach(t => t && clearTimeout(t));
|
|
647
|
+
// Clear any registered anonymous timers
|
|
648
|
+
this._timerRegistry.forEach(t => clearTimeout(t));
|
|
649
|
+
this._timerRegistry.clear();
|
|
578
650
|
if (this._activeListenerStop) {
|
|
579
651
|
try { this._activeListenerStop(); } catch (_) {}
|
|
580
652
|
this._activeListenerStop = null;
|
|
@@ -622,6 +694,99 @@ class FacebookSafety {
|
|
|
622
694
|
}
|
|
623
695
|
}
|
|
624
696
|
|
|
697
|
+
/* ======================== Dynamic Tuning & Pacing ======================== */
|
|
698
|
+
_onRiskLevelChanged(risk){
|
|
699
|
+
// Adjust spacing guard slightly (high risk allow earlier refresh to recover)
|
|
700
|
+
if (risk === 'high') this._minSpacingMs = 30 * 60 * 1000; else this._minSpacingMs = 45 * 60 * 1000;
|
|
701
|
+
// Reschedule heartbeat dynamically
|
|
702
|
+
this._scheduleDynamicHeartbeat(true);
|
|
703
|
+
this.safetyEmit('riskLevelChanged', { risk });
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
_computeBackoffDelay(attempt){
|
|
707
|
+
const risk = this.sessionMetrics.riskLevel;
|
|
708
|
+
const a = Math.min(attempt, 6);
|
|
709
|
+
let base;
|
|
710
|
+
if (risk === 'high') {
|
|
711
|
+
base = 900 * Math.pow(1.6, a); // faster recovery
|
|
712
|
+
} else if (risk === 'medium') {
|
|
713
|
+
base = 1100 * Math.pow(1.7, a);
|
|
714
|
+
} else { // low
|
|
715
|
+
base = 1500 * Math.pow(1.9, a); // slower to reduce noise
|
|
716
|
+
}
|
|
717
|
+
const cap = (risk === 'low') ? 25000 : (risk === 'medium' ? 22000 : 18000);
|
|
718
|
+
const delay = Math.min(cap, base) + Math.random()*600; // jitter
|
|
719
|
+
return delay;
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
_scheduleDynamicHeartbeat(reset){
|
|
723
|
+
if (reset && this._dynamicHeartbeatTimer){ clearTimeout(this._dynamicHeartbeatTimer); this._dynamicHeartbeatTimer = null; }
|
|
724
|
+
if (this._destroyed) return;
|
|
725
|
+
const interval = this._computeHeartbeatInterval();
|
|
726
|
+
this._dynamicHeartbeatTimer = setTimeout(()=>{
|
|
727
|
+
if (this._destroyed) return;
|
|
728
|
+
try {
|
|
729
|
+
if (this.ctx && this.ctx.mqttClient && this.ctx.mqttClient.connected) {
|
|
730
|
+
if (this.ctx.mqttClient.ping) this.ctx.mqttClient.ping();
|
|
731
|
+
try { this.ctx.mqttClient.publish('/foreground_state', JSON.stringify({ foreground: true })); } catch(_) {}
|
|
732
|
+
this.safetyEmit('heartbeat', { ts: Date.now(), dynamic: true });
|
|
733
|
+
}
|
|
734
|
+
} catch(_) {}
|
|
735
|
+
// Watchdog like check
|
|
736
|
+
this._runDynamicWatchdog();
|
|
737
|
+
this._scheduleDynamicHeartbeat(false);
|
|
738
|
+
}, interval);
|
|
739
|
+
this._registerTimer(this._dynamicHeartbeatTimer);
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
_computeHeartbeatInterval(){
|
|
743
|
+
const risk = this.sessionMetrics.riskLevel;
|
|
744
|
+
if (risk === 'high') return (55 + Math.random()*20) * 1000; // 55–75s
|
|
745
|
+
if (risk === 'medium') return (70 + Math.random()*20) * 1000; // 70–90s
|
|
746
|
+
return (80 + Math.random()*20) * 1000; // 80–100s
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
_runDynamicWatchdog(){
|
|
750
|
+
const idle = Date.now() - this._lastEventTs;
|
|
751
|
+
// escalate thresholds slightly by risk (high risk shorter tolerance)
|
|
752
|
+
const hard = (this.sessionMetrics.riskLevel === 'high') ? 8*60*1000 : 12*60*1000;
|
|
753
|
+
if (idle > hard) {
|
|
754
|
+
this._backoff.attempt = 0;
|
|
755
|
+
this._ensureMqttAlive();
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
_markHeavyMaintenance(){
|
|
760
|
+
this._lastHeavyMaintenanceTs = Date.now();
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
computeAdaptiveSendDelay(){
|
|
764
|
+
const risk = this.sessionMetrics.riskLevel;
|
|
765
|
+
const since = Date.now() - this._lastHeavyMaintenanceTs;
|
|
766
|
+
const inWindow = since < this._adaptivePacingWindowMs;
|
|
767
|
+
let min=0, max=0;
|
|
768
|
+
if (inWindow){
|
|
769
|
+
if (risk === 'high'){ min=600; max=1500; }
|
|
770
|
+
else if (risk === 'medium'){ min=200; max=800; }
|
|
771
|
+
else { min=0; max=300; }
|
|
772
|
+
} else {
|
|
773
|
+
// outside pacing window only high risk adds mild delay
|
|
774
|
+
if (risk === 'high'){ min=150; max=600; }
|
|
775
|
+
}
|
|
776
|
+
if (max<=0) return 0;
|
|
777
|
+
return Math.floor(min + Math.random()*(max-min));
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
applyAdaptiveSendDelay(){
|
|
781
|
+
const d = this.computeAdaptiveSendDelay();
|
|
782
|
+
if (!d) return Promise.resolve();
|
|
783
|
+
return new Promise(r=> setTimeout(r, d));
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
startDynamicSystems(){
|
|
787
|
+
this._scheduleDynamicHeartbeat(true);
|
|
788
|
+
}
|
|
789
|
+
|
|
625
790
|
/**
|
|
626
791
|
* Set safety event handler
|
|
627
792
|
*/
|
|
@@ -630,4 +795,4 @@ class FacebookSafety {
|
|
|
630
795
|
}
|
|
631
796
|
}
|
|
632
797
|
|
|
633
|
-
module.exports = FacebookSafety;
|
|
798
|
+
module.exports = FacebookSafety;
|
|
@@ -13,7 +13,8 @@ class FacebookSafetyManager extends EventEmitter {
|
|
|
13
13
|
constructor(options = {}) {
|
|
14
14
|
super();
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
console.warn('[DEPRECATION] FacebookSafetyManager is deprecated. The unified FacebookSafety module now handles all safety logic. Avoid using this manager.');
|
|
17
|
+
this.options = {
|
|
17
18
|
// Auto re-login detection
|
|
18
19
|
autoReloginEnabled: options.autoReloginEnabled !== false,
|
|
19
20
|
autoReloginRetries: options.autoReloginRetries || 3,
|
|
@@ -112,7 +113,7 @@ class FacebookSafetyManager extends EventEmitter {
|
|
|
112
113
|
this.startUserAgentRotation();
|
|
113
114
|
}
|
|
114
115
|
|
|
115
|
-
|
|
116
|
+
logger('🛡️ (Deprecated) Facebook Safety Manager initialized (prefer unified FacebookSafety)', 'info');
|
|
116
117
|
}
|
|
117
118
|
|
|
118
119
|
/**
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nexus-fca",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
|
+
"description": "Nexus-FCA 3.0 – stable, low-risk Facebook Messenger automation API with integrated secure login (ID / Password / 2FA), adaptive MQTT core, safety orchestration, metrics, and TypeScript support.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
package/src/listenMqtt.js
CHANGED
|
@@ -166,9 +166,15 @@ function buildStream(options, WebSocket, Proxy) {
|
|
|
166
166
|
return Stream;
|
|
167
167
|
}
|
|
168
168
|
function listenMqtt(defaultFuncs, api, ctx, globalCallback) {
|
|
169
|
+
const attemptStartTs = Date.now();
|
|
169
170
|
// Attach health metrics container lazily
|
|
170
171
|
if(!ctx.health) ctx.health = new (require('../lib/health/HealthMetrics').HealthMetrics)();
|
|
172
|
+
// Ensure tasks map exists to track ls_req -> ls_resp correlations (avoid TypeError on undefined)
|
|
173
|
+
if(!ctx.tasks) ctx.tasks = new Map();
|
|
171
174
|
const backoff = getBackoffState(ctx);
|
|
175
|
+
if(!ctx._mqttDiag) ctx._mqttDiag = { attempts:0, events:[] };
|
|
176
|
+
ctx._mqttDiag.attempts++;
|
|
177
|
+
log.info('listenMqtt', `Attempt #${ctx._mqttDiag.attempts} starting (backoffCurrent=${backoff.current||0})`);
|
|
172
178
|
const runPreflight = shouldRunPreflight(ctx);
|
|
173
179
|
if (runPreflight) {
|
|
174
180
|
(async () => {
|
|
@@ -263,20 +269,27 @@ function listenMqtt(defaultFuncs, api, ctx, globalCallback) {
|
|
|
263
269
|
log.error("listenMqtt", `Failed to create proxy agent: ${error.message}`);
|
|
264
270
|
}
|
|
265
271
|
}
|
|
272
|
+
// Create raw WebSocket first so we can attach diagnostics hooks.
|
|
273
|
+
const rawWs = new WebSocket(host, options.wsOptions);
|
|
274
|
+
try { require('../lib/mqtt/MqttDiagnostics')(rawWs, ctx, log); } catch(_) {}
|
|
266
275
|
ctx.mqttClient = new mqtt.Client(
|
|
267
|
-
() =>
|
|
268
|
-
buildStream(
|
|
269
|
-
options,
|
|
270
|
-
new WebSocket(host, options.wsOptions),
|
|
271
|
-
buildProxy()
|
|
272
|
-
),
|
|
276
|
+
() => buildStream(options, rawWs, buildProxy()),
|
|
273
277
|
options
|
|
274
278
|
);
|
|
279
|
+
log.info('listenMqtt', `Connecting to ${host}`);
|
|
275
280
|
const mqttClient = ctx.mqttClient;
|
|
276
281
|
global.mqttClient = mqttClient;
|
|
277
282
|
mqttClient.on('error', function (err) {
|
|
278
283
|
const errMsg = (err && (err.error || err.message || "")).toString();
|
|
279
284
|
ctx.health.onError(errMsg.includes('not logged in') ? 'not_logged_in' : 'mqtt_error');
|
|
285
|
+
// Increment failure counter for health tracking
|
|
286
|
+
if(ctx.health && typeof ctx.health.incFailure === 'function') ctx.health.incFailure();
|
|
287
|
+
if(!errMsg){
|
|
288
|
+
log.error('listenMqtt', 'Empty error message (mqtt error event). Raw err object: ' + JSON.stringify(Object.getOwnPropertyNames(err || {}).reduce((a,k)=>{a[k]=err[k];return a;},{})));
|
|
289
|
+
}
|
|
290
|
+
else {
|
|
291
|
+
log.error('listenMqtt', `MQTT error after ${(Date.now()-attemptStartTs)}ms: ${errMsg}`);
|
|
292
|
+
}
|
|
280
293
|
log.error("listenMqtt", errMsg);
|
|
281
294
|
try { mqttClient.end(true); } catch(_){ }
|
|
282
295
|
if (/not logged in|login_redirect|html_login_page/i.test(errMsg)) {
|
|
@@ -294,6 +307,8 @@ function listenMqtt(defaultFuncs, api, ctx, globalCallback) {
|
|
|
294
307
|
// Ensure reconnection also triggers on unexpected close without prior error
|
|
295
308
|
mqttClient.on('close', function () {
|
|
296
309
|
ctx.health.onDisconnect();
|
|
310
|
+
if(ctx.health && typeof ctx.health.incFailure === 'function'){ ctx.health.incFailure(); }
|
|
311
|
+
log.warn('listenMqtt', `Socket closed after ${(Date.now()-attemptStartTs)}ms (attempt #${ctx._mqttDiag.attempts}).`);
|
|
297
312
|
if (!ctx.loggedIn) return; // avoid loops if logged out
|
|
298
313
|
if (ctx.globalOptions.autoReconnect) {
|
|
299
314
|
scheduleAdaptiveReconnect(defaultFuncs, api, ctx, globalCallback);
|
|
@@ -301,6 +316,8 @@ function listenMqtt(defaultFuncs, api, ctx, globalCallback) {
|
|
|
301
316
|
});
|
|
302
317
|
mqttClient.on('disconnect', function(){
|
|
303
318
|
ctx.health.onDisconnect();
|
|
319
|
+
if(ctx.health && typeof ctx.health.incFailure === 'function'){ ctx.health.incFailure(); }
|
|
320
|
+
log.warn('listenMqtt', `MQTT disconnect event after ${(Date.now()-attemptStartTs)}ms (attempt #${ctx._mqttDiag.attempts}).`);
|
|
304
321
|
if (!ctx.loggedIn) return;
|
|
305
322
|
if (ctx.globalOptions.autoReconnect) {
|
|
306
323
|
scheduleAdaptiveReconnect(defaultFuncs, api, ctx, globalCallback);
|
|
@@ -309,6 +326,7 @@ function listenMqtt(defaultFuncs, api, ctx, globalCallback) {
|
|
|
309
326
|
mqttClient.on("connect", function () {
|
|
310
327
|
resetBackoff(backoff);
|
|
311
328
|
ctx.health.onConnect();
|
|
329
|
+
log.info('listenMqtt', `Connected in ${(Date.now()-attemptStartTs)}ms (attempt #${ctx._mqttDiag.attempts}).`);
|
|
312
330
|
if (ctx.globalSafety) { try { ctx.globalSafety.recordEvent(); } catch(_) {} }
|
|
313
331
|
if (process.env.OnStatus === undefined) {
|
|
314
332
|
logger("Nexus-FCA premium features works only with Nexus-Bot framework(Kidding)", "info");
|
|
@@ -418,7 +436,8 @@ function listenMqtt(defaultFuncs, api, ctx, globalCallback) {
|
|
|
418
436
|
} else if (topic == "/ls_resp") {
|
|
419
437
|
const parsedPayload = JSON.parse(jsonMessage.payload);
|
|
420
438
|
const reqID = jsonMessage.request_id;
|
|
421
|
-
|
|
439
|
+
// Guard: ctx.tasks may be empty; only proceed if it's a Map and contains the reqID
|
|
440
|
+
if (ctx.tasks && typeof ctx.tasks.has === 'function' && ctx.tasks.has(reqID)) {
|
|
422
441
|
const taskData = ctx["tasks"].get(reqID);
|
|
423
442
|
const { type: taskType, callback: taskCallback } = taskData;
|
|
424
443
|
const taskRespData = getTaskResponseData(taskType, parsedPayload);
|
package/src/markAsDelivered.js
CHANGED
|
@@ -30,28 +30,61 @@ module.exports = function (defaultFuncs, api, ctx) {
|
|
|
30
30
|
form["message_ids[0]"] = messageID;
|
|
31
31
|
form["thread_ids[" + threadID + "][0]"] = messageID;
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
.
|
|
40
|
-
|
|
41
|
-
.
|
|
42
|
-
if (resData.error) {
|
|
43
|
-
throw resData;
|
|
44
|
-
}
|
|
45
|
-
|
|
33
|
+
// Lightweight retry with exponential backoff for transient network timeouts.
|
|
34
|
+
const maxAttempts = 3;
|
|
35
|
+
let attempt = 0;
|
|
36
|
+
const baseDelay = 500; // ms
|
|
37
|
+
const transientCodes = ['ETIMEDOUT','ECONNRESET','EAI_AGAIN'];
|
|
38
|
+
if(ctx.health){
|
|
39
|
+
ctx.health.deliveryAttempts++;
|
|
40
|
+
// If we previously disabled delivery receipts due to repeated timeouts, short-circuit success.
|
|
41
|
+
if(ctx.health.deliveryDisabledSince){
|
|
46
42
|
return callback();
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function doPost(){
|
|
46
|
+
attempt++;
|
|
47
|
+
defaultFuncs
|
|
48
|
+
.post(
|
|
49
|
+
"https://www.facebook.com/ajax/mercury/delivery_receipts.php",
|
|
50
|
+
ctx.jar,
|
|
51
|
+
form
|
|
52
|
+
)
|
|
53
|
+
.then(utils.saveCookies(ctx.jar))
|
|
54
|
+
.then(utils.parseAndCheckLogin(ctx, defaultFuncs))
|
|
55
|
+
.then(function (resData) {
|
|
56
|
+
if (resData.error) { throw resData; }
|
|
57
|
+
if(ctx.health){ ctx.health.deliverySuccess++; }
|
|
58
|
+
return callback();
|
|
59
|
+
})
|
|
60
|
+
.catch(function (err) {
|
|
61
|
+
const code = err && (err.code || err.errno || (err.error && err.error.code));
|
|
62
|
+
const isTransient = code && transientCodes.includes(code);
|
|
63
|
+
if(code === 'ETIMEDOUT' && ctx.health){ ctx.health.deliveryTimeouts++; }
|
|
64
|
+
if(isTransient && attempt < maxAttempts){
|
|
65
|
+
const delay = Math.round(baseDelay * Math.pow(2, attempt-1) * (1 + Math.random()*0.2));
|
|
66
|
+
log.warn('markAsDelivered', `Transient ${code} attempt ${attempt}/${maxAttempts} -> retrying in ${delay}ms`);
|
|
67
|
+
return setTimeout(doPost, delay);
|
|
68
|
+
}
|
|
69
|
+
// Suppress noisy timeout logs after final retry unless verbose
|
|
70
|
+
if(!(isTransient && attempt >= maxAttempts)){
|
|
71
|
+
log.error("markAsDelivered", err);
|
|
72
|
+
}else{
|
|
73
|
+
log.warn('markAsDelivered', `Giving up after ${attempt} attempts (${code})`);
|
|
74
|
+
// Adaptive disable: if too many timeouts overall, stop calling delivery receipts for this run
|
|
75
|
+
if(code === 'ETIMEDOUT' && ctx.health && ctx.health.deliveryTimeouts >= 5){
|
|
76
|
+
ctx.health.deliveryDisabledSince = Date.now();
|
|
77
|
+
log.warn('markAsDelivered', 'Adaptive disable engaged after repeated ETIMEDOUT. Further receipts suppressed.');
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (utils.getType(err) == "Object" && err.error === "Not logged in.") {
|
|
81
|
+
ctx.loggedIn = false;
|
|
82
|
+
}
|
|
83
|
+
if(ctx.health){ ctx.health.deliveryFailed++; }
|
|
84
|
+
return callback(err);
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
doPost();
|
|
55
88
|
|
|
56
89
|
return returnPromise;
|
|
57
90
|
};
|