nexus-fca 3.0.1 → 3.0.2
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/README.md +1 -0
- package/docs/README.md +3 -0
- package/docs/configuration-reference.md +195 -0
- package/docs/deployment-config.md +178 -0
- package/index.js +77 -22
- package/lib/logger.js +0 -1
- package/lib/safety/SingleSessionGuard.js +99 -0
- package/package.json +1 -1
- package/src/listenMqtt.js +12 -5
- package/lib/login.js +0 -0
package/README.md
CHANGED
|
@@ -147,6 +147,7 @@ const login = require('nexus-fca');
|
|
|
147
147
|
|----------|----------|
|
|
148
148
|
| Full API Reference | `DOCS.md` |
|
|
149
149
|
| Feature Guides | `docs/*.md` |
|
|
150
|
+
| Configuration Reference | `docs/configuration-reference.md` |
|
|
150
151
|
| Safety Details | `docs/account-safety.md` |
|
|
151
152
|
| Examples | `examples/` |
|
|
152
153
|
|
package/docs/README.md
CHANGED
|
@@ -22,6 +22,9 @@ This folder contains comprehensive documentation for all features of Nexus-FCA,
|
|
|
22
22
|
### 🔧 Core API Documentation
|
|
23
23
|
Each `.md` file documents a single core API feature with usage examples, parameters, and safety notes.
|
|
24
24
|
|
|
25
|
+
Additional references:
|
|
26
|
+
- [Configuration Reference](./configuration-reference.md) — all env vars, programmatic options, and config file keys
|
|
27
|
+
|
|
25
28
|
### 🔗 Migration Guides
|
|
26
29
|
- **[Migration-fca-unofficial.md](./Migration-fca-unofficial.md)** - Migrate from fca-unofficial
|
|
27
30
|
- **[Migration-ws3-fca.md](./Migration-ws3-fca.md)** - Migrate from ws3-fca
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
# Configuration Reference (Nexus-FCA 3.0)
|
|
2
|
+
|
|
3
|
+
This document lists every supported configuration surface across the project: programmatic options, config file keys, and environment variables. Use this as a single source of truth when deploying locally or to PaaS.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
## 1) Programmatic options (api.setOptions / login options)
|
|
7
|
+
|
|
8
|
+
These map to `globalOptions` and are available via `api.setOptions({ ... })` or by passing as the second argument to `login(credentials, options)`.
|
|
9
|
+
|
|
10
|
+
- Booleans
|
|
11
|
+
- `online` (default: true) – Advertise chat availability when connected
|
|
12
|
+
- `selfListen` – Receive your own sent messages
|
|
13
|
+
- `listenEvents` – Emit non-message events (nicknames, pins, joins, etc.)
|
|
14
|
+
- `listenTyping` – Emit typing events
|
|
15
|
+
- `updatePresence` – Enable presence updates processing
|
|
16
|
+
- `forceLogin` – Force legacy login behavior where applicable
|
|
17
|
+
- `autoMarkDelivery` – Auto mark delivery for inbound messages
|
|
18
|
+
- `autoMarkRead` – Auto mark read for inbound messages
|
|
19
|
+
- `autoReconnect` (default: true) – Reconnect MQTT after disconnect
|
|
20
|
+
- `emitReady` – Emit a synthetic `ready` event after connect
|
|
21
|
+
|
|
22
|
+
- Strings
|
|
23
|
+
- `logLevel`: 'silent' | 'error' | 'warn' | 'info' | 'verbose'
|
|
24
|
+
- `pageID`: string – Page scope for actions
|
|
25
|
+
- `userAgent`: string – Overrides UA for outbound requests
|
|
26
|
+
- `proxy`: string – HTTP(S) proxy URL
|
|
27
|
+
- `acceptLanguage`: string – Accept-Language header for MQTT/Web requests
|
|
28
|
+
|
|
29
|
+
- Numbers
|
|
30
|
+
- `logRecordSize` – npmlog ring buffer size
|
|
31
|
+
|
|
32
|
+
- Advanced (set via helper methods)
|
|
33
|
+
- `api.setBackoffOptions({ base, factor, max, jitter })` – Adaptive MQTT reconnect backoff tuning
|
|
34
|
+
- `api.setEditOptions({ maxPendingEdits, editTTLms, ackTimeoutMs, maxResendAttempts })` – Message edit safety controls
|
|
35
|
+
- `api.enableLazyPreflight(enable=true)` – When true, preflight is lighter/skipped if recent successful connect
|
|
36
|
+
- Group queue controls for large group threads:
|
|
37
|
+
- `api.enableGroupQueue(enable=true)`
|
|
38
|
+
- `api.setGroupQueueCapacity(n)`
|
|
39
|
+
- Internals: `groupQueueIdleMs` (default 30m) – idle purge window
|
|
40
|
+
|
|
41
|
+
Notes:
|
|
42
|
+
- Unknown keys passed to `setOptions` are warned and ignored.
|
|
43
|
+
- `api.listen` is an alias of `api.listenMqtt`.
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
## 2) Config file: fca-config.json
|
|
47
|
+
|
|
48
|
+
Auto-created in project root on first run and merged with defaults. Good for project-wide non-secret settings.
|
|
49
|
+
|
|
50
|
+
Example keys (merge-safe):
|
|
51
|
+
|
|
52
|
+
```json
|
|
53
|
+
{
|
|
54
|
+
"autoUpdate": true,
|
|
55
|
+
"mqtt": { "enabled": true, "reconnectInterval": 3600 },
|
|
56
|
+
"logLevel": "warn",
|
|
57
|
+
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) ...",
|
|
58
|
+
"proxy": "http://user:pass@host:port"
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
For secrets and per-deploy toggles, prefer environment variables.
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
## 3) Environment variables
|
|
66
|
+
|
|
67
|
+
Environment variables are the primary way to configure behavior in production. They override defaults and complement programmatic options.
|
|
68
|
+
|
|
69
|
+
### 3.1 Storage & persistence
|
|
70
|
+
- `NEXUS_DATA_DIR` – Base directory for persistent files (fallback: `RENDER_DATA_DIR` or CWD)
|
|
71
|
+
- `NEXUS_APPSTATE_PATH` – Path to appstate.json
|
|
72
|
+
- `NEXUS_CREDENTIALS_PATH` – Path to credentials.json (integrated login)
|
|
73
|
+
- `NEXUS_BACKUP_PATH` – Directory for appstate backups
|
|
74
|
+
- `NEXUS_DEVICE_FILE` – Path to persistent-device.json
|
|
75
|
+
- `NEXUS_PERSISTENT_DEVICE` = true|false – Keep stable device fingerprint
|
|
76
|
+
|
|
77
|
+
### 3.2 Networking & headers
|
|
78
|
+
- `NEXUS_PROXY` – HTTP(S) proxy URL (also respects `HTTPS_PROXY` / `HTTP_PROXY`)
|
|
79
|
+
- `NEXUS_ACCEPT_LANGUAGE` – Example: `en-US,en;q=0.9`
|
|
80
|
+
- `NEXUS_UA` – Override User-Agent
|
|
81
|
+
- `NEXUS_REGION` – Force MQTT region (e.g., `HIL`)
|
|
82
|
+
|
|
83
|
+
### 3.3 Stability & preflight
|
|
84
|
+
- `NEXUS_DISABLE_PREFLIGHT` = true|false – Skip heavy preflight validation
|
|
85
|
+
- `NEXUS_ONLINE` = true|false – Override chat availability flag
|
|
86
|
+
- `NEXUS_VERBOSE_MQTT` = true|false – Extra MQTT diagnostics logging
|
|
87
|
+
|
|
88
|
+
### 3.4 Single-session guard (prevents concurrent runs)
|
|
89
|
+
- `NEXUS_SESSION_LOCK_PATH` – Path for session lock file (default: `<DATA_DIR>/session.lock`)
|
|
90
|
+
- `NEXUS_SESSION_TTL_MS` – Lock stale timeout (default: 900000 = 15m)
|
|
91
|
+
- `NEXUS_FORCE_LOCK` = true|false – Force takeover if lock present
|
|
92
|
+
|
|
93
|
+
### 3.5 Safety layer & allow/block lists
|
|
94
|
+
- `NEXUS_FCA_ULTRA_SAFE_MODE` = '1' – Maximum protection presets
|
|
95
|
+
- `NEXUS_FCA_SAFE_MODE` = '1' – Safe mode presets
|
|
96
|
+
- `NEXUS_FCA_ALLOW_LIST` – Comma-separated userIDs allowed
|
|
97
|
+
- `NEXUS_FCA_BLOCK_LIST` – Comma-separated userIDs blocked
|
|
98
|
+
|
|
99
|
+
### 3.6 Example-only envs (for quick scripts)
|
|
100
|
+
Used in example snippets and README demos; not used by the core library itself:
|
|
101
|
+
- `FB_EMAIL` – Facebook username/email
|
|
102
|
+
- `FB_PASS` / `FB_PASSWORD` – Facebook password
|
|
103
|
+
- `FB_2FA_SECRET` – TOTP secret for 2FA (if used)
|
|
104
|
+
- `EMAIL` / `PASSWORD` – Alternative names used in example scripts
|
|
105
|
+
- `DEBUG` – e.g. `nexus-fca:*` to enable debug output in certain examples
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
## 4) Integrated Nexus Login System options
|
|
109
|
+
|
|
110
|
+
Used internally for credential-based login; also exported for direct use via `IntegratedNexusLoginSystem`. Options can be provided in code or via the env overrides above.
|
|
111
|
+
|
|
112
|
+
- Paths & persistence
|
|
113
|
+
- `appstatePath` (env: `NEXUS_APPSTATE_PATH`)
|
|
114
|
+
- `credentialsPath` (env: `NEXUS_CREDENTIALS_PATH`)
|
|
115
|
+
- `backupPath` (env: `NEXUS_BACKUP_PATH`)
|
|
116
|
+
- `persistentDevice` (env: `NEXUS_PERSISTENT_DEVICE`)
|
|
117
|
+
- `persistentDeviceFile` (env: `NEXUS_DEVICE_FILE`)
|
|
118
|
+
|
|
119
|
+
- Behavior
|
|
120
|
+
- `autoLogin` (default: true)
|
|
121
|
+
- `autoSave` (default: true)
|
|
122
|
+
- `safeMode` (default: true)
|
|
123
|
+
- `maxRetries` (default: 3)
|
|
124
|
+
- `retryDelay` (default: 5000 ms)
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
## 5) MQTT & connection behavior
|
|
128
|
+
|
|
129
|
+
Controlled via programmatic options and envs:
|
|
130
|
+
|
|
131
|
+
- `autoReconnect` (default true) – Reconnect on close/error
|
|
132
|
+
- Backoff: `api.setBackoffOptions({ base, factor, max, jitter })`
|
|
133
|
+
- Keepalives & diagnostics: `NEXUS_VERBOSE_MQTT` for extra logs
|
|
134
|
+
- Headers: `acceptLanguage`, `userAgent`, `proxy`
|
|
135
|
+
- Region override: `NEXUS_REGION`
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
## 6) Delivery & read behavior
|
|
139
|
+
|
|
140
|
+
- `autoMarkDelivery` – Calls `markAsDelivered` for inbound messages
|
|
141
|
+
- `autoMarkRead` – Optional follow-up read marking
|
|
142
|
+
- Adaptive suppression & retries are built-in; metrics accessible via `api.getHealthMetrics()`
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
## 7) Health and memory metrics
|
|
146
|
+
|
|
147
|
+
- `api.getHealthMetrics()` – ACKs, reconnect counts, delivery stats, last connect timestamps, etc.
|
|
148
|
+
- `api.getMemoryMetrics()` – Pending edits, outbound queue depth, group queue prunes, memory guard actions
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
## 8) Logging
|
|
152
|
+
|
|
153
|
+
- `logLevel`: 'silent' | 'error' | 'warn' | 'info' | 'verbose'
|
|
154
|
+
- `logRecordSize`: ring buffer size
|
|
155
|
+
- Temporarily increase MQTT verbosity with `NEXUS_VERBOSE_MQTT=true` when debugging
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
## 9) Quick matrix (where to set)
|
|
159
|
+
|
|
160
|
+
| Setting | setOptions | login options | fca-config.json | ENV |
|
|
161
|
+
|--------------------------------|------------|---------------|-----------------|-----|
|
|
162
|
+
| online | ✅ | ✅ | | ✅ (`NEXUS_ONLINE`) |
|
|
163
|
+
| selfListen / listenEvents | ✅ | ✅ | | |
|
|
164
|
+
| updatePresence | ✅ | ✅ | | |
|
|
165
|
+
| autoMarkDelivery / autoMarkRead| ✅ | ✅ | | |
|
|
166
|
+
| autoReconnect | ✅ | ✅ | | |
|
|
167
|
+
| userAgent | ✅ | ✅ | ✅ | ✅ (`NEXUS_UA`) |
|
|
168
|
+
| proxy | ✅ | ✅ | ✅ | ✅ (`NEXUS_PROXY`/`HTTPS_PROXY`/`HTTP_PROXY`) |
|
|
169
|
+
| acceptLanguage | ✅ | ✅ | | ✅ (`NEXUS_ACCEPT_LANGUAGE`) |
|
|
170
|
+
| disablePreflight | via `enableLazyPreflight(false)` | ✅ | | ✅ (`NEXUS_DISABLE_PREFLIGHT`) |
|
|
171
|
+
| pageID | ✅ | ✅ | | |
|
|
172
|
+
| backoff/edit settings | helper fns | | | |
|
|
173
|
+
| data/appstate/backup/device | | via Integrated Login | | ✅ (`NEXUS_*` paths) |
|
|
174
|
+
| session guard (lock/ttl/force) | | | | ✅ |
|
|
175
|
+
| safety modes / lists | | | | ✅ |
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
## 10) Recommended PaaS baseline
|
|
179
|
+
|
|
180
|
+
Set at minimum:
|
|
181
|
+
|
|
182
|
+
```
|
|
183
|
+
NEXUS_DATA_DIR=/var/data/nexus-fca
|
|
184
|
+
NEXUS_APPSTATE_PATH=/var/data/nexus-fca/appstate.json
|
|
185
|
+
NEXUS_DEVICE_FILE=/var/data/nexus-fca/persistent-device.json
|
|
186
|
+
NEXUS_PERSISTENT_DEVICE=true
|
|
187
|
+
NEXUS_ACCEPT_LANGUAGE=en-US,en;q=0.9
|
|
188
|
+
# Optional hardening
|
|
189
|
+
NEXUS_DISABLE_PREFLIGHT=true
|
|
190
|
+
# Optional proxy/region
|
|
191
|
+
# NEXUS_PROXY=http://user:pass@host:port
|
|
192
|
+
# NEXUS_REGION=HIL
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
See also: `docs/deployment-config.md` for end-to-end deployment steps.
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# Deployment & Configuration Guide (Nexus-FCA 3.0)
|
|
2
|
+
|
|
3
|
+
This guide shows how to use Nexus-FCA as an npm package, configure it via file and environment variables, deploy safely on platforms like Render, and avoid the “you couldn't create multiple sessions” warning.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
## 1) Using as an npm package
|
|
7
|
+
|
|
8
|
+
Install:
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npm install nexus-fca
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Basic appstate usage:
|
|
15
|
+
|
|
16
|
+
```js
|
|
17
|
+
const login = require('nexus-fca');
|
|
18
|
+
|
|
19
|
+
(async () => {
|
|
20
|
+
const api = await login({ appState: require('./appstate.json') });
|
|
21
|
+
api.listen((err, evt) => {
|
|
22
|
+
if (err) return console.error(err);
|
|
23
|
+
if (evt.body) api.sendMessage('Echo: ' + evt.body, evt.threadID);
|
|
24
|
+
});
|
|
25
|
+
})();
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
## 2) Configuration options
|
|
30
|
+
|
|
31
|
+
You can control behavior via:
|
|
32
|
+
- Programmatic options: `api.setOptions({ ... })`
|
|
33
|
+
- Config file: `fca-config.json` (auto-created on first run)
|
|
34
|
+
- Environment variables (recommended for hosting)
|
|
35
|
+
|
|
36
|
+
### 2.1 Programmatic options (common)
|
|
37
|
+
```js
|
|
38
|
+
api.setOptions({
|
|
39
|
+
autoReconnect: true,
|
|
40
|
+
updatePresence: false,
|
|
41
|
+
selfListen: false,
|
|
42
|
+
logLevel: 'warn', // npmlog level: silly|verbose|info|warn|error
|
|
43
|
+
userAgent: '...Chrome/...',
|
|
44
|
+
proxy: 'http://user:pass@host:port',
|
|
45
|
+
});
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### 2.2 Config file: `fca-config.json`
|
|
49
|
+
A default file is stored at project root and merged with internal defaults. You can keep project-wide settings here:
|
|
50
|
+
|
|
51
|
+
```json
|
|
52
|
+
{
|
|
53
|
+
"autoUpdate": true,
|
|
54
|
+
"mqtt": { "enabled": true, "reconnectInterval": 3600 },
|
|
55
|
+
"logLevel": "warn",
|
|
56
|
+
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)...",
|
|
57
|
+
"proxy": "http://user:pass@host:port"
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Note: For secrets and deployment toggles, prefer environment variables below.
|
|
62
|
+
|
|
63
|
+
### 2.3 Environment variables (hosting-friendly)
|
|
64
|
+
These are read at runtime and override defaults:
|
|
65
|
+
|
|
66
|
+
- Storage & persistence
|
|
67
|
+
- `NEXUS_DATA_DIR` → base directory for persistent files
|
|
68
|
+
- `NEXUS_APPSTATE_PATH` → path to `appstate.json`
|
|
69
|
+
- `NEXUS_DEVICE_FILE` → path to `persistent-device.json`
|
|
70
|
+
- `NEXUS_BACKUP_PATH` → directory for appstate backups
|
|
71
|
+
- `NEXUS_PERSISTENT_DEVICE` → `true|false` (keep same device fingerprint)
|
|
72
|
+
|
|
73
|
+
- Networking & headers
|
|
74
|
+
- `NEXUS_PROXY` (or `HTTPS_PROXY` / `HTTP_PROXY`) → Proxy URL
|
|
75
|
+
- `NEXUS_ACCEPT_LANGUAGE` → e.g. `en-US,en;q=0.9`
|
|
76
|
+
- `NEXUS_UA` → override User-Agent
|
|
77
|
+
- `NEXUS_REGION` → force MQTT region (e.g., `HIL`)
|
|
78
|
+
|
|
79
|
+
- Stability & preflight
|
|
80
|
+
- `NEXUS_DISABLE_PREFLIGHT` → `true|false` (skip heavy session preflight)
|
|
81
|
+
- `NEXUS_ONLINE` → `true|false`
|
|
82
|
+
- `NEXUS_VERBOSE_MQTT` → `true|false` (extra MQTT logs only when needed)
|
|
83
|
+
|
|
84
|
+
Example (Render):
|
|
85
|
+
```
|
|
86
|
+
NEXUS_DATA_DIR=/var/data/nexus-fca
|
|
87
|
+
NEXUS_APPSTATE_PATH=/var/data/nexus-fca/appstate.json
|
|
88
|
+
NEXUS_DEVICE_FILE=/var/data/nexus-fca/persistent-device.json
|
|
89
|
+
NEXUS_BACKUP_PATH=/var/data/nexus-fca/backups
|
|
90
|
+
NEXUS_PERSISTENT_DEVICE=true
|
|
91
|
+
|
|
92
|
+
NEXUS_ACCEPT_LANGUAGE=en-US,en;q=0.9
|
|
93
|
+
NEXUS_UA=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36
|
|
94
|
+
NEXUS_DISABLE_PREFLIGHT=true
|
|
95
|
+
NEXUS_ONLINE=false
|
|
96
|
+
# Optional region & proxy
|
|
97
|
+
# NEXUS_REGION=HIL
|
|
98
|
+
# NEXUS_PROXY=http://user:pass@host:port
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
## 3) Deploying on Render (or other PaaS)
|
|
103
|
+
|
|
104
|
+
1) Persistent disk
|
|
105
|
+
- Mount a disk and point `NEXUS_DATA_DIR` to it.
|
|
106
|
+
- Ensure `appstate.json` and `persistent-device.json` live on this disk.
|
|
107
|
+
|
|
108
|
+
2) Single instance per account
|
|
109
|
+
- Run only one service instance for a given account.
|
|
110
|
+
- Disable autoscaling/replicas for this worker.
|
|
111
|
+
|
|
112
|
+
3) Environment hardening
|
|
113
|
+
- Set `NEXUS_PERSISTENT_DEVICE=true` to anchor device fingerprint.
|
|
114
|
+
- Set `NEXUS_DISABLE_PREFLIGHT=true` to reduce heavy checks.
|
|
115
|
+
- Optionally use a reputable residential/mobile proxy via `NEXUS_PROXY`.
|
|
116
|
+
|
|
117
|
+
4) Keep your appstate fresh
|
|
118
|
+
- Generate appstate locally on a trusted network, then deploy it.
|
|
119
|
+
- Avoid frequent credential logins from the server itself.
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
## 4) "You couldn't create multiple sessions" – what it means and fixes
|
|
123
|
+
|
|
124
|
+
This warning typically appears when Facebook detects concurrent or rapidly rotating logins for the same account, often from:
|
|
125
|
+
- Multiple bots/instances using the same account at the same time
|
|
126
|
+
- Different IPs or device fingerprints in quick succession
|
|
127
|
+
- Frequent logouts/re-logins (or unstable appstate)
|
|
128
|
+
|
|
129
|
+
How to avoid:
|
|
130
|
+
- Run exactly one bot instance per account.
|
|
131
|
+
- Reuse the same `appstate.json` and `persistent-device.json` (set `NEXUS_PERSISTENT_DEVICE=true`).
|
|
132
|
+
- Avoid logging in with username/password from multiple places—generate appstate once and reuse it.
|
|
133
|
+
- Use a stable IP. If your PaaS IPs rotate or look like a datacenter bot, use a reputable residential/mobile proxy.
|
|
134
|
+
- Keep preflight light on PaaS (`NEXUS_DISABLE_PREFLIGHT=true`).
|
|
135
|
+
|
|
136
|
+
Recovery steps when you see the warning:
|
|
137
|
+
- Stop all other running instances for the same account.
|
|
138
|
+
- Log in manually in a browser, pass any checkpoint, verify recent logins.
|
|
139
|
+
- Regenerate a fresh appstate (locally), deploy, and keep it persistent.
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
## 5) Minimal config-first example
|
|
143
|
+
|
|
144
|
+
`fca-config.json` (project root):
|
|
145
|
+
```json
|
|
146
|
+
{
|
|
147
|
+
"logLevel": "warn",
|
|
148
|
+
"mqtt": { "enabled": true, "reconnectInterval": 3600 },
|
|
149
|
+
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/121.0.0.0 Safari/537.36"
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Bot code:
|
|
154
|
+
```js
|
|
155
|
+
const login = require('nexus-fca');
|
|
156
|
+
(async () => {
|
|
157
|
+
const api = await login({ appState: require(process.env.NEXUS_APPSTATE_PATH || './appstate.json') });
|
|
158
|
+
// Optional fine-tuning
|
|
159
|
+
api.setOptions({
|
|
160
|
+
autoReconnect: true,
|
|
161
|
+
updatePresence: false,
|
|
162
|
+
proxy: process.env.NEXUS_PROXY,
|
|
163
|
+
});
|
|
164
|
+
api.listen((err, evt) => {
|
|
165
|
+
if (err) return console.error(err);
|
|
166
|
+
if (evt.body) api.sendMessage('Echo: ' + evt.body, evt.threadID);
|
|
167
|
+
});
|
|
168
|
+
})();
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
## 6) Troubleshooting quick tips
|
|
173
|
+
- Rapid cookie expiry: ensure persistence paths are on mounted disk; set `NEXUS_PERSISTENT_DEVICE=true`.
|
|
174
|
+
- Frequent reconnect warnings: let adaptive backoff work; keep `autoReconnect=true`.
|
|
175
|
+
- No replies sent: check proxy/network egress; use delivery metrics via `api.getHealthMetrics()`.
|
|
176
|
+
- Noisy logs: avoid `NEXUS_VERBOSE_MQTT` unless debugging; prefer `logLevel=warn`.
|
|
177
|
+
|
|
178
|
+
For full API details, see `DOCS.md` and other files in `docs/`.
|
package/index.js
CHANGED
|
@@ -51,6 +51,7 @@ const { User } = require('./lib/message/User');
|
|
|
51
51
|
|
|
52
52
|
// Advanced Safety Module - Minimizes ban/lock/checkpoint rates
|
|
53
53
|
const FacebookSafety = require('./lib/safety/FacebookSafety');
|
|
54
|
+
const { SingleSessionGuard } = require('./lib/safety/SingleSessionGuard');
|
|
54
55
|
|
|
55
56
|
// Core compatibility imports
|
|
56
57
|
const MqttManager = require('./lib/mqtt/MqttManager');
|
|
@@ -195,6 +196,10 @@ function buildAPI(globalOptions, html, jar) {
|
|
|
195
196
|
} catch (e) {
|
|
196
197
|
log.warning("login", "Not MQTT endpoint");
|
|
197
198
|
}
|
|
199
|
+
// Allow environment override for region (useful on PaaS where HTML may omit region or mismatch)
|
|
200
|
+
if (process.env.NEXUS_REGION) {
|
|
201
|
+
try { region = process.env.NEXUS_REGION.toUpperCase(); } catch(_) {}
|
|
202
|
+
}
|
|
198
203
|
const tokenMatch = html.match(/DTSGInitialData.*?token":"(.*?)"/);
|
|
199
204
|
if (tokenMatch) {
|
|
200
205
|
fb_dtsg = tokenMatch[1];
|
|
@@ -557,20 +562,24 @@ const crypto = require('crypto');
|
|
|
557
562
|
|
|
558
563
|
class IntegratedNexusLoginSystem {
|
|
559
564
|
constructor(options = {}) {
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
565
|
+
const dataDir = process.env.NEXUS_DATA_DIR || process.env.RENDER_DATA_DIR || process.cwd();
|
|
566
|
+
const envPersistent = (v) => (v === '0' || v === 'false') ? false : (v === '1' || v === 'true') ? true : undefined;
|
|
567
|
+
const envPD = envPersistent(process.env.NEXUS_PERSISTENT_DEVICE);
|
|
568
|
+
|
|
569
|
+
this.options = {
|
|
570
|
+
appstatePath: options.appstatePath || process.env.NEXUS_APPSTATE_PATH || path.join(dataDir, 'appstate.json'),
|
|
571
|
+
credentialsPath: options.credentialsPath || process.env.NEXUS_CREDENTIALS_PATH || path.join(dataDir, 'credentials.json'),
|
|
572
|
+
backupPath: options.backupPath || process.env.NEXUS_BACKUP_PATH || path.join(dataDir, 'backups'),
|
|
573
|
+
autoLogin: options.autoLogin !== false,
|
|
574
|
+
autoSave: options.autoSave !== false,
|
|
575
|
+
safeMode: options.safeMode !== false,
|
|
576
|
+
maxRetries: options.maxRetries || 3,
|
|
577
|
+
retryDelay: options.retryDelay || 5000,
|
|
578
|
+
// New: persistentDevice disables random device rotation
|
|
579
|
+
persistentDevice: typeof envPD === 'boolean' ? envPD : (options.persistentDevice !== false),
|
|
580
|
+
persistentDeviceFile: options.persistentDeviceFile || process.env.NEXUS_DEVICE_FILE || path.join(dataDir, 'persistent-device.json'),
|
|
581
|
+
...options
|
|
582
|
+
};
|
|
574
583
|
|
|
575
584
|
this.deviceCache = new Map();
|
|
576
585
|
this.loginAttempts = 0;
|
|
@@ -578,10 +587,30 @@ class IntegratedNexusLoginSystem {
|
|
|
578
587
|
// New: load previously persisted device if any
|
|
579
588
|
this.fixedDeviceProfile = this.loadPersistentDevice();
|
|
580
589
|
|
|
581
|
-
|
|
590
|
+
this.ensureDirectories();
|
|
582
591
|
this.logger('Login system ready', '🚀');
|
|
583
592
|
}
|
|
584
593
|
|
|
594
|
+
ensureDirectories() {
|
|
595
|
+
try {
|
|
596
|
+
// backups dir
|
|
597
|
+
if (this.options.backupPath && !fs.existsSync(this.options.backupPath)) {
|
|
598
|
+
fs.mkdirSync(this.options.backupPath, { recursive: true });
|
|
599
|
+
}
|
|
600
|
+
// parent dir for appstate
|
|
601
|
+
const appstateDir = path.dirname(this.options.appstatePath);
|
|
602
|
+
if (!fs.existsSync(appstateDir)) fs.mkdirSync(appstateDir, { recursive: true });
|
|
603
|
+
// parent dir for credentials
|
|
604
|
+
const credDir = path.dirname(this.options.credentialsPath);
|
|
605
|
+
if (!fs.existsSync(credDir)) fs.mkdirSync(credDir, { recursive: true });
|
|
606
|
+
// parent dir for persistent device
|
|
607
|
+
const pdDir = path.dirname(this.options.persistentDeviceFile);
|
|
608
|
+
if (!fs.existsSync(pdDir)) fs.mkdirSync(pdDir, { recursive: true });
|
|
609
|
+
} catch (e) {
|
|
610
|
+
this.logger('Failed to ensure directories: ' + e.message, '⚠️');
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
|
|
585
614
|
loadPersistentDevice() {
|
|
586
615
|
try {
|
|
587
616
|
if (!this.options.persistentDevice) return null;
|
|
@@ -1102,7 +1131,7 @@ async function integratedNexusLogin(credentials = null, options = {}) {
|
|
|
1102
1131
|
|
|
1103
1132
|
try {
|
|
1104
1133
|
// Prepare global options for bot system
|
|
1105
|
-
|
|
1134
|
+
const globalOptions = {
|
|
1106
1135
|
selfListen: false,
|
|
1107
1136
|
selfListenEvent: false,
|
|
1108
1137
|
listenEvents: false,
|
|
@@ -1113,9 +1142,12 @@ async function integratedNexusLogin(credentials = null, options = {}) {
|
|
|
1113
1142
|
autoMarkRead: false,
|
|
1114
1143
|
autoReconnect: true,
|
|
1115
1144
|
logRecordSize: defaultLogRecordSize,
|
|
1116
|
-
|
|
1145
|
+
online: (process.env.NEXUS_ONLINE ? (process.env.NEXUS_ONLINE === '1' || process.env.NEXUS_ONLINE === 'true') : true),
|
|
1117
1146
|
emitReady: false,
|
|
1118
|
-
|
|
1147
|
+
userAgent: process.env.NEXUS_UA || "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36",
|
|
1148
|
+
proxy: process.env.NEXUS_PROXY || process.env.HTTPS_PROXY || process.env.HTTP_PROXY,
|
|
1149
|
+
acceptLanguage: process.env.NEXUS_ACCEPT_LANGUAGE || 'en-US,en;q=0.9',
|
|
1150
|
+
disablePreflight: process.env.NEXUS_DISABLE_PREFLIGHT === '1' || process.env.NEXUS_DISABLE_PREFLIGHT === 'true',
|
|
1119
1151
|
...options
|
|
1120
1152
|
};
|
|
1121
1153
|
|
|
@@ -1229,7 +1261,18 @@ async function login(loginData, options = {}, callback) {
|
|
|
1229
1261
|
mainLogger.info('✅ Session generated successfully');
|
|
1230
1262
|
mainLogger.info('🔄 Starting bot with generated session (old system)');
|
|
1231
1263
|
|
|
1232
|
-
// STEP 2:
|
|
1264
|
+
// STEP 2: Single session guard before starting bot
|
|
1265
|
+
try {
|
|
1266
|
+
const ssg = new SingleSessionGuard({ dataDir: process.env.NEXUS_DATA_DIR });
|
|
1267
|
+
ssg.acquire();
|
|
1268
|
+
// keep guard reference to release on exit
|
|
1269
|
+
global.__NEXUS_SSG__ = ssg;
|
|
1270
|
+
} catch (e) {
|
|
1271
|
+
mainLogger.error('⚠️ Single session guard blocked start', e.message);
|
|
1272
|
+
if (callback) callback(e);
|
|
1273
|
+
return usePromise ? promise : undefined;
|
|
1274
|
+
}
|
|
1275
|
+
// STEP 3: ALWAYS use OLD system for actual login/session/bot
|
|
1233
1276
|
const globalOptions = {
|
|
1234
1277
|
selfListen: false,
|
|
1235
1278
|
selfListenEvent: false,
|
|
@@ -1271,7 +1314,16 @@ async function login(loginData, options = {}, callback) {
|
|
|
1271
1314
|
return usePromise ? promise : undefined;
|
|
1272
1315
|
}
|
|
1273
1316
|
|
|
1274
|
-
// Direct session authentication using appstate
|
|
1317
|
+
// Direct session authentication using appstate (with single session guard)
|
|
1318
|
+
try {
|
|
1319
|
+
const ssg = new SingleSessionGuard({ dataDir: process.env.NEXUS_DATA_DIR });
|
|
1320
|
+
ssg.acquire();
|
|
1321
|
+
global.__NEXUS_SSG__ = ssg;
|
|
1322
|
+
} catch (e) {
|
|
1323
|
+
mainLogger.error('⚠️ Single session guard blocked start', e.message);
|
|
1324
|
+
if (callback) callback(e);
|
|
1325
|
+
return usePromise ? promise : undefined;
|
|
1326
|
+
}
|
|
1275
1327
|
mainLogger.info('🔄 Starting session authentication');
|
|
1276
1328
|
|
|
1277
1329
|
const globalOptions = {
|
|
@@ -1285,9 +1337,12 @@ async function login(loginData, options = {}, callback) {
|
|
|
1285
1337
|
autoMarkRead: false,
|
|
1286
1338
|
autoReconnect: true,
|
|
1287
1339
|
logRecordSize: defaultLogRecordSize,
|
|
1288
|
-
online: true,
|
|
1340
|
+
online: (process.env.NEXUS_ONLINE ? (process.env.NEXUS_ONLINE === '1' || process.env.NEXUS_ONLINE === 'true') : true),
|
|
1289
1341
|
emitReady: false,
|
|
1290
|
-
userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36",
|
|
1342
|
+
userAgent: process.env.NEXUS_UA || "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36",
|
|
1343
|
+
proxy: process.env.NEXUS_PROXY || process.env.HTTPS_PROXY || process.env.HTTP_PROXY,
|
|
1344
|
+
acceptLanguage: process.env.NEXUS_ACCEPT_LANGUAGE || 'en-US,en;q=0.9',
|
|
1345
|
+
disablePreflight: process.env.NEXUS_DISABLE_PREFLIGHT === '1' || process.env.NEXUS_DISABLE_PREFLIGHT === 'true',
|
|
1291
1346
|
...options
|
|
1292
1347
|
};
|
|
1293
1348
|
|
package/lib/logger.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
const chalk = require('chalk');
|
|
2
2
|
const gradient = require('gradient-string');
|
|
3
3
|
|
|
4
|
-
// Use a professional gradient for info logs
|
|
5
4
|
const infoGradient = gradient(['#00c6ff', '#0072ff']); // blue-cyan gradient
|
|
6
5
|
const warnColor = chalk.yellow.bold;
|
|
7
6
|
const errorColor = chalk.red.bold;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
const fs = require("fs");
|
|
3
|
+
const os = require("os");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
|
|
6
|
+
function nowIso(){ return new Date().toISOString(); }
|
|
7
|
+
|
|
8
|
+
function readJsonSafe(file){
|
|
9
|
+
try { return JSON.parse(fs.readFileSync(file, 'utf8')); } catch(_) { return null; }
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function writeJsonSafe(file, obj){
|
|
13
|
+
try { fs.writeFileSync(file, JSON.stringify(obj, null, 2)); } catch(_) {}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
class SingleSessionGuard {
|
|
17
|
+
constructor(options={}){
|
|
18
|
+
const baseDir = options.dataDir || process.env.NEXUS_DATA_DIR || process.env.RENDER_DATA_DIR || process.cwd();
|
|
19
|
+
this.lockPath = options.lockPath || process.env.NEXUS_SESSION_LOCK_PATH || path.join(baseDir, 'session.lock');
|
|
20
|
+
this.ttlMs = options.ttlMs || parseInt(process.env.NEXUS_SESSION_TTL_MS || '900000', 10); // default 15m
|
|
21
|
+
this.force = options.force || (process.env.NEXUS_FORCE_LOCK === '1' || process.env.NEXUS_FORCE_LOCK === 'true');
|
|
22
|
+
this.heartbeatMs = options.heartbeatMs || 60000; // 1m
|
|
23
|
+
this.sessionId = (options.sessionId) || `${process.pid}-${Math.random().toString(36).slice(2,10)}`;
|
|
24
|
+
this._hbTimer = null;
|
|
25
|
+
this._acquired = false;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
isStale(lock){
|
|
29
|
+
if(!lock || !lock.updatedAt) return true;
|
|
30
|
+
const age = Date.now() - new Date(lock.updatedAt).getTime();
|
|
31
|
+
return age > this.ttlMs;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
acquire(){
|
|
35
|
+
// Ensure parent dir exists
|
|
36
|
+
try { fs.mkdirSync(path.dirname(this.lockPath), { recursive: true }); } catch(_) {}
|
|
37
|
+
if (fs.existsSync(this.lockPath)){
|
|
38
|
+
const existing = readJsonSafe(this.lockPath);
|
|
39
|
+
if (this.isStale(existing) || this.force){
|
|
40
|
+
// Take over
|
|
41
|
+
try { fs.unlinkSync(this.lockPath); } catch(_) {}
|
|
42
|
+
} else {
|
|
43
|
+
const msg = `Another Nexus-FCA session appears active (lock: ${this.lockPath}). Set NEXUS_FORCE_LOCK=true to override or wait until stale.`;
|
|
44
|
+
const error = new Error(msg);
|
|
45
|
+
error.code = 'NEXUS_MULTIPLE_SESSIONS';
|
|
46
|
+
throw error;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
const payload = {
|
|
50
|
+
pid: process.pid,
|
|
51
|
+
host: os.hostname(),
|
|
52
|
+
sessionId: this.sessionId,
|
|
53
|
+
startedAt: nowIso(),
|
|
54
|
+
updatedAt: nowIso()
|
|
55
|
+
};
|
|
56
|
+
writeJsonSafe(this.lockPath, payload);
|
|
57
|
+
this._acquired = true;
|
|
58
|
+
this._startHeartbeat();
|
|
59
|
+
this._installExitHooks();
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
_startHeartbeat(){
|
|
64
|
+
this._clearHeartbeat();
|
|
65
|
+
this._hbTimer = setInterval(()=>{
|
|
66
|
+
try {
|
|
67
|
+
if(!this._acquired) return;
|
|
68
|
+
const cur = readJsonSafe(this.lockPath) || {};
|
|
69
|
+
cur.updatedAt = nowIso();
|
|
70
|
+
cur.pid = process.pid;
|
|
71
|
+
cur.sessionId = this.sessionId;
|
|
72
|
+
cur.host = cur.host || os.hostname();
|
|
73
|
+
writeJsonSafe(this.lockPath, cur);
|
|
74
|
+
} catch(_){}
|
|
75
|
+
}, this.heartbeatMs).unref();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
_clearHeartbeat(){
|
|
79
|
+
if(this._hbTimer){ try { clearInterval(this._hbTimer); } catch(_){} this._hbTimer = null; }
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
_installExitHooks(){
|
|
83
|
+
if (this._exitHooksInstalled) return;
|
|
84
|
+
this._exitHooksInstalled = true;
|
|
85
|
+
const release = () => { try { this.release(); } catch(_){} };
|
|
86
|
+
process.on('SIGINT', release);
|
|
87
|
+
process.on('SIGTERM', release);
|
|
88
|
+
process.on('exit', release);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
release(){
|
|
92
|
+
this._clearHeartbeat();
|
|
93
|
+
if(!this._acquired) return;
|
|
94
|
+
this._acquired = false;
|
|
95
|
+
try { if(fs.existsSync(this.lockPath)) fs.unlinkSync(this.lockPath); } catch(_){}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
module.exports = { SingleSessionGuard };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nexus-fca",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.2",
|
|
4
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": {
|
package/src/listenMqtt.js
CHANGED
|
@@ -68,6 +68,7 @@ function resetBackoff(state){
|
|
|
68
68
|
// Build lazy preflight gating
|
|
69
69
|
function shouldRunPreflight(ctx){
|
|
70
70
|
if(ctx.globalOptions.disablePreflight) return false;
|
|
71
|
+
if(process.env.NEXUS_DISABLE_PREFLIGHT === '1' || process.env.NEXUS_DISABLE_PREFLIGHT === 'true') return false;
|
|
71
72
|
// If we connected successfully within last 10 minutes, skip heavy preflight to reduce surface.
|
|
72
73
|
const now = Date.now();
|
|
73
74
|
const metrics = ctx.health;
|
|
@@ -174,8 +175,9 @@ function listenMqtt(defaultFuncs, api, ctx, globalCallback) {
|
|
|
174
175
|
const backoff = getBackoffState(ctx);
|
|
175
176
|
if(!ctx._mqttDiag) ctx._mqttDiag = { attempts:0, events:[] };
|
|
176
177
|
ctx._mqttDiag.attempts++;
|
|
177
|
-
// Suppress previously noisy test info log (
|
|
178
|
-
|
|
178
|
+
// Suppress previously noisy test info log (visible only if verbose flag enabled or env toggled)
|
|
179
|
+
const verboseMqtt = (ctx.globalOptions && ctx.globalOptions.verboseMqtt) || process.env.NEXUS_VERBOSE_MQTT === '1' || process.env.NEXUS_VERBOSE_MQTT === 'true';
|
|
180
|
+
if (verboseMqtt) {
|
|
179
181
|
log.info('listenMqtt', `Starting Nexus MQTT bridge (attempt=${ctx._mqttDiag.attempts}, backoff=${backoff.current||0}ms)`);
|
|
180
182
|
}
|
|
181
183
|
const runPreflight = shouldRunPreflight(ctx);
|
|
@@ -251,7 +253,7 @@ function listenMqtt(defaultFuncs, api, ctx, globalCallback) {
|
|
|
251
253
|
Upgrade: "websocket",
|
|
252
254
|
"Sec-WebSocket-Version": "13",
|
|
253
255
|
"Accept-Encoding": "gzip, deflate, br",
|
|
254
|
-
|
|
256
|
+
"Accept-Language": (ctx.globalOptions && ctx.globalOptions.acceptLanguage) || process.env.NEXUS_ACCEPT_LANGUAGE || "en-US,en;q=0.9",
|
|
255
257
|
"Sec-WebSocket-Extensions":
|
|
256
258
|
"permessage-deflate; client_max_window_bits",
|
|
257
259
|
},
|
|
@@ -264,6 +266,11 @@ function listenMqtt(defaultFuncs, api, ctx, globalCallback) {
|
|
|
264
266
|
reconnectPeriod: 1000,
|
|
265
267
|
connectTimeout: 5000,
|
|
266
268
|
};
|
|
269
|
+
// Proxy support via option or environment
|
|
270
|
+
if (ctx.globalOptions.proxy === undefined) {
|
|
271
|
+
const envProxy = process.env.NEXUS_PROXY || process.env.HTTPS_PROXY || process.env.HTTP_PROXY;
|
|
272
|
+
if (envProxy) ctx.globalOptions.proxy = envProxy;
|
|
273
|
+
}
|
|
267
274
|
if (ctx.globalOptions.proxy !== undefined) {
|
|
268
275
|
try {
|
|
269
276
|
const agent = new HttpsProxyAgent(ctx.globalOptions.proxy);
|
|
@@ -279,7 +286,7 @@ function listenMqtt(defaultFuncs, api, ctx, globalCallback) {
|
|
|
279
286
|
() => buildStream(options, rawWs, buildProxy()),
|
|
280
287
|
options
|
|
281
288
|
);
|
|
282
|
-
if (
|
|
289
|
+
if (verboseMqtt) {
|
|
283
290
|
log.info('listenMqtt', `MQTT bridge dialing ${host}`);
|
|
284
291
|
}
|
|
285
292
|
const mqttClient = ctx.mqttClient;
|
|
@@ -331,7 +338,7 @@ function listenMqtt(defaultFuncs, api, ctx, globalCallback) {
|
|
|
331
338
|
mqttClient.on("connect", function () {
|
|
332
339
|
resetBackoff(backoff);
|
|
333
340
|
ctx.health.onConnect();
|
|
334
|
-
if (
|
|
341
|
+
if (verboseMqtt) {
|
|
335
342
|
log.info('listenMqtt', `Nexus MQTT bridge established in ${(Date.now()-attemptStartTs)}ms (attempt=${ctx._mqttDiag.attempts}).`);
|
|
336
343
|
}
|
|
337
344
|
if (ctx.globalSafety) { try { ctx.globalSafety.recordEvent(); } catch(_) {} }
|
package/lib/login.js
DELETED
|
File without changes
|