fca-phantom 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +58 -0
- package/README.md +534 -0
- package/index.js +35 -0
- package/package.json +101 -0
- package/phantom/core/builder/bootstrap.js +334 -0
- package/phantom/core/builder/config.js +78 -0
- package/phantom/core/builder/forge.js +113 -0
- package/phantom/core/builder/ignite.js +386 -0
- package/phantom/core/builder/options.js +61 -0
- package/phantom/core/engine.js +71 -0
- package/phantom/core/reactor.js +2 -0
- package/phantom/datastore/appState.js +2 -0
- package/phantom/datastore/appStateBackup.js +34 -0
- package/phantom/datastore/models/cipher/e2ee.js +48 -0
- package/phantom/datastore/models/cipher/vault.js +153 -0
- package/phantom/datastore/models/index.js +3 -0
- package/phantom/datastore/models/matrix/auth.js +151 -0
- package/phantom/datastore/models/matrix/cache.js +3 -0
- package/phantom/datastore/models/matrix/checker.js +2 -0
- package/phantom/datastore/models/matrix/clients.js +2 -0
- package/phantom/datastore/models/matrix/constants.js +2 -0
- package/phantom/datastore/models/matrix/credentials.js +2 -0
- package/phantom/datastore/models/matrix/cycle.js +2 -0
- package/phantom/datastore/models/matrix/gate.js +282 -0
- package/phantom/datastore/models/matrix/ghost.js +332 -0
- package/phantom/datastore/models/matrix/headers.js +193 -0
- package/phantom/datastore/models/matrix/heartbeat.js +298 -0
- package/phantom/datastore/models/matrix/identity.js +235 -0
- package/phantom/datastore/models/matrix/logger.js +271 -0
- package/phantom/datastore/models/matrix/monitor.js +2 -0
- package/phantom/datastore/models/matrix/net.js +316 -0
- package/phantom/datastore/models/matrix/response.js +193 -0
- package/phantom/datastore/models/matrix/revive.js +255 -0
- package/phantom/datastore/models/matrix/signals.js +2 -0
- package/phantom/datastore/models/matrix/store.js +263 -0
- package/phantom/datastore/models/matrix/telemetry.js +272 -0
- package/phantom/datastore/models/matrix/tools.js +93 -0
- package/phantom/datastore/models/matrix/transform/cookieParser.js +2 -0
- package/phantom/datastore/models/matrix/transform/cookies.js +114 -0
- package/phantom/datastore/models/matrix/transform/index.js +203 -0
- package/phantom/datastore/models/matrix/validator.js +157 -0
- package/phantom/datastore/models/types/index.d.ts +498 -0
- package/phantom/datastore/schema.js +167 -0
- package/phantom/datastore/session.js +129 -0
- package/phantom/datastore/threads.js +22 -0
- package/phantom/datastore/users.js +26 -0
- package/phantom/dispatch/addExternalModule.js +239 -0
- package/phantom/dispatch/addUserToGroup.js +161 -0
- package/phantom/dispatch/changeAdminStatus.js +142 -0
- package/phantom/dispatch/changeArchivedStatus.js +135 -0
- package/phantom/dispatch/changeAvatar.js +123 -0
- package/phantom/dispatch/changeBio.js +86 -0
- package/phantom/dispatch/changeBlockedStatus.js +86 -0
- package/phantom/dispatch/changeGroupImage.js +145 -0
- package/phantom/dispatch/changeThreadColor.js +172 -0
- package/phantom/dispatch/changeThreadEmoji.js +130 -0
- package/phantom/dispatch/comment.js +136 -0
- package/phantom/dispatch/createAITheme.js +333 -0
- package/phantom/dispatch/createNewGroup.js +99 -0
- package/phantom/dispatch/createPoll.js +148 -0
- package/phantom/dispatch/deleteMessage.js +131 -0
- package/phantom/dispatch/deleteThread.js +155 -0
- package/phantom/dispatch/e2ee.js +101 -0
- package/phantom/dispatch/editMessage.js +158 -0
- package/phantom/dispatch/emoji.js +143 -0
- package/phantom/dispatch/fetchThemeData.js +233 -0
- package/phantom/dispatch/follow.js +111 -0
- package/phantom/dispatch/forwardMessage.js +110 -0
- package/phantom/dispatch/friend.js +189 -0
- package/phantom/dispatch/gcmember.js +138 -0
- package/phantom/dispatch/gcname.js +131 -0
- package/phantom/dispatch/gcrule.js +111 -0
- package/phantom/dispatch/getAccess.js +109 -0
- package/phantom/dispatch/getBotInfo.js +81 -0
- package/phantom/dispatch/getBotInitialData.js +110 -0
- package/phantom/dispatch/getFriendsList.js +118 -0
- package/phantom/dispatch/getMessage.js +199 -0
- package/phantom/dispatch/getTheme.js +199 -0
- package/phantom/dispatch/getThemeInfo.js +160 -0
- package/phantom/dispatch/getThreadHistory.js +139 -0
- package/phantom/dispatch/getThreadInfo.js +153 -0
- package/phantom/dispatch/getThreadList.js +132 -0
- package/phantom/dispatch/getThreadPictures.js +93 -0
- package/phantom/dispatch/getUserID.js +147 -0
- package/phantom/dispatch/getUserInfo.js +513 -0
- package/phantom/dispatch/getUserInfoV2.js +146 -0
- package/phantom/dispatch/handleMessageRequest.js +50 -0
- package/phantom/dispatch/httpGet.js +63 -0
- package/phantom/dispatch/httpPost.js +89 -0
- package/phantom/dispatch/httpPostFormData.js +69 -0
- package/phantom/dispatch/listenMqtt.js +1236 -0
- package/phantom/dispatch/listenSpeed.js +179 -0
- package/phantom/dispatch/logout.js +93 -0
- package/phantom/dispatch/markAsDelivered.js +92 -0
- package/phantom/dispatch/markAsRead.js +119 -0
- package/phantom/dispatch/markAsReadAll.js +215 -0
- package/phantom/dispatch/markAsSeen.js +70 -0
- package/phantom/dispatch/mqttDeltaValue.js +278 -0
- package/phantom/dispatch/muteThread.js +253 -0
- package/phantom/dispatch/nickname.js +132 -0
- package/phantom/dispatch/notes.js +263 -0
- package/phantom/dispatch/pinMessage.js +238 -0
- package/phantom/dispatch/produceMetaTheme.js +335 -0
- package/phantom/dispatch/realtime.js +291 -0
- package/phantom/dispatch/removeUserFromGroup.js +248 -0
- package/phantom/dispatch/resolvePhotoUrl.js +217 -0
- package/phantom/dispatch/searchForThread.js +258 -0
- package/phantom/dispatch/sendMessage.js +354 -0
- package/phantom/dispatch/sendMessageMqtt.js +249 -0
- package/phantom/dispatch/sendTypingIndicator.js +206 -0
- package/phantom/dispatch/setMessageReaction.js +188 -0
- package/phantom/dispatch/setMessageReactionMqtt.js +248 -0
- package/phantom/dispatch/setThreadTheme.js +330 -0
- package/phantom/dispatch/setThreadThemeMqtt.js +207 -0
- package/phantom/dispatch/share.js +200 -0
- package/phantom/dispatch/shareContact.js +216 -0
- package/phantom/dispatch/stickers.js +395 -0
- package/phantom/dispatch/story.js +240 -0
- package/phantom/dispatch/theme.js +296 -0
- package/phantom/dispatch/unfriend.js +199 -0
- package/phantom/dispatch/unsendMessage.js +124 -0
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const { makeParsable, warn, log } = require("./logger");
|
|
4
|
+
const { globalGate, configureRateLimiter } = require("./gate");
|
|
5
|
+
|
|
6
|
+
function _formatCookie(arr, domain) {
|
|
7
|
+
return `${arr[0]}=${arr[1]}; Path=${arr[3]}; Domain=${domain}.com`;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function _delay(ms) { return new Promise(r => setTimeout(r, ms)); }
|
|
11
|
+
|
|
12
|
+
const SESSION_ERRORS = {
|
|
13
|
+
1357046: "Session token expired",
|
|
14
|
+
1357045: "Invalid session token",
|
|
15
|
+
458: "Not logged in",
|
|
16
|
+
1357004: "Checkpoint required",
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const ACCOUNT_ERRORS = {
|
|
20
|
+
1357001: "Account blocked — automated behavior detected",
|
|
21
|
+
1357031: "Account temporarily locked",
|
|
22
|
+
1357033: "Account suspended",
|
|
23
|
+
2056003: "Account restricted — suspicious activity",
|
|
24
|
+
1357048: "Account requires verification",
|
|
25
|
+
190: "OAuth access token expired or invalid",
|
|
26
|
+
100: "Invalid parameter",
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
function parseAndCheckLogin(ctx, http, retry = 0) {
|
|
30
|
+
return async (data) => {
|
|
31
|
+
if (data.statusCode === 401) {
|
|
32
|
+
const e = Object.assign(new Error("Session expired — re-authentication required"), {
|
|
33
|
+
error: 401, errorCode: 401, errorType: "AUTH_EXPIRED", requiresReLogin: true,
|
|
34
|
+
});
|
|
35
|
+
warn("Session", "Session expired (401)");
|
|
36
|
+
throw e;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (data.statusCode === 429) {
|
|
40
|
+
warn("Session", "Rate limited by server (429)");
|
|
41
|
+
await _delay(5000 + Math.random() * 5000);
|
|
42
|
+
throw Object.assign(new Error("Rate limited"), {
|
|
43
|
+
error: 429, errorCode: 429, errorType: "RATE_LIMITED",
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (data.statusCode >= 500 && data.statusCode < 600) {
|
|
48
|
+
if (retry >= 5) {
|
|
49
|
+
throw Object.assign(new Error(`Server error ${data.statusCode} after retries`), {
|
|
50
|
+
statusCode: data.statusCode, res: data.body,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
await _delay(1000 + Math.random() * 4000);
|
|
54
|
+
const url = data.request.uri.protocol + "//" + data.request.uri.hostname + data.request.uri.pathname;
|
|
55
|
+
const ct = (data.request.headers["content-type"] || "").split(";")[0];
|
|
56
|
+
const nd = ct === "multipart/form-data"
|
|
57
|
+
? await http.postFormData(url, ctx.jar, data.request.formData, data.request.qs, ctx.globalOptions, ctx)
|
|
58
|
+
: await http.post(url, ctx.jar, data.request.form, ctx.globalOptions, ctx);
|
|
59
|
+
return parseAndCheckLogin(ctx, http, retry + 1)(nd);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (data.statusCode === 404) return;
|
|
63
|
+
|
|
64
|
+
if (data.statusCode !== 200) {
|
|
65
|
+
warn("Session", `Unexpected HTTP ${data.statusCode}`);
|
|
66
|
+
throw new Error(`Unexpected HTTP ${data.statusCode}`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
let res;
|
|
70
|
+
if (typeof data.body === "object" && data.body !== null) {
|
|
71
|
+
res = data.body;
|
|
72
|
+
} else if (typeof data.body === "string") {
|
|
73
|
+
try {
|
|
74
|
+
res = JSON.parse(makeParsable(data.body));
|
|
75
|
+
} catch (e) {
|
|
76
|
+
throw Object.assign(new Error("Response parse error"), { detail: e, res: data.body });
|
|
77
|
+
}
|
|
78
|
+
} else {
|
|
79
|
+
throw new Error(`Unknown response body type: ${typeof data.body}`);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (res.redirect && data.request.method === "GET") {
|
|
83
|
+
return parseAndCheckLogin(ctx, http)(await http.get(res.redirect, ctx.jar));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (res.jsmods?.require?.[0]?.[0] === "Cookie") {
|
|
87
|
+
res.jsmods.require[0][3][0] = res.jsmods.require[0][3][0].replace("_js_", "");
|
|
88
|
+
const ck = res.jsmods.require[0][3];
|
|
89
|
+
try { ctx.jar.setCookie(_formatCookie(ck, "facebook"), "https://www.facebook.com"); } catch (_) {}
|
|
90
|
+
try { ctx.jar.setCookie(_formatCookie(ck, "messenger"), "https://www.messenger.com"); } catch (_) {}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (Array.isArray(res.jsmods?.require)) {
|
|
94
|
+
for (const r of res.jsmods.require) {
|
|
95
|
+
if (r[0] === "DTSG" && r[1] === "setToken" && r[3]?.[0]) {
|
|
96
|
+
ctx.fb_dtsg = r[3][0];
|
|
97
|
+
ctx.ttstamp = "2";
|
|
98
|
+
for (let i = 0; i < ctx.fb_dtsg.length; i++) {
|
|
99
|
+
ctx.ttstamp += ctx.fb_dtsg.charCodeAt(i);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (res.error && SESSION_ERRORS[res.error]) {
|
|
106
|
+
const e = Object.assign(new Error(SESSION_ERRORS[res.error]), {
|
|
107
|
+
error: res.error, errorCode: res.error, errorType: "SESSION_EXPIRED", requiresReLogin: true,
|
|
108
|
+
});
|
|
109
|
+
warn("Session", `${SESSION_ERRORS[res.error]} (code ${res.error})`);
|
|
110
|
+
throw e;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (res.error && ACCOUNT_ERRORS[res.error]) {
|
|
114
|
+
const kind = res.error === 1357004 ? "CHECKPOINT" :
|
|
115
|
+
res.error === 1357031 ? "LOCKED" : "BLOCKED";
|
|
116
|
+
const e = Object.assign(new Error(ACCOUNT_ERRORS[res.error]), {
|
|
117
|
+
error: "account_security", errorCode: res.error, errorType: kind,
|
|
118
|
+
requiresReLogin: [1357004, 1357031, 1357048].includes(res.error),
|
|
119
|
+
});
|
|
120
|
+
warn("Account", `${ACCOUNT_ERRORS[res.error]} (code ${res.error})`);
|
|
121
|
+
throw e;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const rs = JSON.stringify(res);
|
|
125
|
+
|
|
126
|
+
if (rs.includes("XCheckpointFBScrapingWarningController") || rs.includes("601051028565049")) {
|
|
127
|
+
warn("Shield", "Scraping checkpoint — applying cooldown");
|
|
128
|
+
try {
|
|
129
|
+
globalGate.setEndpointCooldown("__GLOBAL__", 8 * 60_000);
|
|
130
|
+
configureRateLimiter({ maxConcurrentRequests: 2 });
|
|
131
|
+
} catch (_) {}
|
|
132
|
+
throw Object.assign(new Error("Automation checkpoint — account may be flagged"), {
|
|
133
|
+
errorCode: "CHECKPOINT_SCRAPING", errorType: "BOT_DETECTION", requiresReLogin: true,
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (rs.includes("1501092823525282")) {
|
|
138
|
+
warn("Shield", "Critical checkpoint 282 detected");
|
|
139
|
+
try {
|
|
140
|
+
globalGate.setEndpointCooldown("__GLOBAL__", 15 * 60_000);
|
|
141
|
+
configureRateLimiter({ maxConcurrentRequests: 1, maxRequestsPerMinute: 20 });
|
|
142
|
+
} catch (_) {}
|
|
143
|
+
throw Object.assign(new Error("Critical automation checkpoint"), {
|
|
144
|
+
errorCode: "CHECKPOINT_282", errorType: "BOT_DETECTION_CRITICAL", requiresReLogin: true,
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (rs.includes("828281030927956")) {
|
|
149
|
+
warn("Shield", "Checkpoint 956 detected — account may be restricted");
|
|
150
|
+
try { globalGate.setEndpointCooldown("__GLOBAL__", 5 * 60_000); } catch (_) {}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (String(res.redirect || "").includes("login.php")) {
|
|
154
|
+
warn("Session", "Login redirect detected — session expired");
|
|
155
|
+
throw Object.assign(new Error("Session expired — login redirect"), {
|
|
156
|
+
error: 401, errorCode: 401, errorType: "LOGIN_REDIRECT", requiresReLogin: true,
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (typeof data.body === "string" && (data.body.includes("login_form") || data.body.includes("LoginForm"))) {
|
|
161
|
+
warn("Session", "Login page detected in response — session expired");
|
|
162
|
+
throw Object.assign(new Error("Session expired — login page in response"), {
|
|
163
|
+
error: 401, errorCode: 401, errorType: "LOGIN_REDIRECT", requiresReLogin: true,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return res;
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function saveCookies(jar) {
|
|
172
|
+
return (res) => {
|
|
173
|
+
const cookies = res.headers?.["set-cookie"] || [];
|
|
174
|
+
for (const c of cookies) {
|
|
175
|
+
try { jar.setCookie(c, "https://www.facebook.com"); } catch (_) {}
|
|
176
|
+
try { jar.setCookie(c.replace(/domain=\.facebook\.com/i, "domain=.messenger.com"), "https://www.messenger.com"); } catch (_) {}
|
|
177
|
+
}
|
|
178
|
+
return res;
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function getAppState(jar) {
|
|
183
|
+
const fbCookies = jar.getCookiesSync("https://www.facebook.com");
|
|
184
|
+
const msgCookies = jar.getCookiesSync("https://www.messenger.com");
|
|
185
|
+
const seen = new Set();
|
|
186
|
+
return [...fbCookies, ...msgCookies].filter(c => {
|
|
187
|
+
if (seen.has(c.key)) return false;
|
|
188
|
+
seen.add(c.key);
|
|
189
|
+
return true;
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
module.exports = { parseAndCheckLogin, saveCookies, getAppState };
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const { warn, log, error } = require("./logger");
|
|
4
|
+
|
|
5
|
+
const NET_ERRORS = [
|
|
6
|
+
"ECONNRESET", "ETIMEDOUT", "ECONNREFUSED", "ENETUNREACH", "EHOSTUNREACH",
|
|
7
|
+
"EAI_AGAIN", "ENOTFOUND", "ESOCKETTIMEDOUT", "EPIPE", "ECONNABORTED",
|
|
8
|
+
"socket hang up", "network error", "connect ETIMEDOUT",
|
|
9
|
+
"read ECONNRESET", "write ECONNRESET", "network socket disconnected",
|
|
10
|
+
];
|
|
11
|
+
|
|
12
|
+
function isNetworkError(err) {
|
|
13
|
+
if (!err) return false;
|
|
14
|
+
const msg = (err.message || String(err || "")).toLowerCase();
|
|
15
|
+
const code = err.code || "";
|
|
16
|
+
return NET_ERRORS.some(p => msg.includes(p.toLowerCase()) || code === p);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function _jitter(ms) { return Math.floor(Math.random() * ms); }
|
|
20
|
+
|
|
21
|
+
class ReviveManager {
|
|
22
|
+
constructor() {
|
|
23
|
+
this.creds = null;
|
|
24
|
+
this.opts = null;
|
|
25
|
+
this.cb = null;
|
|
26
|
+
this.active = false;
|
|
27
|
+
this.pending = [];
|
|
28
|
+
this.maxRetries = 6;
|
|
29
|
+
this.retries = 0;
|
|
30
|
+
this.lastFailAt = 0;
|
|
31
|
+
this.decayMs = 2 * 60 * 60_000;
|
|
32
|
+
this.onSuccess = null;
|
|
33
|
+
this.onFailure = null;
|
|
34
|
+
this.checkInterval = 30 * 60_000;
|
|
35
|
+
this._monitorTimer = null;
|
|
36
|
+
this._lock = false;
|
|
37
|
+
this._lockResolve = null;
|
|
38
|
+
this.sessionMonitorInterval = null;
|
|
39
|
+
this._healthHistory = [];
|
|
40
|
+
this._healthScore = 100;
|
|
41
|
+
this._lastReviveAt = null;
|
|
42
|
+
this._reviveCount = 0;
|
|
43
|
+
this._strategyIndex = 0;
|
|
44
|
+
this._strategies = ["token_refresh", "cookie_refresh", "full_reauth"];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
setCredentials(creds, opts, cb) {
|
|
48
|
+
this.creds = creds;
|
|
49
|
+
this.opts = opts || {};
|
|
50
|
+
this.cb = cb;
|
|
51
|
+
this.active = true;
|
|
52
|
+
this.retries = 0;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
isEnabled() { return this.active && !!this.creds; }
|
|
56
|
+
getHealthScore() { return this._healthScore; }
|
|
57
|
+
|
|
58
|
+
_recordHealth(ok) {
|
|
59
|
+
this._healthHistory.push({ ok, at: Date.now() });
|
|
60
|
+
if (this._healthHistory.length > 50) this._healthHistory.shift();
|
|
61
|
+
const recent = this._healthHistory.slice(-10);
|
|
62
|
+
const okCount = recent.filter(h => h.ok).length;
|
|
63
|
+
this._healthScore = Math.round((okCount / recent.length) * 100);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
startSessionMonitoring(api) {
|
|
67
|
+
if (this._monitorTimer) clearInterval(this._monitorTimer);
|
|
68
|
+
if (!this.active || !api) return;
|
|
69
|
+
|
|
70
|
+
this._monitorTimer = setInterval(async () => {
|
|
71
|
+
if (this._lock) return;
|
|
72
|
+
try {
|
|
73
|
+
const alive = await api.isSessionAlive?.() ?? await api.isSessionValid?.();
|
|
74
|
+
if (alive === "network_error") {
|
|
75
|
+
warn("Revive", "Session probe — transient network, skipping");
|
|
76
|
+
this._recordHealth(true);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
this._recordHealth(!!alive);
|
|
80
|
+
if (alive) return;
|
|
81
|
+
|
|
82
|
+
warn("Revive", `Session check failed [health:${this._healthScore}%] — attempting recovery`);
|
|
83
|
+
|
|
84
|
+
let recovered = false;
|
|
85
|
+
if (api._cycleManager?.refreshTokens) {
|
|
86
|
+
try {
|
|
87
|
+
recovered = await api._cycleManager.refreshTokens(
|
|
88
|
+
api.ctx, api.defaultFuncs, "https://www.facebook.com"
|
|
89
|
+
);
|
|
90
|
+
} catch (e) {
|
|
91
|
+
if (isNetworkError(e)) { warn("Revive", "Token refresh — network error:", e.message); return; }
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (!recovered) {
|
|
96
|
+
warn("Revive", "Refresh unsuccessful — triggering full revive");
|
|
97
|
+
await this.handleSessionExpiry(api, "https://www.facebook.com", "Session expired");
|
|
98
|
+
} else {
|
|
99
|
+
log("Revive", "Token refresh restored session");
|
|
100
|
+
this._recordHealth(true);
|
|
101
|
+
}
|
|
102
|
+
} catch (e) {
|
|
103
|
+
if (isNetworkError(e)) { warn("Revive", "Monitor error (network):", e.message); return; }
|
|
104
|
+
error("Revive", "Monitor error:", e.message);
|
|
105
|
+
}
|
|
106
|
+
}, this.checkInterval);
|
|
107
|
+
|
|
108
|
+
try { this._monitorTimer.unref(); } catch (_) {}
|
|
109
|
+
this.sessionMonitorInterval = this._monitorTimer;
|
|
110
|
+
log("Revive", `Session monitor started (every ${Math.round(this.checkInterval / 60_000)}min)`);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
stopSessionMonitoring() {
|
|
114
|
+
if (this._monitorTimer) {
|
|
115
|
+
clearInterval(this._monitorTimer);
|
|
116
|
+
this._monitorTimer = null;
|
|
117
|
+
this.sessionMonitorInterval = null;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async handleSessionExpiry(api, urlOrFn, errMsg) {
|
|
122
|
+
if (!this.isEnabled()) {
|
|
123
|
+
warn("Revive", "Not enabled — no credentials set");
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const originFn = typeof urlOrFn === "function" ? urlOrFn : () => urlOrFn;
|
|
128
|
+
|
|
129
|
+
if (this._lock) {
|
|
130
|
+
return new Promise((res, rej) => this.pending.push({ res, rej }));
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (this.retries >= this.maxRetries && this.lastFailAt > 0) {
|
|
134
|
+
const age = Date.now() - this.lastFailAt;
|
|
135
|
+
if (age >= this.decayMs) {
|
|
136
|
+
log("Revive", `Decay reset after ${Math.round(age / 60_000)}min`);
|
|
137
|
+
this.retries = 0;
|
|
138
|
+
this._strategyIndex = 0;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (this.retries >= this.maxRetries) {
|
|
143
|
+
error("Revive", `Max retries (${this.maxRetries}) exceeded`);
|
|
144
|
+
this.onFailure?.(new Error("Max session revive retries exceeded"));
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
this._lock = true;
|
|
149
|
+
let lockRelease;
|
|
150
|
+
const lockPromise = new Promise(r => { lockRelease = r; });
|
|
151
|
+
this.retries++;
|
|
152
|
+
this._reviveCount++;
|
|
153
|
+
|
|
154
|
+
const strategy = this._strategies[Math.min(this._strategyIndex, this._strategies.length - 1)];
|
|
155
|
+
log("Revive", `Reviving session [attempt:${this.retries}/${this.maxRetries}, strategy:${strategy}]`);
|
|
156
|
+
|
|
157
|
+
await new Promise(r => setTimeout(r, 800 + _jitter(400)));
|
|
158
|
+
|
|
159
|
+
try {
|
|
160
|
+
const ignite = require("../core/builder/bootstrap");
|
|
161
|
+
const forge = require("../core/builder/forge");
|
|
162
|
+
const applyConfig = require("../core/builder/options");
|
|
163
|
+
|
|
164
|
+
await new Promise((res, rej) => {
|
|
165
|
+
ignite(this.creds, this.opts, (err, newApi) => {
|
|
166
|
+
if (err) return rej(err);
|
|
167
|
+
if (api) {
|
|
168
|
+
api.ctx = newApi.ctx;
|
|
169
|
+
api.defaultFuncs = newApi.defaultFuncs;
|
|
170
|
+
api._cycleManager?.resetFailureCount?.();
|
|
171
|
+
}
|
|
172
|
+
res(newApi);
|
|
173
|
+
}, applyConfig, forge, api, originFn, errMsg);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
log("Revive", `Session revived successfully (total revives: ${this._reviveCount})`);
|
|
177
|
+
this.retries = 0;
|
|
178
|
+
this._strategyIndex = 0;
|
|
179
|
+
this._lock = false;
|
|
180
|
+
lockRelease?.();
|
|
181
|
+
this._recordHealth(true);
|
|
182
|
+
this._lastReviveAt = Date.now();
|
|
183
|
+
this._resolvePending(true);
|
|
184
|
+
this.onSuccess?.();
|
|
185
|
+
|
|
186
|
+
try {
|
|
187
|
+
if (api?.ctx?._listeningActive) {
|
|
188
|
+
const cb = api.ctx._lastListenCallback;
|
|
189
|
+
try { api.stopListening?.(); } catch (_) {}
|
|
190
|
+
if (cb) api.listenMqtt(cb);
|
|
191
|
+
}
|
|
192
|
+
} catch (_) {}
|
|
193
|
+
|
|
194
|
+
return true;
|
|
195
|
+
|
|
196
|
+
} catch (e) {
|
|
197
|
+
error("Revive", `Revive failed [strategy:${strategy}]:`, e.message);
|
|
198
|
+
this._lock = false;
|
|
199
|
+
this.lastFailAt = Date.now();
|
|
200
|
+
lockRelease?.();
|
|
201
|
+
this._recordHealth(false);
|
|
202
|
+
this._strategyIndex = Math.min(this._strategyIndex + 1, this._strategies.length - 1);
|
|
203
|
+
this._resolvePending(false);
|
|
204
|
+
|
|
205
|
+
if (this.retries >= this.maxRetries) {
|
|
206
|
+
this.onFailure?.(e);
|
|
207
|
+
return false;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const wait = Math.min(60_000, Math.pow(2, this.retries) * 1500 + _jitter(2000));
|
|
211
|
+
log("Revive", `Retrying in ${Math.round(wait / 1000)}s...`);
|
|
212
|
+
await new Promise(r => setTimeout(r, wait));
|
|
213
|
+
return this.handleSessionExpiry(api, urlOrFn, errMsg);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
_resolvePending(ok) {
|
|
218
|
+
const queue = this.pending.splice(0);
|
|
219
|
+
queue.forEach(({ res, rej }) => ok ? res(true) : rej(new Error("Session revive failed")));
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
updateAppState(state) {
|
|
223
|
+
if (!this.creds || !Array.isArray(state) || !state.length) return;
|
|
224
|
+
this.creds.appState = state;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
disable() {
|
|
228
|
+
this.active = false;
|
|
229
|
+
this.stopSessionMonitoring();
|
|
230
|
+
this.creds = this.opts = this.cb = null;
|
|
231
|
+
log("Revive", "Auto-revive disabled");
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
getStats() {
|
|
235
|
+
return {
|
|
236
|
+
enabled: this.isEnabled(),
|
|
237
|
+
healthScore: this._healthScore,
|
|
238
|
+
retries: this.retries,
|
|
239
|
+
maxRetries: this.maxRetries,
|
|
240
|
+
reviveCount: this._reviveCount,
|
|
241
|
+
lastReviveAt: this._lastReviveAt,
|
|
242
|
+
strategy: this._strategies[Math.min(this._strategyIndex, this._strategies.length - 1)],
|
|
243
|
+
monitoring: !!this.sessionMonitorInterval,
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const globalReviveManager = new ReviveManager();
|
|
249
|
+
|
|
250
|
+
module.exports = {
|
|
251
|
+
ReviveManager,
|
|
252
|
+
globalReviveManager,
|
|
253
|
+
globalAutoReLoginManager: globalReviveManager,
|
|
254
|
+
isNetworkError,
|
|
255
|
+
};
|