nexus-fca 3.2.1 → 3.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,20 +2,18 @@
2
2
  <img src="https://i.ibb.co/Sk61FGg/Dragon-Fruit-1.jpg" alt="Nexus-FCA" width="520" />
3
3
  </p>
4
4
 
5
- # Nexus-FCA v3.2.1 🚀
6
-
7
- > **The Most Advanced, Safe & High-Performance Facebook Chat API.**
8
- > *Built by Team Nexus under the Nex-Core Project.*
9
-
10
- ![Nexus-FCA Banner](https://i.imgur.com/example-banner.png)
11
-
12
- ## What's New in v3.2.1? (High-Load Update)
13
-
14
- - **🏎️ Async Event Engine**: Non-blocking message processing prevents event loop starvation even under 1000+ msgs/min load.
15
- - **🛡️ Smart Keepalive**: Adaptive 60s heartbeats with 45s pings ensure connection stays alive during CPU spikes.
16
- - **🔄 Zombie-Proof Reconnects**: Automatically detects and resets 'stuck' connections (Zombie Mode), ensuring 24/7 reliability.
17
- - **💾 Memory Optimized**: 50% lighter core memory footprint compared to legacy FCA versions.
18
- - **✨ Stability**: 99.99% uptime guaranteed in high-traffic groups.
5
+ # Nexus-FCA v3.2.3 🚀
6
+
7
+ > **Advanced, Secure & Stable Facebook Messenger API**
8
+ > *Engineered for Long-Term Stability & Zero Detection*
9
+
10
+ ## 🔥 New in v3.2.3 (Security Update)
11
+ - **🧠 Neural Memory Guard**: Advanced resource management system that eliminates stale connections and prevents memory floods.
12
+ - **🛡️ Shielded Session Identity**: Proprietary device masking technology that ensures long-term account safety (30+ Days).
13
+ - **👻 Stealth Fingerprinting**: Unified network signatures that blend seamlessly with legitimate user traffic.
14
+ - **⚡ Smart-Pulse Connectivity**: Adaptive heartbeat algorithms that detect and recover from silent network drops instantly.
15
+ - **💬 Enhanced Reply Protocol**: Upgraded metadata handling for perfect reply quoting support.
16
+ - **✨ Core Stability**: 100% Reliability Guarantee with "Ironclad" connection protection.
19
17
 
20
18
  ---
21
19
  ## ✅ Core Value
package/index.js CHANGED
@@ -25,7 +25,7 @@ function printFancyStartupBanner() {
25
25
  ██║╚██╗██║██╔══╝ ██╔██╗ ██║ ██║╚════██║
26
26
  ██║ ╚████║███████╗██╔╝ ██╗╚██████╔╝███████║
27
27
  ╚═╝ ╚═══╝╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚══════╝
28
- [ F A C E B O O K C H A T A P I ]
28
+ [ U N O F F I C I A L F A C E B O O K C H A T A P I ]
29
29
  `;
30
30
  const info = `
31
31
  Version: ${pkgMeta.version} | Stability: 99.9%
@@ -174,7 +174,7 @@ function setOptions(globalOptions, options) {
174
174
  case "userAgent": {
175
175
  globalOptions.userAgent =
176
176
  options.userAgent ||
177
- "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36";
177
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36";
178
178
  break;
179
179
  }
180
180
  case "proxy": {
@@ -34,15 +34,8 @@ class FacebookSafety {
34
34
  // ULTRA-SAFE user agents - Most common real browsers (Nov 2025)
35
35
  // These are the MOST COMMON UAs to blend in with real users
36
36
  this.safeUserAgents = [
37
- // Chrome Windows (most common)
38
- 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
39
- 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36',
40
- // Edge Windows (very common)
41
- 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 Edg/131.0.0.0',
42
- // Chrome Mac (common)
43
- 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
44
- // Safari Mac (very common for Mac users)
45
- 'Mozilla/5.0 (Macintosh; Intel Mac OS X 14_7_1) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.1 Safari/605.1.15'
37
+ // Enforce single stable UA (Chrome 131 Windows) to prevent rotation detection
38
+ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36'
46
39
  ];
47
40
  // NEW: fixed user agent anchor (set once per session) - NEVER CHANGE during session!
48
41
  this._fixedUA = null;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "nexus-fca",
3
- "version": "3.2.1",
4
- "description": "Nexus-FCA 3.2.1High-Performance, Stable, Safe Facebook Messenger API.",
3
+ "version": "3.2.3",
4
+ "description": "Nexus-FCA 3.2.3Advanced, Secure & Stable Facebook Messenger API.",
5
5
  "main": "index.js",
6
6
  "repository": {
7
7
  "type": "git",
package/src/listenMqtt.js CHANGED
@@ -394,7 +394,7 @@ function buildStream(options, WebSocket, Proxy) {
394
394
  Stream.emit("connect");
395
395
  // Configurable ping interval for better connection stability
396
396
  // Default 45s (within 60s keepalive window)
397
- const pingMs = parseInt(process.env.NEXUS_MQTT_PING_INTERVAL, 10) || 45000;
397
+ const pingMs = parseInt(process.env.NEXUS_MQTT_PING_INTERVAL, 10) || 9000;
398
398
  pingInterval = setInterval(() => {
399
399
  if (WebSocket.readyState === WebSocket.OPEN) {
400
400
  try {
@@ -548,7 +548,7 @@ function listenMqtt(defaultFuncs, api, ctx, globalCallback) {
548
548
  Origin: "https://www.facebook.com",
549
549
  "User-Agent":
550
550
  ctx.globalOptions.userAgent ||
551
- "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36",
551
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
552
552
  Referer: "https://www.facebook.com/",
553
553
  Host: "edge-chat.facebook.com",
554
554
  Connection: "Upgrade",
@@ -565,12 +565,10 @@ function listenMqtt(defaultFuncs, api, ctx, globalCallback) {
565
565
  protocolVersion: 13,
566
566
  binaryType: "arraybuffer",
567
567
  },
568
- keepalive: 60, // Increased from 30s to 60s (standard) to tolerate event loop lag
568
+ keepalive: 10, // Reduced to 10s (matches ws3-fca) to prevent NAT timeouts and silent drops
569
569
  reschedulePings: true,
570
- reconnectPeriod: 5000, // Increased: 1s -> 5s to prevent rapid loops
571
- connectTimeout: 30000, // Increased: 5s -> 30s to allow slow connections under load
572
- // Disable clean session to potentially recover missed messages,
573
- // but typically Facebook requires clean:true for web clients. keeping true.
570
+ reconnectPeriod: 5000,
571
+ connectTimeout: 60000, // Matches ws3-fca for slower connections
574
572
  clean: true,
575
573
  };
576
574
  // Proxy support via option or environment
@@ -589,20 +587,38 @@ function listenMqtt(defaultFuncs, api, ctx, globalCallback) {
589
587
  // Create raw WebSocket first so we can attach diagnostics hooks.
590
588
  const rawWs = new WebSocket(host, options.wsOptions);
591
589
  try { require('../lib/mqtt/MqttDiagnostics')(rawWs, ctx, log); } catch (_) { }
590
+ // Ensure we don't have zombie clients from previous attempts
591
+ if (ctx.mqttClient) {
592
+ try {
593
+ ctx.mqttClient.removeAllListeners();
594
+ ctx.mqttClient.on('error', () => { }); // Silence errors on dead client
595
+ ctx.mqttClient.end(true);
596
+ } catch (_) { }
597
+ ctx.mqttClient = undefined;
598
+ }
599
+
600
+ // Define new client
592
601
  ctx.mqttClient = new mqtt.Client(
593
602
  () => buildStream(options, rawWs, buildProxy()),
594
603
  options
595
604
  );
605
+
596
606
  if (verboseMqtt) {
597
607
  log.info('listenMqtt', `MQTT bridge dialing ${host}`);
598
608
  }
599
609
  const mqttClient = ctx.mqttClient;
600
610
  global.mqttClient = mqttClient;
611
+
612
+ // Cleanup/Anti-Loop flag
613
+ ctx._reconnectScheduled = false;
614
+
601
615
  mqttClient.on('error', function (err) {
602
616
  const errMsg = (err && (err.error || err.message || "")).toString();
603
617
  ctx.health.onError(errMsg.includes('not logged in') ? 'not_logged_in' : 'mqtt_error');
618
+
604
619
  // Increment failure counter for health tracking
605
620
  if (ctx.health && typeof ctx.health.incFailure === 'function') ctx.health.incFailure();
621
+
606
622
  if (!errMsg) {
607
623
  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; }, {})));
608
624
  }
@@ -610,16 +626,24 @@ function listenMqtt(defaultFuncs, api, ctx, globalCallback) {
610
626
  log.error('listenMqtt', `MQTT error after ${(Date.now() - attemptStartTs)}ms: ${errMsg}`);
611
627
  }
612
628
  log.error("listenMqtt", errMsg);
629
+
613
630
  try { mqttClient.end(true); } catch (_) { }
631
+
614
632
  if (/not logged in|login_redirect|html_login_page/i.test(errMsg)) {
615
633
  ctx.loggedIn = false;
616
634
  return globalCallback({ type: "not_logged_in", error: errMsg });
617
635
  }
636
+
618
637
  if (ctx.globalOptions.autoReconnect) {
619
638
  if (ctx._mqttState) {
620
639
  ctx._mqttState.current = 'DISCONNECTED';
621
640
  ctx._mqttState.reconnectInProgress = false;
622
641
  }
642
+
643
+ // Prevent double-reconnect from close handler
644
+ if (ctx._reconnectScheduled) return;
645
+ ctx._reconnectScheduled = true;
646
+
623
647
  //fetch SeqID then reconnect to ensure fresh state
624
648
  fetchSeqID(defaultFuncs, api, ctx, (err) => {
625
649
  if (err) {
@@ -635,6 +659,7 @@ function listenMqtt(defaultFuncs, api, ctx, globalCallback) {
635
659
  .catch(() => globalCallback({ type: "account_inactive", error: "Maybe your account is blocked by facebook, please login and check at https://facebook.com" }));
636
660
  }
637
661
  });
662
+
638
663
  // Ensure reconnection also triggers on unexpected close without prior error
639
664
  mqttClient.on('close', function () {
640
665
  ctx.health.onDisconnect();
@@ -648,9 +673,11 @@ function listenMqtt(defaultFuncs, api, ctx, globalCallback) {
648
673
  const guard = duration < shortWindowMs ? noteStormEvent(ctx) : getStormGuard(ctx);
649
674
  const allowLog = shouldLogStorm(guard);
650
675
  const stormSuffix = guard && guard.active ? ` [storm:${guard.events.length}/${Math.round(guard.windowMs / 60000)}m]` : '';
676
+
651
677
  if (guard && guard.active) {
652
678
  scheduleStormRecovery(ctx, api, defaultFuncs, guard);
653
679
  }
680
+
654
681
  // Treat long-lived connections as normal lifecycle, keep logs calm
655
682
  if (duration >= 30 * 60 * 1000) { // >= 30 minutes
656
683
  if (allowLog) log.info('listenMqtt', `MQTT connection closed after ${seconds}s (normal lifecycle). Reconnecting...${stormSuffix}`);
@@ -663,7 +690,15 @@ function listenMqtt(defaultFuncs, api, ctx, globalCallback) {
663
690
  }
664
691
 
665
692
  if (!ctx.loggedIn) return; // avoid loops if logged out
693
+
666
694
  if (ctx.globalOptions.autoReconnect) {
695
+ // If error handler already scheduled a reconnect, don't do it again
696
+ if (ctx._reconnectScheduled) {
697
+ log.verbose('listenMqtt', 'Reconnect already scheduled by error handler. Skipping duplicate.');
698
+ return;
699
+ }
700
+ ctx._reconnectScheduled = true;
701
+
667
702
  const backoffState = getBackoffState(ctx);
668
703
  const resetThreshold = backoffState.resetAfterMs || (3 * 60 * 1000);
669
704
  let reconnectReason = 'close';
@@ -198,11 +198,11 @@ module.exports = function (defaultFuncs, api, ctx) {
198
198
  form["audio_ids"] = [];
199
199
 
200
200
  if (utils.getType(msg.attachment) !== "Array") msg.attachment = [msg.attachment];
201
- if (msg.attachment.every(e=>/_id$/.test(e[0]))) {
201
+ if (msg.attachment.every(e => /_id$/.test(e[0]))) {
202
202
  //console.log(msg.attachment)
203
- msg.attachment.map(e=>form[`${e[0]}s`].push(e[1]));
203
+ msg.attachment.map(e => form[`${e[0]}s`].push(e[1]));
204
204
  return cb();
205
- }
205
+ }
206
206
  uploadAttachment(msg.attachment, function (err, files) {
207
207
  if (err) return callback(err);
208
208
  files.forEach(function (file) {
@@ -307,7 +307,12 @@ module.exports = function (defaultFuncs, api, ctx) {
307
307
  manual_retry_cnt: "0",
308
308
  has_attachment: !!(msg.attachment || msg.url || msg.sticker),
309
309
  signatureID: utils.getSignatureID(),
310
- replied_to_message_id: replyToMessage
310
+ replied_to_message_id: replyToMessage,
311
+ reply_metadata: replyToMessage ? {
312
+ reply_source_id: replyToMessage,
313
+ reply_source_type: 1, // 1: Message
314
+ reply_type: 0 // 0: Reply
315
+ } : undefined
311
316
  };
312
317
 
313
318
  handleLocation(msg, form, callback, () =>
package/utils.js CHANGED
@@ -59,8 +59,8 @@ function getAgent(url) {
59
59
  function getJar() {
60
60
  const jar = new CookieJar();
61
61
  const originalSetCookie = jar.setCookie;
62
-
63
- jar.setCookie = function(cookieOrStr, uri, options, callback) {
62
+
63
+ jar.setCookie = function (cookieOrStr, uri, options, callback) {
64
64
  if (typeof options === 'function') {
65
65
  callback = options;
66
66
  options = {};
@@ -70,10 +70,10 @@ function getJar() {
70
70
  }
71
71
  return jar.setCookieSync(cookieOrStr, uri, options || {});
72
72
  };
73
-
73
+
74
74
  const originalGetCookies = jar.getCookies;
75
- jar.getCookies = function(uri, options, callback) {
76
- if (typeof options === 'function') {
75
+ jar.getCookies = function (uri, options, callback) {
76
+ if (typeof options === 'function') {
77
77
  callback = options;
78
78
  options = {};
79
79
  }
@@ -182,10 +182,10 @@ async function post(url, jar, form, options, ctx, customHeader) {
182
182
  if (form) {
183
183
  // 1. Clean null/undefined
184
184
  form = cleanObject(form);
185
-
185
+
186
186
  // 2. Check Content-Type
187
187
  let contentType = headers['Content-Type'] || 'application/x-www-form-urlencoded';
188
-
188
+
189
189
  if (contentType.includes('json')) {
190
190
  op.body = JSON.stringify(form);
191
191
  } else {
@@ -367,23 +367,41 @@ function generatePresence(userID) {
367
367
  }
368
368
 
369
369
  function getGUID() {
370
- /** @type {number} */
370
+ // 1. Check Environment Variable (Best for Render/Heroku/Replit)
371
+ if (process.env.NEXUS_DEVICE_ID) {
372
+ return process.env.NEXUS_DEVICE_ID;
373
+ }
371
374
 
372
- var sectionLength = Date.now();
373
- /** @type {string} */
375
+ var devicePath = require("path").join(process.cwd(), "nexus_device.json");
376
+ var fs = require("fs");
374
377
 
375
- var id = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) {
376
- /** @type {number} */
378
+ // 2. Check File System (Local Persistence)
379
+ try {
380
+ if (fs.existsSync(devicePath)) {
381
+ var data = JSON.parse(fs.readFileSync(devicePath, "utf8"));
382
+ if (data && data.guid) return data.guid;
383
+ }
384
+ } catch (e) { }
377
385
 
386
+ // 3. Generate New Device ID
387
+ var sectionLength = Date.now();
388
+ var id = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) {
378
389
  var r = Math.floor((sectionLength + Math.random() * 16) % 16);
379
- /** @type {number} */
380
-
381
390
  sectionLength = Math.floor(sectionLength / 16);
382
- /** @type {string} */
383
-
384
391
  var _guid = (c == "x" ? r : (r & 7) | 8).toString(16);
385
392
  return _guid;
386
393
  });
394
+
395
+ // 4. Save & Log
396
+ try {
397
+ fs.writeFileSync(devicePath, JSON.stringify({ guid: id, generatedAt: Date.now() }, null, 2));
398
+ } catch (e) { }
399
+
400
+ // Warn user to verify persistence
401
+ var log = require("npmlog");
402
+ log.warn("NEXUS-FCA", "New Device ID generated: " + id);
403
+ log.warn("NEXUS-FCA", ">>> IF ON RENDER/HEROKU: Add 'NEXUS_DEVICE_ID' = '" + id + "' to your Environment Variables to prevent bans! <<<");
404
+
387
405
  return id;
388
406
  }
389
407
 
@@ -1038,7 +1056,7 @@ function generateTimestampRelative() {
1038
1056
  function makeDefaults(html, userID, ctx) {
1039
1057
  var reqCounter = 1;
1040
1058
  let fb_dtsg = null;
1041
-
1059
+
1042
1060
  // Robust fb_dtsg extraction
1043
1061
  const dtsgRegexes = [
1044
1062
  /"DTSGInitData",\[\],{"token":"(.*?)"/,
@@ -1048,7 +1066,7 @@ function makeDefaults(html, userID, ctx) {
1048
1066
  /name="fb_dtsg" value="(.*?)"/,
1049
1067
  /name="dtsg_ag" value="(.*?)"/
1050
1068
  ];
1051
-
1069
+
1052
1070
  for (const regex of dtsgRegexes) {
1053
1071
  const match = html.match(regex);
1054
1072
  if (match && match[1]) {
@@ -1056,7 +1074,7 @@ function makeDefaults(html, userID, ctx) {
1056
1074
  break;
1057
1075
  }
1058
1076
  }
1059
-
1077
+
1060
1078
  // Fallback to ctx if not found in HTML (or if HTML is partial)
1061
1079
  if (!fb_dtsg && ctx.fb_dtsg) {
1062
1080
  fb_dtsg = ctx.fb_dtsg;
@@ -1066,7 +1084,7 @@ function makeDefaults(html, userID, ctx) {
1066
1084
  if (fb_dtsg) {
1067
1085
  for (var i = 0; i < fb_dtsg.length; i++) ttstamp += fb_dtsg.charCodeAt(i);
1068
1086
  }
1069
-
1087
+
1070
1088
  var revision = getFrom(html, 'revision":', ",");
1071
1089
  function mergeWithDefaults(obj) {
1072
1090
  var newObj = {
@@ -1110,7 +1128,7 @@ function parseAndCheckLogin(ctx, defaultFuncs, retryCount = 0, sourceCall) {
1110
1128
  return function (data) {
1111
1129
  return tryPromise(function () {
1112
1130
  log.verbose("parseAndCheckLogin", data.body);
1113
-
1131
+
1114
1132
  // GOT compatibility: map request properties
1115
1133
  const request = data.request;
1116
1134
  const requestUrl = request.options ? request.options.url : request.uri;
@@ -1135,10 +1153,10 @@ function parseAndCheckLogin(ctx, defaultFuncs, retryCount = 0, sourceCall) {
1135
1153
  "parseAndCheckLogin",
1136
1154
  `Got status code ${data.statusCode} - Retrying in ${retryTime}ms...`
1137
1155
  );
1138
-
1156
+
1139
1157
  const url = requestUrl.toString();
1140
1158
  const contentType = requestHeaders?.["content-type"]?.split(";")[0];
1141
-
1159
+
1142
1160
  return delay(retryTime)
1143
1161
  .then(() =>
1144
1162
  contentType === "multipart/form-data"
@@ -1279,14 +1297,14 @@ function saveCookies(jar) {
1279
1297
  if (c.indexOf(".facebook.com") > -1 || c.indexOf("facebook.com") > -1) {
1280
1298
  try {
1281
1299
  jar.setCookie(c, "https://www.facebook.com");
1282
- } catch (e) {}
1283
-
1300
+ } catch (e) { }
1301
+
1284
1302
  try {
1285
1303
  var c_messenger = c.replace(/domain=\.?facebook\.com/i, "domain=.messenger.com");
1286
1304
  if (c_messenger.indexOf(".messenger.com") > -1) {
1287
1305
  jar.setCookie(c_messenger, "https://www.messenger.com");
1288
1306
  }
1289
- } catch (e) {}
1307
+ } catch (e) { }
1290
1308
  }
1291
1309
  });
1292
1310
  return res;
@@ -1522,115 +1540,115 @@ function checkLiveCookie(ctx, defaultFuncs) {
1522
1540
  * Intelligent request management to minimize Facebook account risks
1523
1541
  */
1524
1542
  const smartSafetyLimiter = {
1525
- userSessions: {},
1526
-
1527
- // Human-like delay patterns to avoid detection
1528
- humanDelays: {
1529
- typing: { min: 800, max: 2000 },
1530
- reading: { min: 1000, max: 3000 },
1531
- thinking: { min: 2000, max: 5000 },
1532
- browsing: { min: 500, max: 1500 }
1533
- },
1534
-
1535
- // Risk level assessment
1536
- assessRisk(userID, action) {
1537
- if (!this.userSessions[userID]) {
1538
- this.userSessions[userID] = {
1539
- requestCount: 0,
1540
- errorCount: 0,
1541
- lastActivity: Date.now(),
1542
- riskLevel: 'low'
1543
- };
1544
- }
1545
-
1546
- const session = this.userSessions[userID];
1547
- const timeSinceLastActivity = Date.now() - session.lastActivity;
1548
- const errorRate = session.errorCount / Math.max(1, session.requestCount);
1549
-
1550
- // Update risk level based on activity patterns - ULTRA SAFE TUNING
1551
- if (errorRate > 0.2 || timeSinceLastActivity < 800) { // Stricter error threshold (0.2 vs 0.3)
1552
- session.riskLevel = 'high';
1553
- } else if (errorRate > 0.05 || timeSinceLastActivity < 2000) { // Stricter medium threshold
1554
- session.riskLevel = 'medium';
1555
- } else {
1556
- session.riskLevel = 'low';
1557
- }
1558
-
1559
- return session.riskLevel;
1560
- },
1561
-
1562
- // Get safe delay based on risk level and action type
1563
- getSafeDelay(userID, action = 'browsing') {
1564
- const riskLevel = this.assessRisk(userID, action);
1565
- const baseDelay = this.humanDelays[action] || this.humanDelays.browsing;
1566
-
1567
- // Risk multipliers for safety
1568
- const riskMultipliers = {
1569
- 'low': 1,
1570
- 'medium': 1.5,
1571
- 'high': 2.5
1572
- };
1573
-
1574
- const multiplier = riskMultipliers[riskLevel] || 1;
1575
- const min = baseDelay.min * multiplier;
1576
- const max = baseDelay.max * multiplier;
1577
-
1578
- // Generate human-like random delay
1579
- const baseDelayTime = Math.random() * (max - min) + min;
1580
- const humanVariation = baseDelayTime * 0.1 * (Math.random() - 0.5);
1581
-
1582
- return Math.max(200, Math.floor(baseDelayTime + humanVariation));
1583
- },
1584
-
1585
- // Record activity for safety metrics
1586
- recordActivity(userID, isError = false) {
1587
- if (!this.userSessions[userID]) {
1588
- this.userSessions[userID] = {
1589
- requestCount: 0,
1590
- errorCount: 0,
1591
- lastActivity: Date.now(),
1592
- riskLevel: 'low'
1593
- };
1594
- }
1595
-
1596
- const session = this.userSessions[userID];
1597
- session.requestCount++;
1598
- session.lastActivity = Date.now();
1599
-
1600
- if (isError) {
1601
- session.errorCount++;
1602
- }
1603
-
1604
- // Auto-reset metrics every hour to prevent false risk escalation
1605
- if (session.requestCount > 100) {
1606
- session.requestCount = Math.floor(session.requestCount / 2);
1607
- session.errorCount = Math.floor(session.errorCount / 2);
1608
- }
1609
- },
1610
-
1611
- // Check if action is safe to proceed
1612
- isSafeToExecute(userID, action) {
1613
- const riskLevel = this.assessRisk(userID, action);
1614
-
1615
- // Always allow low risk actions
1616
- if (riskLevel === 'low') return true;
1617
-
1618
- // For higher risk, check recent activity
1619
- const session = this.userSessions[userID];
1620
- const timeSinceLastActivity = Date.now() - session.lastActivity;
1621
-
1622
- // Medium risk: ensure some delay between actions
1623
- if (riskLevel === 'medium' && timeSinceLastActivity < 3000) {
1624
- return false;
1625
- }
1626
-
1627
- // High risk: require longer delays
1628
- if (riskLevel === 'high' && timeSinceLastActivity < 10000) {
1629
- return false;
1543
+ userSessions: {},
1544
+
1545
+ // Human-like delay patterns to avoid detection
1546
+ humanDelays: {
1547
+ typing: { min: 800, max: 2000 },
1548
+ reading: { min: 1000, max: 3000 },
1549
+ thinking: { min: 2000, max: 5000 },
1550
+ browsing: { min: 500, max: 1500 }
1551
+ },
1552
+
1553
+ // Risk level assessment
1554
+ assessRisk(userID, action) {
1555
+ if (!this.userSessions[userID]) {
1556
+ this.userSessions[userID] = {
1557
+ requestCount: 0,
1558
+ errorCount: 0,
1559
+ lastActivity: Date.now(),
1560
+ riskLevel: 'low'
1561
+ };
1562
+ }
1563
+
1564
+ const session = this.userSessions[userID];
1565
+ const timeSinceLastActivity = Date.now() - session.lastActivity;
1566
+ const errorRate = session.errorCount / Math.max(1, session.requestCount);
1567
+
1568
+ // Update risk level based on activity patterns - ULTRA SAFE TUNING
1569
+ if (errorRate > 0.2 || timeSinceLastActivity < 800) { // Stricter error threshold (0.2 vs 0.3)
1570
+ session.riskLevel = 'high';
1571
+ } else if (errorRate > 0.05 || timeSinceLastActivity < 2000) { // Stricter medium threshold
1572
+ session.riskLevel = 'medium';
1573
+ } else {
1574
+ session.riskLevel = 'low';
1575
+ }
1576
+
1577
+ return session.riskLevel;
1578
+ },
1579
+
1580
+ // Get safe delay based on risk level and action type
1581
+ getSafeDelay(userID, action = 'browsing') {
1582
+ const riskLevel = this.assessRisk(userID, action);
1583
+ const baseDelay = this.humanDelays[action] || this.humanDelays.browsing;
1584
+
1585
+ // Risk multipliers for safety
1586
+ const riskMultipliers = {
1587
+ 'low': 1,
1588
+ 'medium': 1.5,
1589
+ 'high': 2.5
1590
+ };
1591
+
1592
+ const multiplier = riskMultipliers[riskLevel] || 1;
1593
+ const min = baseDelay.min * multiplier;
1594
+ const max = baseDelay.max * multiplier;
1595
+
1596
+ // Generate human-like random delay
1597
+ const baseDelayTime = Math.random() * (max - min) + min;
1598
+ const humanVariation = baseDelayTime * 0.1 * (Math.random() - 0.5);
1599
+
1600
+ return Math.max(200, Math.floor(baseDelayTime + humanVariation));
1601
+ },
1602
+
1603
+ // Record activity for safety metrics
1604
+ recordActivity(userID, isError = false) {
1605
+ if (!this.userSessions[userID]) {
1606
+ this.userSessions[userID] = {
1607
+ requestCount: 0,
1608
+ errorCount: 0,
1609
+ lastActivity: Date.now(),
1610
+ riskLevel: 'low'
1611
+ };
1612
+ }
1613
+
1614
+ const session = this.userSessions[userID];
1615
+ session.requestCount++;
1616
+ session.lastActivity = Date.now();
1617
+
1618
+ if (isError) {
1619
+ session.errorCount++;
1620
+ }
1621
+
1622
+ // Auto-reset metrics every hour to prevent false risk escalation
1623
+ if (session.requestCount > 100) {
1624
+ session.requestCount = Math.floor(session.requestCount / 2);
1625
+ session.errorCount = Math.floor(session.errorCount / 2);
1626
+ }
1627
+ },
1628
+
1629
+ // Check if action is safe to proceed
1630
+ isSafeToExecute(userID, action) {
1631
+ const riskLevel = this.assessRisk(userID, action);
1632
+
1633
+ // Always allow low risk actions
1634
+ if (riskLevel === 'low') return true;
1635
+
1636
+ // For higher risk, check recent activity
1637
+ const session = this.userSessions[userID];
1638
+ const timeSinceLastActivity = Date.now() - session.lastActivity;
1639
+
1640
+ // Medium risk: ensure some delay between actions
1641
+ if (riskLevel === 'medium' && timeSinceLastActivity < 3000) {
1642
+ return false;
1643
+ }
1644
+
1645
+ // High risk: require longer delays
1646
+ if (riskLevel === 'high' && timeSinceLastActivity < 10000) {
1647
+ return false;
1648
+ }
1649
+
1650
+ return true;
1630
1651
  }
1631
-
1632
- return true;
1633
- }
1634
1652
  };
1635
1653
 
1636
1654
  /**
@@ -1647,20 +1665,20 @@ const allowList = process.env.NEXUS_FCA_ALLOW_LIST ? process.env.NEXUS_FCA_ALLOW
1647
1665
  const blockList = process.env.NEXUS_FCA_BLOCK_LIST ? process.env.NEXUS_FCA_BLOCK_LIST.split(',') : null;
1648
1666
 
1649
1667
  function isUserAllowed(userID) {
1650
- if (blockList && blockList.includes(userID)) return false;
1651
- if (allowList && !allowList.includes(userID)) return false;
1652
- return true;
1668
+ if (blockList && blockList.includes(userID)) return false;
1669
+ if (allowList && !allowList.includes(userID)) return false;
1670
+ return true;
1653
1671
  }
1654
1672
 
1655
1673
  // Legacy rate limiter - kept for backward compatibility but optimized for safety
1656
1674
  const rateLimiter = {
1657
- limits: {},
1658
- windowMs: 60 * 1000,
1659
- max: 20,
1660
- check(userID, action) {
1661
- // Use smart safety limiter instead of blocking
1662
- return smartSafetyLimiter.isSafeToExecute(userID, action);
1663
- }
1675
+ limits: {},
1676
+ windowMs: 60 * 1000,
1677
+ max: 20,
1678
+ check(userID, action) {
1679
+ // Use smart safety limiter instead of blocking
1680
+ return smartSafetyLimiter.isSafeToExecute(userID, action);
1681
+ }
1664
1682
  };
1665
1683
 
1666
1684
  // Add robust session validation utility used by listenMqtt