nexus-fca 3.0.2 → 3.0.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/docs/advanced-configuration.md +101 -0
- package/docs/cookie-management.md +87 -0
- package/index.js +121 -11
- package/lib/safety/CookieManager.js +168 -0
- package/lib/safety/CookieRefresher.js +277 -0
- package/lib/safety/DeviceManager.js +161 -0
- package/lib/safety/SessionLock.js +3 -0
- package/lib/safety/sessionStability.js +3 -0
- package/package.json +1 -1
|
@@ -0,0 +1,101 @@
|
|
|
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
|
+
# Uses SingleSessionGuard with NEXUS_SESSION_LOCK_PATH and NEXUS_FORCE_LOCK
|
|
18
|
+
NEXUS_SESSION_LOCK_PATH=./lock # Path to session lock file used by SingleSessionGuard
|
|
19
|
+
NEXUS_FORCE_LOCK=false # Force acquire lock even if lock exists
|
|
20
|
+
|
|
21
|
+
# Region control
|
|
22
|
+
NEXUS_REGION=NA # Set fixed region (NA, EU, AS)
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### Cookie Management
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
# Cookie refresher settings
|
|
29
|
+
NEXUS_COOKIE_REFRESH_INTERVAL=1800000 # Refresh interval in ms (default: 30 minutes)
|
|
30
|
+
NEXUS_COOKIE_EXPIRY_DAYS=90 # Days to extend cookie expiry
|
|
31
|
+
NEXUS_COOKIE_BACKUP_PATH=./backups # Path to store cookie backups
|
|
32
|
+
NEXUS_COOKIE_MAX_BACKUPS=5 # Maximum number of cookie backups to keep
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Connection Settings
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
# Connection resilience
|
|
39
|
+
NEXUS_MAX_RETRIES=5 # Maximum connection retry attempts
|
|
40
|
+
NEXUS_RETRY_DELAY=5000 # Base delay between retries (ms)
|
|
41
|
+
NEXUS_KEEPALIVE_INTERVAL=300000 # Keepalive interval (ms)
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Safety Features
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
# Safety controls
|
|
48
|
+
NEXUS_FCA_SAFE_MODE=1 # Enable basic safety measures
|
|
49
|
+
NEXUS_FCA_ULTRA_SAFE_MODE=1 # Enable maximum safety for stability
|
|
50
|
+
NEXUS_DELIVERY_TIMEOUT=60000 # Message delivery timeout (ms)
|
|
51
|
+
NEXUS_AUTO_MARK_READ=false # Automatically mark messages as read
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## API Configuration Options
|
|
55
|
+
|
|
56
|
+
When initializing the API, you can pass additional options:
|
|
57
|
+
|
|
58
|
+
```javascript
|
|
59
|
+
const api = await login({
|
|
60
|
+
appState: require('./appstate.json'),
|
|
61
|
+
|
|
62
|
+
// Session stability options
|
|
63
|
+
persistentDevice: true, // Use consistent device fingerprinting
|
|
64
|
+
deviceFilePath: './device.json', // Custom device profile path
|
|
65
|
+
// Single-session guard is enabled by default internally
|
|
66
|
+
// Configure via env: NEXUS_SESSION_LOCK_PATH, NEXUS_FORCE_LOCK
|
|
67
|
+
|
|
68
|
+
// Connection options
|
|
69
|
+
region: 'NA', // Set fixed region
|
|
70
|
+
userAgent: 'custom-user-agent', // Override user agent
|
|
71
|
+
|
|
72
|
+
// Cookie management
|
|
73
|
+
cookieRefreshInterval: 1800000, // Cookie refresh interval (ms)
|
|
74
|
+
cookieExpiryDays: 90, // Days to extend cookie expiry
|
|
75
|
+
cookieBackupEnabled: true, // Enable cookie backups
|
|
76
|
+
cookieMaxBackups: 5, // Maximum cookie backups
|
|
77
|
+
|
|
78
|
+
// Safety options
|
|
79
|
+
forceLogout: true, // Force logout any other sessions
|
|
80
|
+
safetyLevel: 2 // Safety level (0-2)
|
|
81
|
+
});
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Best Practices
|
|
85
|
+
|
|
86
|
+
For the most stable experience:
|
|
87
|
+
|
|
88
|
+
1. **Always enable persistent device**: Use the same device fingerprint across restarts
|
|
89
|
+
2. **Enable session locking**: Prevent multiple instances from using the same account
|
|
90
|
+
3. **Set a fixed region**: Use the `NEXUS_REGION` environment variable
|
|
91
|
+
4. **Use a modern user agent**: Let the system select an appropriate one
|
|
92
|
+
5. **Keep cookie refreshing enabled**: Maintains fresh cookies
|
|
93
|
+
6. **Back up working appstate files**: Keep copies of working sessions
|
|
94
|
+
|
|
95
|
+
## Security Considerations
|
|
96
|
+
|
|
97
|
+
- Store sensitive information like appstate files securely
|
|
98
|
+
- Use environment variables for credentials instead of hardcoding
|
|
99
|
+
- Use `.env` files in development (gitignored)
|
|
100
|
+
- Consider using a dedicated account for automation
|
|
101
|
+
- 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
|
@@ -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 };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nexus-fca",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.3",
|
|
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": {
|