nexus-fca 3.0.2 → 3.0.4

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.
@@ -0,0 +1,102 @@
1
+ # Advanced Configuration Options for Nexus-FCA
2
+
3
+ This document outlines additional configuration options available to optimize your Nexus-FCA experience.
4
+
5
+ ## Environment Variables
6
+
7
+ Nexus-FCA supports various environment variables to fine-tune its behavior:
8
+
9
+ ### Session Management
10
+
11
+ ```
12
+ # Device persistence
13
+ NEXUS_PERSISTENT_DEVICE=true # Enable consistent device fingerprinting (default)
14
+ NEXUS_DEVICE_FILE=./device.json # Custom path to device profile file
15
+
16
+ # Single-session guard (built-in)
17
+ # Toggle and configure SingleSessionGuard
18
+ NEXUS_SESSION_LOCK_ENABLED=true # Enable/disable lock globally (default true)
19
+ NEXUS_SESSION_LOCK_PATH=./lock # Path to session lock file used by SingleSessionGuard
20
+ NEXUS_FORCE_LOCK=false # Force acquire lock even if lock exists
21
+
22
+ # Region control
23
+ NEXUS_REGION=NA # Set fixed region (NA, EU, AS)
24
+ ```
25
+
26
+ ### Cookie Management
27
+
28
+ ```
29
+ # Cookie refresher settings
30
+ NEXUS_COOKIE_REFRESH_INTERVAL=1800000 # Refresh interval in ms (default: 30 minutes)
31
+ NEXUS_COOKIE_EXPIRY_DAYS=90 # Days to extend cookie expiry
32
+ NEXUS_COOKIE_BACKUP_PATH=./backups # Path to store cookie backups
33
+ NEXUS_COOKIE_MAX_BACKUPS=5 # Maximum number of cookie backups to keep
34
+ ```
35
+
36
+ ### Connection Settings
37
+
38
+ ```
39
+ # Connection resilience
40
+ NEXUS_MAX_RETRIES=5 # Maximum connection retry attempts
41
+ NEXUS_RETRY_DELAY=5000 # Base delay between retries (ms)
42
+ NEXUS_KEEPALIVE_INTERVAL=300000 # Keepalive interval (ms)
43
+ ```
44
+
45
+ ### Safety Features
46
+
47
+ ```
48
+ # Safety controls
49
+ NEXUS_FCA_SAFE_MODE=1 # Enable basic safety measures
50
+ NEXUS_FCA_ULTRA_SAFE_MODE=1 # Enable maximum safety for stability
51
+ NEXUS_DELIVERY_TIMEOUT=60000 # Message delivery timeout (ms)
52
+ NEXUS_AUTO_MARK_READ=false # Automatically mark messages as read
53
+ ```
54
+
55
+ ## API Configuration Options
56
+
57
+ When initializing the API, you can pass additional options:
58
+
59
+ ```javascript
60
+ const api = await login({
61
+ appState: require('./appstate.json'),
62
+
63
+ // Session stability options
64
+ persistentDevice: true, // Use consistent device fingerprinting
65
+ deviceFilePath: './device.json', // Custom device profile path
66
+ // Single-session guard (optional toggle)
67
+ sessionLockEnabled: true, // or control via env NEXUS_SESSION_LOCK_ENABLED
68
+
69
+ // Connection options
70
+ region: 'NA', // Set fixed region
71
+ userAgent: 'custom-user-agent', // Override user agent
72
+
73
+ // Cookie management
74
+ cookieRefreshInterval: 1800000, // Cookie refresh interval (ms)
75
+ cookieExpiryDays: 90, // Days to extend cookie expiry
76
+ cookieBackupEnabled: true, // Enable cookie backups
77
+ cookieMaxBackups: 5, // Maximum cookie backups
78
+
79
+ // Safety options
80
+ forceLogout: true, // Force logout any other sessions
81
+ safetyLevel: 2 // Safety level (0-2)
82
+ });
83
+ ```
84
+
85
+ ## Best Practices
86
+
87
+ For the most stable experience:
88
+
89
+ 1. **Always enable persistent device**: Use the same device fingerprint across restarts
90
+ 2. **Enable session locking**: Prevent multiple instances from using the same account
91
+ 3. **Set a fixed region**: Use the `NEXUS_REGION` environment variable
92
+ 4. **Use a modern user agent**: Let the system select an appropriate one
93
+ 5. **Keep cookie refreshing enabled**: Maintains fresh cookies
94
+ 6. **Back up working appstate files**: Keep copies of working sessions
95
+
96
+ ## Security Considerations
97
+
98
+ - Store sensitive information like appstate files securely
99
+ - Use environment variables for credentials instead of hardcoding
100
+ - Use `.env` files in development (gitignored)
101
+ - Consider using a dedicated account for automation
102
+ - Respect Facebook's terms of service and rate limits
@@ -0,0 +1,87 @@
1
+ # Cookie Expiry and Session Management Guide
2
+
3
+ This document explains how Nexus-FCA handles cookie management to prevent rapid session expiry and improve overall stability.
4
+
5
+ ## Understanding the Issue
6
+
7
+ Facebook cookies may expire rapidly (within hours) due to several reasons:
8
+
9
+ 1. **Missing Expiry Dates**: Some cookies don't have proper expiry timestamps
10
+ 2. **Short-lived Sessions**: Facebook may issue cookies with short expiry times
11
+ 3. **Device Inconsistency**: Using different device profiles between sessions
12
+ 4. **Multiple Sessions**: Running multiple bots with the same account
13
+ 5. **Security Triggers**: Suspicious activities triggering Facebook safety mechanisms
14
+
15
+ ## Nexus-FCA Solution
16
+
17
+ Nexus-FCA implements a robust multi-layered approach to maintain session stability:
18
+
19
+ ### 1. Cookie Expiry Extension
20
+
21
+ The system automatically extends cookie expiry dates:
22
+
23
+ - Critical cookies (`c_user`, `xs`, `fr`, `datr`, `sb`) are extended to 90 days
24
+ - All cookies are validated at startup and fixed if needed
25
+ - Proper expiry format is enforced using the standard RFC format
26
+
27
+ ### 2. Persistent Device Profile
28
+
29
+ To maintain consistency across restarts:
30
+
31
+ - Device fingerprints (device ID, user agent, family device ID) are preserved
32
+ - The same device profile is used for all requests
33
+ - Configuration option: `persistentDevice: true` (enabled by default)
34
+
35
+ ### 3. Cookie Refresher
36
+
37
+ An active background service keeps your session fresh:
38
+
39
+ - Periodically refreshes cookies (every 30 minutes)
40
+ - Makes lightweight requests to maintain session activity
41
+ - Extends cookie expiry dates automatically
42
+ - Creates backups of working sessions
43
+
44
+ ### 4. Single Session Guard
45
+
46
+ Prevents multiple instances from using the same account:
47
+
48
+ - File-based locking mechanism prevents duplicate sessions
49
+ - Ensures Facebook sees consistent device information
50
+ - Configuration via `NEXUS_SESSION_LOCK_PATH` and `NEXUS_FORCE_LOCK`
51
+
52
+ ## Best Practices
53
+
54
+ For maximum session stability:
55
+
56
+ 1. **Use Persistent Device**: Always enable persistentDevice (default)
57
+ 2. **Keep Proxy Consistent**: Use the same proxy for extended periods
58
+ 3. **Set Region**: Use `NEXUS_REGION` to maintain a consistent region
59
+ 4. **Avoid Multiple Instances**: Never run multiple bots with the same account
60
+ 5. **Regular Backups**: Keep backups of working appstate files
61
+
62
+ ## Configuration
63
+
64
+ Key environment variables for session stability:
65
+
66
+ ```
67
+ # Session stability
68
+ NEXUS_PERSISTENT_DEVICE=true # Keep device fingerprint consistent (default)
69
+ NEXUS_DEVICE_FILE=./device.json # Custom device profile path
70
+ NEXUS_SESSION_LOCK_PATH=./lock # Single session lock file location
71
+ NEXUS_REGION=NA # Fixed region (NA, EU, AS, etc.)
72
+
73
+ # Safety settings
74
+ NEXUS_FCA_ULTRA_SAFE_MODE=1 # Maximum safety for stability
75
+ ```
76
+
77
+ ## Troubleshooting
78
+
79
+ If you still experience session expiry:
80
+
81
+ 1. **Check Logs**: Look for warnings about cookie expiry or checkpoint
82
+ 2. **Monitor Activity**: High message volume can trigger Facebook limits
83
+ 3. **Fresh Login**: Generate a completely new appstate if needed
84
+ 4. **API Limits**: Respect Facebook rate limits with delay between actions
85
+ 5. **Secure Network**: Use residential IPs rather than datacenter IPs
86
+
87
+ The new cookie management system should significantly improve stability and prevent the rapid expiry issues previously encountered.
package/index.js CHANGED
@@ -7,6 +7,8 @@ const { promises: fsPromises, readFileSync } = require('fs');
7
7
  const fs = require('fs');
8
8
  const axios = require('axios');
9
9
  const path = require('path');
10
+ const crypto = require("crypto");
11
+ const { v4: uuidv4 } = require("uuid");
10
12
  const models = require("./lib/database/models");
11
13
  const logger = require("./lib/logger");
12
14
  const { safeMode, ultraSafeMode, smartSafetyLimiter, isUserAllowed } = require('./utils'); // Enhanced safety system
@@ -52,6 +54,8 @@ const { User } = require('./lib/message/User');
52
54
  // Advanced Safety Module - Minimizes ban/lock/checkpoint rates
53
55
  const FacebookSafety = require('./lib/safety/FacebookSafety');
54
56
  const { SingleSessionGuard } = require('./lib/safety/SingleSessionGuard');
57
+ const { CookieRefresher } = require('./lib/safety/CookieRefresher');
58
+ const { CookieManager } = require('./lib/safety/CookieManager');
55
59
 
56
60
  // Core compatibility imports
57
61
  const MqttManager = require('./lib/mqtt/MqttManager');
@@ -459,7 +463,14 @@ function loginHelper(appState, email, password, globalOptions, callback, prCallb
459
463
  }
460
464
 
461
465
  try {
462
- appState.forEach(c => {
466
+ // Fix any cookie expiry issues before setting
467
+ const fixedAppState = CookieManager.fixCookieExpiry(appState, {
468
+ defaultExpiryDays: 90,
469
+ criticalExpiryDays: 90,
470
+ refreshExisting: true
471
+ });
472
+
473
+ fixedAppState.forEach(c => {
463
474
  const str = `${c.key}=${c.value}; expires=${c.expires}; domain=${c.domain}; path=${c.path};`;
464
475
  jar.setCookie(str, "http://" + c.domain);
465
476
  });
@@ -520,7 +531,48 @@ function loginHelper(appState, email, password, globalOptions, callback, prCallb
520
531
  logger('✅ Session authenticated successfully', 'info');
521
532
  // Initialize safety monitoring
522
533
  globalSafety.startMonitoring(ctx, api);
523
- try { globalSafety.startDynamicSystems(); } catch(_) {}
534
+ try { globalSafety.startDynamicSystems(); } catch(_) {}
535
+
536
+ // Initialize Cookie Refresher to prevent cookie expiry (env-configurable)
537
+ try {
538
+ const envBool = (v) => (v === '1' || (v && v.toLowerCase && v.toLowerCase() === 'true'));
539
+ const toInt = (v, def) => {
540
+ const n = parseInt(v, 10);
541
+ return Number.isFinite(n) && n > 0 ? n : def;
542
+ };
543
+
544
+ const refreshEnabled = process.env.NEXUS_COOKIE_REFRESH_ENABLED ? envBool(process.env.NEXUS_COOKIE_REFRESH_ENABLED) : true;
545
+ const refreshInterval = toInt(process.env.NEXUS_COOKIE_REFRESH_INTERVAL, 30 * 60 * 1000);
546
+ const expiryDays = toInt(process.env.NEXUS_COOKIE_EXPIRY_DAYS, 90);
547
+ const maxBackups = toInt(process.env.NEXUS_COOKIE_MAX_BACKUPS, 5);
548
+ const backupsEnabled = process.env.NEXUS_COOKIE_BACKUP_ENABLED ? envBool(process.env.NEXUS_COOKIE_BACKUP_ENABLED) : true;
549
+
550
+ const cookieRefresher = new CookieRefresher({
551
+ enabled: refreshEnabled,
552
+ cookieRefreshIntervalMs: refreshInterval,
553
+ forceExpiryExtension: true,
554
+ expiryDays: expiryDays,
555
+ backupEnabled: backupsEnabled,
556
+ maxBackups: maxBackups
557
+ });
558
+
559
+ // Get appstate path from options or ctx
560
+ const appstatePath = globalOptions.appstatePath || process.env.NEXUS_APPSTATE_PATH || (ctx.dataDir ? path.join(ctx.dataDir, 'appstate.json') : null);
561
+ const backupPath = process.env.NEXUS_COOKIE_BACKUP_PATH || globalOptions.backupPath || (ctx.dataDir ? path.join(ctx.dataDir, 'backups') : null);
562
+
563
+ if (appstatePath) {
564
+ ctx.cookieRefresher = cookieRefresher.initialize(ctx, utils, defaultFuncs, appstatePath, backupPath);
565
+ logger('✅ Cookie Refresher initialized - cookies will be kept fresh', 'info');
566
+
567
+ // Immediate first refresh to ensure long expiry
568
+ cookieRefresher.refreshNow().catch(err => {
569
+ logger(`❌ Initial cookie refresh failed: ${err.message}`, 'warn');
570
+ });
571
+ }
572
+ } catch (err) {
573
+ logger(`❌ Cookie Refresher initialization failed: ${err.message}`, 'error');
574
+ }
575
+
524
576
  // Consolidated: delegate light poke to unified safety module (prevents duplicate refresh scheduling)
525
577
  if (globalSafety && typeof globalSafety.scheduleLightPoke === 'function') {
526
578
  globalSafety.scheduleLightPoke();
@@ -556,9 +608,7 @@ function loginHelper(appState, email, password, globalOptions, callback, prCallb
556
608
 
557
609
  // --- INTEGRATED NEXUS LOGIN SYSTEM ---
558
610
  // Full Nexus Login System integrated for npm package compatibility
559
- const { v4: uuidv4 } = require('uuid');
560
611
  const { TOTP } = require("totp-generator");
561
- const crypto = require('crypto');
562
612
 
563
613
  class IntegratedNexusLoginSystem {
564
614
  constructor(options = {}) {
@@ -723,7 +773,56 @@ class IntegratedNexusLoginSystem {
723
773
  try {
724
774
  const appstate = JSON.parse(fs.readFileSync(this.options.appstatePath, 'utf8'));
725
775
  this.logger(`Loaded appstate with ${appstate.length} cookies`, '✅');
726
- return appstate;
776
+
777
+ // Enhanced: Check and fix cookie expiry
778
+ const fixedAppstate = CookieManager.fixCookieExpiry(appstate, {
779
+ defaultExpiryDays: 90,
780
+ criticalExpiryDays: 90,
781
+ refreshExisting: true
782
+ });
783
+
784
+ // Save the fixed appstate back to file
785
+ if (fixedAppstate !== appstate) {
786
+ fs.writeFileSync(this.options.appstatePath, JSON.stringify(fixedAppstate, null, 2));
787
+ this.logger('Fixed cookie expiry dates and saved appstate', '🔧');
788
+ }
789
+
790
+ // Validate critical cookies
791
+ const validation = CookieManager.validateCriticalCookies(fixedAppstate);
792
+ if (!validation.valid) {
793
+ this.logger(`Warning: Missing critical cookies: ${validation.missing.join(', ')}`, '⚠️');
794
+ }
795
+
796
+ // Warn if any critical cookies are expiring soon (< 7 days)
797
+ try {
798
+ const critical = new Set(['c_user', 'xs', 'fr', 'datr', 'sb', 'spin']);
799
+ let hasExpiringSoon = false;
800
+ for (const cookie of fixedAppstate) {
801
+ if (!cookie || !cookie.key || !critical.has(cookie.key)) continue;
802
+ if (!cookie.expires) continue;
803
+ try {
804
+ const expiry = new Date(cookie.expires);
805
+ if (isNaN(expiry.getTime())) {
806
+ this.logger(`Warning: ${cookie.key} cookie has invalid expiry format: ${cookie.expires}`, '⚠️');
807
+ hasExpiringSoon = true;
808
+ continue;
809
+ }
810
+ const daysRemaining = Math.floor((expiry - new Date()) / (1000 * 60 * 60 * 24));
811
+ if (daysRemaining < 7) {
812
+ this.logger(`Warning: ${cookie.key} cookie expires in ${daysRemaining} days`, '⚠️');
813
+ hasExpiringSoon = true;
814
+ }
815
+ } catch (_) {
816
+ this.logger(`Warning: ${cookie.key} cookie has invalid expiry format: ${cookie.expires}`, '⚠️');
817
+ hasExpiringSoon = true;
818
+ }
819
+ }
820
+ if (hasExpiringSoon) {
821
+ this.logger(`Some critical cookies expire soon - Cookie Refresher will extend them`, 'ℹ️');
822
+ }
823
+ } catch (_) {}
824
+
825
+ return fixedAppstate;
727
826
  } catch (error) {
728
827
  this.logger(`Failed to load appstate: ${error.message}`, '❌');
729
828
  return null;
@@ -732,14 +831,21 @@ class IntegratedNexusLoginSystem {
732
831
 
733
832
  saveAppstate(appstate, metadata = {}) {
734
833
  try {
735
- fs.writeFileSync(this.options.appstatePath, JSON.stringify(appstate, null, 2));
834
+ // First fix any cookie expiry issues
835
+ const fixedAppstate = CookieManager.fixCookieExpiry(appstate, {
836
+ defaultExpiryDays: 90,
837
+ criticalExpiryDays: 90,
838
+ refreshExisting: false
839
+ });
840
+
841
+ fs.writeFileSync(this.options.appstatePath, JSON.stringify(fixedAppstate, null, 2));
736
842
 
737
843
  // Create backup
738
844
  const backupName = `appstate_${new Date().toISOString().replace(/[:.]/g, '-')}.json`;
739
845
  const backupPath = path.join(this.options.backupPath, backupName);
740
846
 
741
847
  const backupData = {
742
- appstate,
848
+ appstate: fixedAppstate,
743
849
  metadata: {
744
850
  ...metadata,
745
851
  created: new Date().toISOString(),
@@ -851,7 +957,7 @@ class IntegratedNexusLoginSystem {
851
957
  value: cookie.value,
852
958
  domain: cookie.domain,
853
959
  path: cookie.path,
854
- expires: cookie.expires,
960
+ expires: cookie.expires ? new Date(cookie.expires * 1000).toUTCString() : CookieManager.getDefaultExpiry(cookie.name),
855
961
  httpOnly: cookie.httpOnly,
856
962
  secure: cookie.secure
857
963
  }));
@@ -861,7 +967,9 @@ class IntegratedNexusLoginSystem {
861
967
  key: 'i_user',
862
968
  value: credentials.i_user,
863
969
  domain: '.facebook.com',
864
- path: '/'
970
+ path: '/',
971
+ expires: CookieManager.getDefaultExpiry('i_user'),
972
+ secure: true
865
973
  });
866
974
  }
867
975
 
@@ -951,7 +1059,7 @@ class IntegratedNexusLoginSystem {
951
1059
  value: cookie.value,
952
1060
  domain: cookie.domain,
953
1061
  path: cookie.path,
954
- expires: cookie.expires,
1062
+ expires: cookie.expires ? new Date(cookie.expires * 1000).toUTCString() : CookieManager.getDefaultExpiry(cookie.name),
955
1063
  httpOnly: cookie.httpOnly,
956
1064
  secure: cookie.secure
957
1065
  }));
@@ -961,7 +1069,9 @@ class IntegratedNexusLoginSystem {
961
1069
  key: 'i_user',
962
1070
  value: credentials.i_user,
963
1071
  domain: '.facebook.com',
964
- path: '/'
1072
+ path: '/',
1073
+ expires: CookieManager.getDefaultExpiry('i_user'),
1074
+ secure: true
965
1075
  });
966
1076
  }
967
1077
 
@@ -1261,12 +1371,18 @@ async function login(loginData, options = {}, callback) {
1261
1371
  mainLogger.info('✅ Session generated successfully');
1262
1372
  mainLogger.info('🔄 Starting bot with generated session (old system)');
1263
1373
 
1264
- // STEP 2: Single session guard before starting bot
1374
+ // STEP 2: Single session guard before starting bot (configurable)
1265
1375
  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;
1376
+ const lockEnabled = (typeof options.sessionLockEnabled !== 'undefined')
1377
+ ? !!options.sessionLockEnabled
1378
+ : !(process.env.NEXUS_SESSION_LOCK_ENABLED === '0' || (process.env.NEXUS_SESSION_LOCK_ENABLED || '').toLowerCase() === 'false');
1379
+
1380
+ if (lockEnabled) {
1381
+ const ssg = new SingleSessionGuard({ dataDir: process.env.NEXUS_DATA_DIR });
1382
+ ssg.acquire();
1383
+ // keep guard reference to release on exit
1384
+ global.__NEXUS_SSG__ = ssg;
1385
+ }
1270
1386
  } catch (e) {
1271
1387
  mainLogger.error('⚠️ Single session guard blocked start', e.message);
1272
1388
  if (callback) callback(e);
@@ -1316,9 +1432,15 @@ async function login(loginData, options = {}, callback) {
1316
1432
 
1317
1433
  // Direct session authentication using appstate (with single session guard)
1318
1434
  try {
1319
- const ssg = new SingleSessionGuard({ dataDir: process.env.NEXUS_DATA_DIR });
1320
- ssg.acquire();
1321
- global.__NEXUS_SSG__ = ssg;
1435
+ const lockEnabled = (typeof options.sessionLockEnabled !== 'undefined')
1436
+ ? !!options.sessionLockEnabled
1437
+ : !(process.env.NEXUS_SESSION_LOCK_ENABLED === '0' || (process.env.NEXUS_SESSION_LOCK_ENABLED || '').toLowerCase() === 'false');
1438
+
1439
+ if (lockEnabled) {
1440
+ const ssg = new SingleSessionGuard({ dataDir: process.env.NEXUS_DATA_DIR });
1441
+ ssg.acquire();
1442
+ global.__NEXUS_SSG__ = ssg;
1443
+ }
1322
1444
  } catch (e) {
1323
1445
  mainLogger.error('⚠️ Single session guard blocked start', e.message);
1324
1446
  if (callback) callback(e);
@@ -0,0 +1,168 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * Nexus Cookie Manager
5
+ * Ensures Facebook session cookies stay valid and fixes expiry issues
6
+ */
7
+
8
+ const fs = require('fs');
9
+ const path = require('path');
10
+ const logger = require('../logger');
11
+
12
+ class CookieManager {
13
+ /**
14
+ * Fix expiry dates on Facebook cookies to prevent rapid expiration
15
+ * @param {Array} cookies - Array of Facebook cookies from appstate
16
+ * @param {Object} options - Options for fixing cookies
17
+ * @returns {Array} - Fixed cookies with proper expiry dates
18
+ */
19
+ static fixCookieExpiry(cookies, options = {}) {
20
+ if (!Array.isArray(cookies) || cookies.length === 0) {
21
+ return cookies;
22
+ }
23
+
24
+ const {
25
+ defaultExpiryDays = 90,
26
+ criticalExpiryDays = 90,
27
+ refreshExisting = true
28
+ } = options;
29
+
30
+ const now = new Date();
31
+ const criticalCookies = ['c_user', 'xs', 'fr', 'datr', 'sb', 'spin'];
32
+ let fixedCount = 0;
33
+
34
+ for (const cookie of cookies) {
35
+ // Skip cookies with no key
36
+ if (!cookie.key) continue;
37
+
38
+ const isCritical = criticalCookies.includes(cookie.key);
39
+ const days = isCritical ? criticalExpiryDays : defaultExpiryDays;
40
+
41
+ // Check if cookie needs expiry fix
42
+ const needsFix = !cookie.expires ||
43
+ refreshExisting ||
44
+ !this._isValidDate(cookie.expires) ||
45
+ this._getRemainingDays(cookie.expires) < 7;
46
+
47
+ if (needsFix) {
48
+ // Set expiry to future date
49
+ const futureDate = new Date(now.getTime() + (days * 24 * 60 * 60 * 1000));
50
+ cookie.expires = futureDate.toUTCString();
51
+ fixedCount++;
52
+ }
53
+ }
54
+
55
+ if (fixedCount > 0) {
56
+ logger(`Fixed expiry dates for ${fixedCount} cookies`, 'info');
57
+ }
58
+
59
+ return cookies;
60
+ }
61
+
62
+ /**
63
+ * Check if cookie expiry is a valid date
64
+ * @param {string} dateStr - Date string to check
65
+ * @returns {boolean} - True if valid date
66
+ */
67
+ static _isValidDate(dateStr) {
68
+ if (!dateStr) return false;
69
+ const date = new Date(dateStr);
70
+ return !isNaN(date.getTime());
71
+ }
72
+
73
+ /**
74
+ * Get days remaining until expiry
75
+ * @param {string} dateStr - Date string to check
76
+ * @returns {number} - Days remaining
77
+ */
78
+ static _getRemainingDays(dateStr) {
79
+ if (!dateStr) return 0;
80
+ try {
81
+ const expiryDate = new Date(dateStr);
82
+ const now = new Date();
83
+ return Math.floor((expiryDate - now) / (1000 * 60 * 60 * 24));
84
+ } catch (e) {
85
+ return 0;
86
+ }
87
+ }
88
+
89
+ /**
90
+ * Check if appstate has critical cookies
91
+ * @param {Array} cookies - Cookies to check
92
+ * @returns {Object} - Result with status and missing cookies
93
+ */
94
+ static validateCriticalCookies(cookies) {
95
+ if (!Array.isArray(cookies) || cookies.length === 0) {
96
+ return { valid: false, missing: ['all'] };
97
+ }
98
+
99
+ const criticalCookies = ['c_user', 'xs', 'datr', 'sb'];
100
+ const missing = [];
101
+
102
+ for (const critical of criticalCookies) {
103
+ if (!cookies.some(c => c.key === critical)) {
104
+ missing.push(critical);
105
+ }
106
+ }
107
+
108
+ return {
109
+ valid: missing.length === 0,
110
+ missing
111
+ };
112
+ }
113
+
114
+ /**
115
+ * Generate default cookie expiry date
116
+ * @param {string} cookieName - Name of cookie
117
+ * @returns {string} - Expiry date string
118
+ */
119
+ static getDefaultExpiry(cookieName) {
120
+ const now = new Date();
121
+ const criticalCookies = ['c_user', 'xs', 'fr', 'datr', 'sb'];
122
+
123
+ // Critical cookies get 90 days, others get 30 days
124
+ const days = criticalCookies.includes(cookieName) ? 90 : 30;
125
+ const future = new Date(now.getTime() + (days * 24 * 60 * 60 * 1000));
126
+ return future.toUTCString();
127
+ }
128
+
129
+ /**
130
+ * Load and fix appstate file
131
+ * @param {string} appstatePath - Path to appstate.json
132
+ * @returns {Array|null} - Fixed cookies or null if failed
133
+ */
134
+ static loadAndFixAppstate(appstatePath) {
135
+ try {
136
+ if (!fs.existsSync(appstatePath)) {
137
+ logger(`Appstate file not found: ${appstatePath}`, 'error');
138
+ return null;
139
+ }
140
+
141
+ const cookies = JSON.parse(fs.readFileSync(appstatePath, 'utf8'));
142
+ if (!Array.isArray(cookies)) {
143
+ logger('Invalid appstate format: not an array', 'error');
144
+ return null;
145
+ }
146
+
147
+ // Fix expiry dates
148
+ const fixed = this.fixCookieExpiry(cookies);
149
+
150
+ // Validate critical cookies
151
+ const validation = this.validateCriticalCookies(fixed);
152
+ if (!validation.valid) {
153
+ logger(`Missing critical cookies: ${validation.missing.join(', ')}`, 'warn');
154
+ }
155
+
156
+ // Save back fixed cookies
157
+ fs.writeFileSync(appstatePath, JSON.stringify(fixed, null, 2));
158
+ logger(`Fixed and saved appstate with ${fixed.length} cookies`, 'info');
159
+
160
+ return fixed;
161
+ } catch (err) {
162
+ logger(`Failed to load and fix appstate: ${err.message}`, 'error');
163
+ return null;
164
+ }
165
+ }
166
+ }
167
+
168
+ module.exports = { CookieManager };
@@ -0,0 +1,277 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * Nexus Cookie Refresher
5
+ * Maintains cookie freshness and prevents expiry by strategically refreshing sessions
6
+ */
7
+
8
+ const fs = require('fs');
9
+ const path = require('path');
10
+ const logger = require('../logger');
11
+
12
+ class CookieRefresher {
13
+ constructor(options = {}) {
14
+ this.options = {
15
+ enabled: options.enabled !== false,
16
+ cookieRefreshIntervalMs: options.cookieRefreshIntervalMs || 60 * 60 * 1000, // 1 hour default
17
+ forceExpiryExtension: options.forceExpiryExtension !== false,
18
+ expiryDays: options.expiryDays || 60, // 60 days default expiry extension
19
+ backupEnabled: options.backupEnabled !== false,
20
+ maxBackups: options.maxBackups || 5,
21
+ ...options
22
+ };
23
+
24
+ this.refreshTimer = null;
25
+ this.ctx = null;
26
+ this.jar = null;
27
+ this.utils = null;
28
+ this.defaultFuncs = null;
29
+ this.appstatePath = null;
30
+ this.backupPath = null;
31
+ this.sessionStartTime = Date.now();
32
+ this.lastRefreshTime = null;
33
+ this.refreshCount = 0;
34
+ }
35
+
36
+ /**
37
+ * Initialize the refresher with API context
38
+ */
39
+ initialize(ctx, utils, defaultFuncs, appstatePath, backupPath) {
40
+ this.ctx = ctx;
41
+ this.jar = ctx.jar;
42
+ this.utils = utils;
43
+ this.defaultFuncs = defaultFuncs;
44
+ this.appstatePath = appstatePath;
45
+ this.backupPath = backupPath || path.dirname(appstatePath);
46
+
47
+ // Add timestamps to context for monitoring
48
+ ctx._cookieRefresher = {
49
+ sessionStartTime: this.sessionStartTime,
50
+ lastRefreshTime: null,
51
+ refreshCount: 0,
52
+ nextScheduledRefresh: null
53
+ };
54
+
55
+ if (this.options.enabled) {
56
+ this.start();
57
+ }
58
+
59
+ return this;
60
+ }
61
+
62
+ /**
63
+ * Start the cookie refresh cycle
64
+ */
65
+ start() {
66
+ if (this.refreshTimer) {
67
+ clearTimeout(this.refreshTimer);
68
+ }
69
+
70
+ // Schedule next refresh
71
+ const nextRefresh = this.options.cookieRefreshIntervalMs;
72
+ logger('CookieRefresher', `Scheduling next cookie refresh in ${Math.floor(nextRefresh/60000)} minutes`, 'debug');
73
+
74
+ this.refreshTimer = setTimeout(() => {
75
+ this.performRefresh()
76
+ .then(() => this.start()) // Schedule next refresh after success
77
+ .catch(err => {
78
+ logger('CookieRefresher', `Refresh failed: ${err.message}. Retrying in 15 minutes...`, 'warn');
79
+ // If refresh fails, try again sooner
80
+ setTimeout(() => this.start(), 15 * 60 * 1000);
81
+ });
82
+ }, nextRefresh);
83
+
84
+ // Don't prevent Node from exiting
85
+ this.refreshTimer.unref();
86
+
87
+ // Update next scheduled time
88
+ if (this.ctx && this.ctx._cookieRefresher) {
89
+ this.ctx._cookieRefresher.nextScheduledRefresh = Date.now() + nextRefresh;
90
+ }
91
+ }
92
+
93
+ /**
94
+ * Perform the actual cookie refresh operation
95
+ */
96
+ async performRefresh() {
97
+ if (!this.ctx || !this.jar || !this.utils || !this.defaultFuncs) {
98
+ throw new Error('CookieRefresher not properly initialized');
99
+ }
100
+
101
+ try {
102
+ // 1. First do a simple fetch to keep session alive
103
+ logger('CookieRefresher', 'Refreshing cookies to extend session...', 'info');
104
+ const urls = [
105
+ 'https://www.facebook.com/me',
106
+ 'https://www.facebook.com/ajax/haste-response/?__a=1',
107
+ 'https://www.facebook.com/ajax/bootloader-endpoint/?__a=1'
108
+ ];
109
+
110
+ let success = false;
111
+ for (const url of urls) {
112
+ try {
113
+ // Try multiple URLs in case some are blocked
114
+ const res = await this.defaultFuncs.get(url, this.jar, {});
115
+
116
+ // Save any cookies that were set
117
+ if (res && res.headers && res.headers["set-cookie"]) {
118
+ this.utils.saveCookies(this.jar)(res);
119
+ success = true;
120
+ break;
121
+ }
122
+ } catch (err) {
123
+ logger('CookieRefresher', `Failed to refresh with ${url}: ${err.message}`, 'debug');
124
+ // Continue to next URL
125
+ }
126
+ }
127
+
128
+ if (!success) {
129
+ throw new Error('All refresh URLs failed');
130
+ }
131
+
132
+ // 2. Get current cookies
133
+ const appstate = this.utils.getAppState(this.jar);
134
+
135
+ // 3. Force extend cookie expiry dates if enabled
136
+ if (this.options.forceExpiryExtension) {
137
+ this._extendCookieExpiry(appstate);
138
+ }
139
+
140
+ // 4. Save refreshed cookies
141
+ if (this.appstatePath) {
142
+ this._saveAppstate(appstate);
143
+ }
144
+
145
+ // 5. Update counters
146
+ this.lastRefreshTime = Date.now();
147
+ this.refreshCount++;
148
+ if (this.ctx && this.ctx._cookieRefresher) {
149
+ this.ctx._cookieRefresher.lastRefreshTime = this.lastRefreshTime;
150
+ this.ctx._cookieRefresher.refreshCount = this.refreshCount;
151
+ }
152
+
153
+ logger('CookieRefresher', `Successfully refreshed cookies (refresh #${this.refreshCount})`, 'info');
154
+ return true;
155
+ } catch (err) {
156
+ logger('CookieRefresher', `Cookie refresh failed: ${err.message}`, 'error');
157
+ throw err;
158
+ }
159
+ }
160
+
161
+ /**
162
+ * Force extend cookie expiry dates
163
+ */
164
+ _extendCookieExpiry(appstate) {
165
+ if (!Array.isArray(appstate)) return;
166
+
167
+ const now = new Date();
168
+ const expiryDate = new Date(now.getTime() + this.options.expiryDays * 24 * 60 * 60 * 1000);
169
+ const expiryStr = expiryDate.toUTCString();
170
+
171
+ // Important cookies that should never expire
172
+ const criticalCookies = ['c_user', 'xs', 'fr', 'datr', 'sb', 'spin'];
173
+
174
+ let extended = 0;
175
+ for (const cookie of appstate) {
176
+ // Skip cookies that shouldn't have their expiry extended
177
+ if (cookie.key && cookie.key.startsWith('_')) continue;
178
+
179
+ // Set cookie expiry to far future, prioritize critical cookies
180
+ if (criticalCookies.includes(cookie.key) || !cookie.expires || new Date(cookie.expires) < expiryDate) {
181
+ cookie.expires = expiryStr;
182
+ extended++;
183
+ }
184
+ }
185
+
186
+ logger('CookieRefresher', `Extended expiration for ${extended} cookies to ${expiryStr}`, 'debug');
187
+ }
188
+
189
+ /**
190
+ * Save appstate with backup
191
+ */
192
+ _saveAppstate(appstate) {
193
+ try {
194
+ // 1. Save main appstate file
195
+ fs.writeFileSync(this.appstatePath, JSON.stringify(appstate, null, 2));
196
+
197
+ // 2. Create backup if enabled
198
+ if (this.options.backupEnabled) {
199
+ const backupName = `appstate_refreshed_${new Date().toISOString().replace(/[:.]/g, '-')}.json`;
200
+ const backupPath = path.join(this.backupPath, backupName);
201
+
202
+ const backupData = {
203
+ appstate,
204
+ metadata: {
205
+ refreshed: new Date().toISOString(),
206
+ refreshCount: this.refreshCount,
207
+ sessionStartTime: new Date(this.sessionStartTime).toISOString(),
208
+ source: 'NexusCookieRefresher'
209
+ }
210
+ };
211
+
212
+ fs.writeFileSync(backupPath, JSON.stringify(backupData, null, 2));
213
+
214
+ // 3. Cleanup old backups if needed
215
+ this._cleanupOldBackups();
216
+ }
217
+
218
+ return true;
219
+ } catch (err) {
220
+ logger('CookieRefresher', `Failed to save refreshed appstate: ${err.message}`, 'error');
221
+ return false;
222
+ }
223
+ }
224
+
225
+ /**
226
+ * Clean up old backup files
227
+ */
228
+ _cleanupOldBackups() {
229
+ try {
230
+ const maxBackups = this.options.maxBackups;
231
+ const backupDir = this.backupPath;
232
+
233
+ // Find all appstate backup files
234
+ const files = fs.readdirSync(backupDir)
235
+ .filter(file => file.startsWith('appstate_refreshed_') && file.endsWith('.json'))
236
+ .map(file => path.join(backupDir, file));
237
+
238
+ // Sort by modified time (newest first)
239
+ files.sort((a, b) => {
240
+ return fs.statSync(b).mtime.getTime() - fs.statSync(a).mtime.getTime();
241
+ });
242
+
243
+ // Delete old backups beyond the limit
244
+ if (files.length > maxBackups) {
245
+ const toDelete = files.slice(maxBackups);
246
+ for (const file of toDelete) {
247
+ fs.unlinkSync(file);
248
+ }
249
+ logger('CookieRefresher', `Cleaned up ${toDelete.length} old cookie backup files`, 'debug');
250
+ }
251
+ } catch (err) {
252
+ logger('CookieRefresher', `Backup cleanup error: ${err.message}`, 'debug');
253
+ }
254
+ }
255
+
256
+ /**
257
+ * Stop the refresher
258
+ */
259
+ stop() {
260
+ if (this.refreshTimer) {
261
+ clearTimeout(this.refreshTimer);
262
+ this.refreshTimer = null;
263
+ }
264
+ }
265
+
266
+ /**
267
+ * Perform an immediate refresh
268
+ */
269
+ async refreshNow() {
270
+ this.stop();
271
+ await this.performRefresh();
272
+ this.start();
273
+ return true;
274
+ }
275
+ }
276
+
277
+ module.exports = { CookieRefresher };
@@ -0,0 +1,161 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * Nexus Device Manager
5
+ * Ensures consistent device fingerprinting to prevent Facebook security triggers
6
+ */
7
+
8
+ const fs = require('fs');
9
+ const path = require('path');
10
+ const logger = require('../logger');
11
+ const { v4: uuidv4 } = require('uuid');
12
+
13
+ class DeviceManager {
14
+ constructor(options = {}) {
15
+ this.options = {
16
+ enabled: options.enabled !== false,
17
+ deviceFilePath: options.deviceFilePath || './device.json',
18
+ ...options
19
+ };
20
+
21
+ this.deviceInfo = null;
22
+ this.initialized = false;
23
+ }
24
+
25
+ /**
26
+ * Initialize device manager and load/create device profile
27
+ */
28
+ initialize() {
29
+ if (this.initialized) return this;
30
+
31
+ try {
32
+ // Check if environment variable overrides path
33
+ const envPath = process.env.NEXUS_DEVICE_FILE;
34
+ if (envPath) {
35
+ this.options.deviceFilePath = envPath;
36
+ }
37
+
38
+ // Ensure directory exists
39
+ const dirPath = path.dirname(this.options.deviceFilePath);
40
+ if (!fs.existsSync(dirPath)) {
41
+ fs.mkdirSync(dirPath, { recursive: true });
42
+ }
43
+
44
+ // Try to load existing device info
45
+ if (fs.existsSync(this.options.deviceFilePath)) {
46
+ this.deviceInfo = JSON.parse(fs.readFileSync(this.options.deviceFilePath, 'utf8'));
47
+ logger(`Loaded existing device profile: ${this.deviceInfo.deviceId}`, 'info');
48
+ } else {
49
+ // Create new device info
50
+ this.deviceInfo = this._generateDeviceInfo();
51
+ this._saveDeviceInfo();
52
+ logger(`Created new device profile: ${this.deviceInfo.deviceId}`, 'info');
53
+ }
54
+
55
+ this.initialized = true;
56
+ return this;
57
+ } catch (err) {
58
+ logger(`Failed to initialize DeviceManager: ${err.message}`, 'error');
59
+ // Fallback to default device info
60
+ this.deviceInfo = this._generateDeviceInfo();
61
+ return this;
62
+ }
63
+ }
64
+
65
+ /**
66
+ * Generate new device information
67
+ */
68
+ _generateDeviceInfo() {
69
+ const deviceId = `device_${uuidv4().replace(/-/g, '')}`;
70
+ const familyDeviceId = `family_device_${uuidv4().replace(/-/g, '')}`;
71
+
72
+ // Modern Facebook user agent strings
73
+ const userAgents = [
74
+ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.99 Safari/537.36',
75
+ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36',
76
+ 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.99 Safari/537.36',
77
+ 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36'
78
+ ];
79
+
80
+ return {
81
+ deviceId,
82
+ familyDeviceId,
83
+ userAgent: userAgents[Math.floor(Math.random() * userAgents.length)],
84
+ created: new Date().toISOString(),
85
+ lastUsed: new Date().toISOString()
86
+ };
87
+ }
88
+
89
+ /**
90
+ * Save device information to file
91
+ */
92
+ _saveDeviceInfo() {
93
+ if (!this.options.enabled || !this.deviceInfo) return false;
94
+
95
+ try {
96
+ // Update last used timestamp
97
+ this.deviceInfo.lastUsed = new Date().toISOString();
98
+
99
+ // Save to file
100
+ fs.writeFileSync(this.options.deviceFilePath, JSON.stringify(this.deviceInfo, null, 2));
101
+ return true;
102
+ } catch (err) {
103
+ logger(`Failed to save device info: ${err.message}`, 'error');
104
+ return false;
105
+ }
106
+ }
107
+
108
+ /**
109
+ * Get device ID for consistent identification
110
+ */
111
+ getDeviceId() {
112
+ if (!this.initialized) this.initialize();
113
+ return this.deviceInfo?.deviceId || `device_${uuidv4().replace(/-/g, '')}`;
114
+ }
115
+
116
+ /**
117
+ * Get family device ID for consistent identification
118
+ */
119
+ getFamilyDeviceId() {
120
+ if (!this.initialized) this.initialize();
121
+ return this.deviceInfo?.familyDeviceId || `family_device_${uuidv4().replace(/-/g, '')}`;
122
+ }
123
+
124
+ /**
125
+ * Get user agent for consistent browser fingerprint
126
+ */
127
+ getUserAgent() {
128
+ if (!this.initialized) this.initialize();
129
+ return this.deviceInfo?.userAgent || 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.99 Safari/537.36';
130
+ }
131
+
132
+ /**
133
+ * Apply device information to request options
134
+ */
135
+ applyToRequestOptions(options = {}) {
136
+ if (!this.initialized) this.initialize();
137
+
138
+ return {
139
+ ...options,
140
+ headers: {
141
+ ...options.headers,
142
+ 'User-Agent': this.getUserAgent(),
143
+ 'X-FB-Device-Id': this.getDeviceId(),
144
+ 'X-FB-Family-Device-ID': this.getFamilyDeviceId()
145
+ }
146
+ };
147
+ }
148
+
149
+ /**
150
+ * Update last used timestamp
151
+ */
152
+ updateUsage() {
153
+ if (!this.initialized) this.initialize();
154
+ if (this.deviceInfo) {
155
+ this.deviceInfo.lastUsed = new Date().toISOString();
156
+ this._saveDeviceInfo();
157
+ }
158
+ }
159
+ }
160
+
161
+ module.exports = { DeviceManager };
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ // Deprecated optional lock. Use built-in SingleSessionGuard instead.
3
+ module.exports = { };
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ // Deprecated helper. Stability is handled in core + SingleSessionGuard.
3
+ module.exports = {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nexus-fca",
3
- "version": "3.0.2",
3
+ "version": "3.0.4",
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": {