@cexy/hoonfca 1.0.7 → 2.0.1
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 +3 -0
- package/examples/login-with-cookies.js +102 -0
- package/examples/verify.js +70 -0
- package/index.js +2 -8
- package/package.json +82 -61
- package/src/{api/action → apis}/addExternalModule.js +24 -25
- package/src/apis/addUserToGroup.js +108 -0
- package/src/apis/changeAdminStatus.js +148 -0
- package/src/apis/changeArchivedStatus.js +61 -0
- package/src/apis/changeAvatar.js +103 -0
- package/src/apis/changeBio.js +69 -0
- package/src/apis/changeBlockedStatus.js +54 -0
- package/src/apis/changeGroupImage.js +136 -0
- package/src/apis/changeThreadColor.js +116 -0
- package/src/apis/changeThreadEmoji.js +53 -0
- package/src/apis/comment.js +207 -0
- package/src/apis/createAITheme.js +129 -0
- package/src/apis/createNewGroup.js +79 -0
- package/src/apis/createPoll.js +73 -0
- package/src/apis/deleteMessage.js +44 -0
- package/src/apis/deleteThread.js +52 -0
- package/src/apis/e2ee.js +17 -0
- package/src/apis/editMessage.js +70 -0
- package/src/apis/emoji.js +124 -0
- package/src/apis/fetchThemeData.js +82 -0
- package/src/apis/follow.js +81 -0
- package/src/apis/forwardMessage.js +52 -0
- package/src/apis/friend.js +243 -0
- package/src/apis/gcmember.js +122 -0
- package/src/apis/gcname.js +123 -0
- package/src/apis/gcrule.js +119 -0
- package/src/apis/getAccess.js +111 -0
- package/src/apis/getBotInfo.js +88 -0
- package/src/apis/getBotInitialData.js +43 -0
- package/src/apis/getFriendsList.js +79 -0
- package/src/apis/getMessage.js +423 -0
- package/src/apis/getTheme.js +95 -0
- package/src/apis/getThemeInfo.js +116 -0
- package/src/apis/getThreadHistory.js +239 -0
- package/src/apis/getThreadInfo.js +267 -0
- package/src/apis/getThreadList.js +232 -0
- package/src/apis/getThreadPictures.js +58 -0
- package/src/apis/getUserID.js +117 -0
- package/src/apis/getUserInfo.js +513 -0
- package/src/{api/users → apis}/getUserInfoV2.js +146 -134
- package/src/apis/handleMessageRequest.js +50 -0
- package/src/apis/httpGet.js +63 -0
- package/src/apis/httpPost.js +89 -0
- package/src/apis/httpPostFormData.js +69 -0
- package/src/apis/listenMqtt.js +1236 -0
- package/src/apis/listenSpeed.js +179 -0
- package/src/apis/logout.js +87 -0
- package/src/apis/markAsDelivered.js +47 -0
- package/src/{api/messaging → apis}/markAsRead.js +99 -88
- package/src/apis/markAsReadAll.js +40 -0
- package/src/apis/markAsSeen.js +70 -0
- package/src/apis/mqttDeltaValue.js +278 -0
- package/src/apis/muteThread.js +45 -0
- package/src/apis/nickname.js +132 -0
- package/src/apis/notes.js +163 -0
- package/src/apis/pinMessage.js +150 -0
- package/src/apis/produceMetaTheme.js +180 -0
- package/src/apis/realtime.js +182 -0
- package/src/apis/removeUserFromGroup.js +117 -0
- package/src/apis/resolvePhotoUrl.js +58 -0
- package/src/apis/searchForThread.js +154 -0
- package/src/apis/sendMessage.js +354 -0
- package/src/apis/sendMessageMqtt.js +249 -0
- package/src/apis/sendTypingIndicator.js +91 -0
- package/src/apis/setMessageReaction.js +27 -0
- package/src/apis/setMessageReactionMqtt.js +61 -0
- package/src/apis/setThreadTheme.js +260 -0
- package/src/apis/setThreadThemeMqtt.js +94 -0
- package/src/apis/share.js +107 -0
- package/src/apis/shareContact.js +66 -0
- package/src/apis/stickers.js +257 -0
- package/src/apis/story.js +181 -0
- package/src/apis/theme.js +233 -0
- package/src/apis/unfriend.js +47 -0
- package/src/apis/unsendMessage.js +17 -0
- package/src/engine/client.js +92 -0
- package/src/engine/models/buildAPI.js +152 -0
- package/src/engine/models/loginHelper.js +519 -0
- package/src/engine/models/setOptions.js +88 -0
- package/src/security/e2ee.js +109 -0
- package/src/types/index.d.ts +498 -0
- package/src/utils/antiSuspension.js +506 -0
- package/src/utils/auth-helpers.js +149 -0
- package/src/utils/autoReLogin.js +336 -0
- package/src/utils/axios.js +436 -0
- package/src/utils/cache.js +54 -0
- package/src/utils/clients.js +282 -0
- package/src/utils/constants.js +410 -23
- package/src/utils/formatters/data/formatAttachment.js +370 -0
- package/src/utils/formatters/data/formatDelta.js +109 -0
- package/src/utils/formatters/index.js +159 -0
- package/src/utils/formatters/value/formatCookie.js +91 -0
- package/src/utils/formatters/value/formatDate.js +36 -0
- package/src/utils/formatters/value/formatID.js +16 -0
- package/src/utils/formatters.js +1373 -0
- package/src/utils/headers.js +214 -99
- package/src/utils/index.js +153 -0
- package/src/utils/monitoring.js +333 -0
- package/src/utils/rateLimiter.js +319 -0
- package/src/utils/tokenRefresh.js +657 -0
- package/src/utils/user-agents.js +238 -0
- package/src/utils/validation.js +157 -0
- package/CHANGELOG.md +0 -181
- package/DOCS.md +0 -2636
- package/LICENSE-MIT +0 -21
- package/README.md +0 -632
- package/func/checkUpdate.js +0 -233
- package/func/logger.js +0 -48
- package/index.d.ts +0 -746
- package/module/config.js +0 -67
- package/module/login.js +0 -154
- package/module/loginHelper.js +0 -1034
- package/module/options.js +0 -54
- package/src/api/action/changeAvatar.js +0 -137
- package/src/api/action/changeBio.js +0 -75
- package/src/api/action/enableAutoSaveAppState.js +0 -73
- package/src/api/action/getCurrentUserID.js +0 -7
- package/src/api/action/handleFriendRequest.js +0 -57
- package/src/api/action/logout.js +0 -76
- package/src/api/action/refreshFb_dtsg.js +0 -48
- package/src/api/action/setPostReaction.js +0 -106
- package/src/api/action/unfriend.js +0 -54
- package/src/api/http/httpGet.js +0 -46
- package/src/api/http/httpPost.js +0 -52
- package/src/api/http/postFormData.js +0 -47
- package/src/api/messaging/addUserToGroup.js +0 -68
- package/src/api/messaging/changeAdminStatus.js +0 -122
- package/src/api/messaging/changeArchivedStatus.js +0 -55
- package/src/api/messaging/changeBlockedStatus.js +0 -48
- package/src/api/messaging/changeGroupImage.js +0 -90
- package/src/api/messaging/changeNickname.js +0 -70
- package/src/api/messaging/changeThreadColor.js +0 -79
- package/src/api/messaging/changeThreadEmoji.js +0 -106
- package/src/api/messaging/createNewGroup.js +0 -88
- package/src/api/messaging/createPoll.js +0 -43
- package/src/api/messaging/createThemeAI.js +0 -98
- package/src/api/messaging/deleteMessage.js +0 -56
- package/src/api/messaging/deleteThread.js +0 -56
- package/src/api/messaging/editMessage.js +0 -68
- package/src/api/messaging/forwardAttachment.js +0 -51
- package/src/api/messaging/getEmojiUrl.js +0 -29
- package/src/api/messaging/getFriendsList.js +0 -82
- package/src/api/messaging/getMessage.js +0 -829
- package/src/api/messaging/getThemePictures.js +0 -62
- package/src/api/messaging/handleMessageRequest.js +0 -65
- package/src/api/messaging/markAsDelivered.js +0 -57
- package/src/api/messaging/markAsReadAll.js +0 -49
- package/src/api/messaging/markAsSeen.js +0 -61
- package/src/api/messaging/muteThread.js +0 -50
- package/src/api/messaging/removeUserFromGroup.js +0 -106
- package/src/api/messaging/resolvePhotoUrl.js +0 -43
- package/src/api/messaging/scheduler.js +0 -264
- package/src/api/messaging/searchForThread.js +0 -52
- package/src/api/messaging/sendMessage.js +0 -272
- package/src/api/messaging/sendTypingIndicator.js +0 -67
- package/src/api/messaging/setMessageReaction.js +0 -76
- package/src/api/messaging/setTitle.js +0 -119
- package/src/api/messaging/shareContact.js +0 -49
- package/src/api/messaging/threadColors.js +0 -128
- package/src/api/messaging/unsendMessage.js +0 -81
- package/src/api/messaging/uploadAttachment.js +0 -94
- package/src/api/socket/core/connectMqtt.js +0 -255
- package/src/api/socket/core/emitAuth.js +0 -106
- package/src/api/socket/core/getSeqID.js +0 -40
- package/src/api/socket/core/getTaskResponseData.js +0 -22
- package/src/api/socket/core/markDelivery.js +0 -12
- package/src/api/socket/core/parseDelta.js +0 -391
- package/src/api/socket/detail/buildStream.js +0 -208
- package/src/api/socket/detail/constants.js +0 -24
- package/src/api/socket/listenMqtt.js +0 -364
- package/src/api/socket/middleware/index.js +0 -216
- package/src/api/threads/getThreadHistory.js +0 -664
- package/src/api/threads/getThreadInfo.js +0 -438
- package/src/api/threads/getThreadList.js +0 -293
- package/src/api/threads/getThreadPictures.js +0 -78
- package/src/api/users/getUserID.js +0 -65
- package/src/api/users/getUserInfo.js +0 -327
- package/src/core/sendReqMqtt.js +0 -96
- package/src/database/models/index.js +0 -87
- package/src/database/models/thread.js +0 -45
- package/src/database/models/user.js +0 -46
- package/src/database/threadData.js +0 -98
- package/src/database/userData.js +0 -89
- package/src/utils/client.js +0 -320
- package/src/utils/format.js +0 -1115
- package/src/utils/request.js +0 -305
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const utils = require('../../utils');
|
|
4
|
+
const SimpleCache = require('../../utils/cache');
|
|
5
|
+
const { globalValidator } = require('../../utils/validation');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Builds the core API context and default functions after successful login.
|
|
9
|
+
*
|
|
10
|
+
* @param {string} html The HTML body from the initial Facebook page.
|
|
11
|
+
* @param {object} jar The cookie jar.
|
|
12
|
+
* @param {Array<object>} netData Network data extracted from the HTML.
|
|
13
|
+
* @param {object} globalOptions The global options object.
|
|
14
|
+
* @param {function} fbLinkFunc A function to generate Facebook links.
|
|
15
|
+
* @param {string} errorRetrievingMsg The error message for retrieving user ID.
|
|
16
|
+
* @returns {Array<object>} An array containing [ctx, defaultFuncs, {}].
|
|
17
|
+
*/
|
|
18
|
+
async function buildAPI(html, jar, netData, globalOptions, fbLinkFunc, errorRetrievingMsg) {
|
|
19
|
+
let userID;
|
|
20
|
+
const cookies = jar.getCookiesSync(fbLinkFunc());
|
|
21
|
+
const primaryProfile = cookies.find((val) => val.cookieString().startsWith("c_user="));
|
|
22
|
+
const secondaryProfile = cookies.find((val) => val.cookieString().startsWith("i_user="));
|
|
23
|
+
if (!primaryProfile && !secondaryProfile) {
|
|
24
|
+
throw new Error(errorRetrievingMsg);
|
|
25
|
+
}
|
|
26
|
+
userID = secondaryProfile?.cookieString().split("=")[1] || primaryProfile.cookieString().split("=")[1];
|
|
27
|
+
|
|
28
|
+
const findConfig = (key) => {
|
|
29
|
+
for (const scriptData of netData) {
|
|
30
|
+
if (scriptData.require) {
|
|
31
|
+
for (const req of scriptData.require) {
|
|
32
|
+
if (Array.isArray(req) && req[0] === key && req[2]) {
|
|
33
|
+
return req[2];
|
|
34
|
+
}
|
|
35
|
+
if (Array.isArray(req) && req[3] && req[3][0] && req[3][0].__bbox && req[3][0].__bbox.define) {
|
|
36
|
+
for (const def of req[3][0].__bbox.define) {
|
|
37
|
+
if (Array.isArray(def) && def[0].endsWith(key) && def[2]) {
|
|
38
|
+
return def[2];
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return null;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const dtsgData = findConfig("DTSGInitialData");
|
|
49
|
+
const dtsg = dtsgData ? dtsgData.token : utils.getFrom(html, '"token":"', '"');
|
|
50
|
+
|
|
51
|
+
const lsdData = findConfig("LSD");
|
|
52
|
+
const lsd = lsdData ? lsdData.token : utils.getFrom(html, '"LSD",[],{"token":"', '"');
|
|
53
|
+
|
|
54
|
+
// Extract additional DTSG AG token for better session persistence
|
|
55
|
+
const dtsgAgData = findConfig("DTSGAGInitialData");
|
|
56
|
+
const fb_dtsg_ag = dtsgAgData ? dtsgAgData.token : utils.getFrom(html, '"DTSGAGInitialData",[],{"token":"', '"');
|
|
57
|
+
|
|
58
|
+
// Extract spin parameters for consistency
|
|
59
|
+
const spinRMatch = html.match(/"__spin_r":(\d+)/);
|
|
60
|
+
const __spin_r = spinRMatch ? spinRMatch[1] : undefined;
|
|
61
|
+
|
|
62
|
+
const spinBMatch = html.match(/"__spin_b":"([^"]+)"/);
|
|
63
|
+
const __spin_b = spinBMatch ? spinBMatch[1] : undefined;
|
|
64
|
+
|
|
65
|
+
const spinTMatch = html.match(/"__spin_t":(\d+)/);
|
|
66
|
+
const __spin_t = spinTMatch ? spinTMatch[1] : undefined;
|
|
67
|
+
|
|
68
|
+
// Extract hsi (host session identifier)
|
|
69
|
+
const hsiMatch = html.match(/"hsi":"(\d+)"/);
|
|
70
|
+
const hsi = hsiMatch ? hsiMatch[1] : undefined;
|
|
71
|
+
|
|
72
|
+
// Extract dyn and csr for consistency
|
|
73
|
+
const dynMatch = html.match(/"dyn":"([^"]+)"/);
|
|
74
|
+
const dyn = dynMatch ? dynMatch[1] : undefined;
|
|
75
|
+
|
|
76
|
+
const csrMatch = html.match(/"csr":"([^"]+)"/);
|
|
77
|
+
const csr = csrMatch ? csrMatch[1] : undefined;
|
|
78
|
+
|
|
79
|
+
const dtsgResult = {
|
|
80
|
+
fb_dtsg: dtsg,
|
|
81
|
+
jazoest: `2${Array.from(dtsg).reduce((a, b) => a + b.charCodeAt(0), '')}`,
|
|
82
|
+
lsd: lsd,
|
|
83
|
+
fb_dtsg_ag: fb_dtsg_ag,
|
|
84
|
+
__spin_r: __spin_r,
|
|
85
|
+
__spin_b: __spin_b,
|
|
86
|
+
__spin_t: __spin_t,
|
|
87
|
+
hsi: hsi,
|
|
88
|
+
dyn: dyn,
|
|
89
|
+
csr: csr
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const clientIDData = findConfig("MqttWebDeviceID");
|
|
93
|
+
const clientID = clientIDData ? clientIDData.clientID : undefined;
|
|
94
|
+
|
|
95
|
+
const mqttConfigData = findConfig("MqttWebConfig");
|
|
96
|
+
const mqttAppID = mqttConfigData ? mqttConfigData.appID : undefined;
|
|
97
|
+
|
|
98
|
+
const currentUserData = findConfig("CurrentUserInitialData");
|
|
99
|
+
const userAppID = currentUserData ? currentUserData.APP_ID : undefined;
|
|
100
|
+
|
|
101
|
+
let primaryAppID = userAppID || mqttAppID;
|
|
102
|
+
|
|
103
|
+
let mqttEndpoint = mqttConfigData ? mqttConfigData.endpoint : undefined;
|
|
104
|
+
|
|
105
|
+
let region;
|
|
106
|
+
if (mqttEndpoint) {
|
|
107
|
+
try {
|
|
108
|
+
region = new URL(mqttEndpoint).searchParams.get("region")?.toUpperCase();
|
|
109
|
+
} catch (_) {
|
|
110
|
+
// Malformed or missing MQTT endpoint — region stays undefined.
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
const irisSeqIDMatch = html.match(/irisSeqID:"(.+?)"/);
|
|
114
|
+
const irisSeqID = irisSeqIDMatch ? irisSeqIDMatch[1] : null;
|
|
115
|
+
if (globalOptions.bypassRegion && mqttEndpoint) {
|
|
116
|
+
const currentEndpoint = new URL(mqttEndpoint);
|
|
117
|
+
currentEndpoint.searchParams.set('region', globalOptions.bypassRegion.toLowerCase());
|
|
118
|
+
mqttEndpoint = currentEndpoint.toString();
|
|
119
|
+
region = globalOptions.bypassRegion.toUpperCase();
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const ctx = {
|
|
123
|
+
userID,
|
|
124
|
+
jar,
|
|
125
|
+
clientID,
|
|
126
|
+
appID: primaryAppID,
|
|
127
|
+
mqttAppID: mqttAppID,
|
|
128
|
+
userAppID: userAppID,
|
|
129
|
+
globalOptions,
|
|
130
|
+
loggedIn: true,
|
|
131
|
+
access_token: "NONE",
|
|
132
|
+
clientMutationId: 0,
|
|
133
|
+
mqttClient: undefined,
|
|
134
|
+
lastSeqId: irisSeqID,
|
|
135
|
+
syncToken: undefined,
|
|
136
|
+
mqttEndpoint,
|
|
137
|
+
wsReqNumber: 0,
|
|
138
|
+
wsTaskNumber: 0,
|
|
139
|
+
reqCallbacks: {},
|
|
140
|
+
callback_Task: {},
|
|
141
|
+
region,
|
|
142
|
+
firstListen: true,
|
|
143
|
+
cache: new SimpleCache(),
|
|
144
|
+
validator: globalValidator,
|
|
145
|
+
...dtsgResult,
|
|
146
|
+
};
|
|
147
|
+
const defaultFuncs = utils.makeDefaults(html, userID, ctx);
|
|
148
|
+
|
|
149
|
+
return [ctx, defaultFuncs, {}];
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
module.exports = buildAPI;
|
|
@@ -0,0 +1,519 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const utils = require('../../utils');
|
|
4
|
+
const axios = require("axios");
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const qs = require("querystring");
|
|
8
|
+
const { normalizeCookieHeaderString, setJarFromPairs } = require('../../utils/formatters/value/formatCookie');
|
|
9
|
+
const { parseRegion, genTotp } = require('../../utils/auth-helpers');
|
|
10
|
+
const { generateUserAgentByPersona, cachePersonaData } = require('../../utils/user-agents');
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* The main login helper function, orchestrating the login process.
|
|
14
|
+
*
|
|
15
|
+
* @param {object} credentials User credentials or appState.
|
|
16
|
+
* @param {object} globalOptions Global options for the API.
|
|
17
|
+
* @param {function} callback The final callback function.
|
|
18
|
+
* @param {function} setOptionsFunc Reference to the setOptions function from models.
|
|
19
|
+
* @param {function} buildAPIFunc Reference to the buildAPI function from models.
|
|
20
|
+
* @param {object} initialApi The initial API object to extend.
|
|
21
|
+
* @param {function} fbLinkFunc A function to generate Facebook links.
|
|
22
|
+
* @param {string} errorRetrievingMsg The error message for retrieving user ID.
|
|
23
|
+
* @returns {Promise<void>}
|
|
24
|
+
*/
|
|
25
|
+
async function loginHelper(credentials, globalOptions, callback, setOptionsFunc, buildAPIFunc, initialApi, fbLinkFunc, errorRetrievingMsg) {
|
|
26
|
+
let ctx = null;
|
|
27
|
+
let defaultFuncs = null;
|
|
28
|
+
let api = initialApi;
|
|
29
|
+
|
|
30
|
+
// Display startup banner
|
|
31
|
+
const { startupBanner } = require('../../utils');
|
|
32
|
+
startupBanner();
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
const jar = utils.getJar();
|
|
36
|
+
utils.log("Logging in...");
|
|
37
|
+
|
|
38
|
+
const persona = globalOptions.persona || 'desktop';
|
|
39
|
+
const personaSwitched = globalOptions.cachedPersona && globalOptions.cachedPersona !== persona;
|
|
40
|
+
|
|
41
|
+
if (personaSwitched) {
|
|
42
|
+
const oldPersona = globalOptions.cachedPersona;
|
|
43
|
+
utils.log(`Persona switched from ${oldPersona} to ${persona}, clearing ALL cached fingerprints`);
|
|
44
|
+
|
|
45
|
+
delete globalOptions.cachedUserAgent;
|
|
46
|
+
delete globalOptions.cachedSecChUa;
|
|
47
|
+
delete globalOptions.cachedSecChUaFullVersionList;
|
|
48
|
+
delete globalOptions.cachedSecChUaPlatform;
|
|
49
|
+
delete globalOptions.cachedSecChUaPlatformVersion;
|
|
50
|
+
delete globalOptions.cachedBrowser;
|
|
51
|
+
|
|
52
|
+
delete globalOptions.cachedAndroidUA;
|
|
53
|
+
delete globalOptions.cachedAndroidVersion;
|
|
54
|
+
delete globalOptions.cachedAndroidDevice;
|
|
55
|
+
delete globalOptions.cachedAndroidBuildId;
|
|
56
|
+
delete globalOptions.cachedAndroidResolution;
|
|
57
|
+
delete globalOptions.cachedAndroidFbav;
|
|
58
|
+
delete globalOptions.cachedAndroidFbbv;
|
|
59
|
+
delete globalOptions.cachedAndroidLocale;
|
|
60
|
+
delete globalOptions.cachedAndroidCarrier;
|
|
61
|
+
|
|
62
|
+
delete globalOptions.cachedLocale;
|
|
63
|
+
delete globalOptions.cachedTimezone;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const needsDesktopCache = (persona === 'desktop') && !globalOptions.cachedUserAgent;
|
|
67
|
+
const needsAndroidCache = (persona === 'android' || persona === 'mobile') && !globalOptions.cachedAndroidUA;
|
|
68
|
+
|
|
69
|
+
if (needsDesktopCache || needsAndroidCache) {
|
|
70
|
+
const personaData = generateUserAgentByPersona(persona, globalOptions);
|
|
71
|
+
cachePersonaData(globalOptions, personaData);
|
|
72
|
+
globalOptions.cachedPersona = persona;
|
|
73
|
+
|
|
74
|
+
if (persona === 'desktop') {
|
|
75
|
+
utils.log("Using desktop persona with browser:", personaData.browser);
|
|
76
|
+
} else {
|
|
77
|
+
utils.log("Using Android/Orca mobile persona");
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const { getRandomLocale, getRandomTimezone } = require('../../utils/headers');
|
|
81
|
+
if (!globalOptions.cachedLocale) {
|
|
82
|
+
globalOptions.cachedLocale = getRandomLocale();
|
|
83
|
+
}
|
|
84
|
+
if (!globalOptions.cachedTimezone) {
|
|
85
|
+
globalOptions.cachedTimezone = getRandomTimezone();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Lock the session fingerprint in anti-suspension module so it
|
|
89
|
+
// stays consistent for the entire session — UA/platform changes
|
|
90
|
+
// between requests are a strong bot detection signal.
|
|
91
|
+
try {
|
|
92
|
+
const { globalAntiSuspension } = require('../../utils/antiSuspension');
|
|
93
|
+
globalAntiSuspension.lockSessionFingerprint(
|
|
94
|
+
personaData.userAgent || globalOptions.cachedAndroidUA,
|
|
95
|
+
personaData.secChUa || '',
|
|
96
|
+
personaData.secChUaPlatform || personaData.persona || 'desktop',
|
|
97
|
+
globalOptions.cachedLocale,
|
|
98
|
+
globalOptions.cachedTimezone
|
|
99
|
+
);
|
|
100
|
+
} catch (_) {}
|
|
101
|
+
} else {
|
|
102
|
+
if (persona === 'desktop' && globalOptions.cachedUserAgent) {
|
|
103
|
+
utils.log("Using cached desktop persona");
|
|
104
|
+
} else if ((persona === 'android' || persona === 'mobile') && globalOptions.cachedAndroidUA) {
|
|
105
|
+
utils.log("Using cached Android/Orca mobile persona");
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
let appState = credentials.appState;
|
|
110
|
+
|
|
111
|
+
if (appState) {
|
|
112
|
+
let cookieStrings = [];
|
|
113
|
+
if (Array.isArray(appState)) {
|
|
114
|
+
cookieStrings = appState.map(c => [c.name || c.key, c.value].join('='));
|
|
115
|
+
} else if (typeof appState === 'string') {
|
|
116
|
+
cookieStrings = normalizeCookieHeaderString(appState);
|
|
117
|
+
|
|
118
|
+
if (cookieStrings.length === 0) {
|
|
119
|
+
cookieStrings = appState.split(';').map(s => s.trim()).filter(Boolean);
|
|
120
|
+
}
|
|
121
|
+
} else {
|
|
122
|
+
throw new Error("Invalid appState format. Please provide an array of cookie objects or a cookie string.");
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
setJarFromPairs(jar, cookieStrings);
|
|
126
|
+
utils.log("Cookies set for facebook.com and messenger.com domains");
|
|
127
|
+
|
|
128
|
+
} else if (credentials.email && credentials.password) {
|
|
129
|
+
|
|
130
|
+
if (credentials.totpSecret) {
|
|
131
|
+
utils.log("TOTP secret detected, will generate 2FA code if needed");
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const url = "https://api.facebook.com/method/auth.login";
|
|
135
|
+
const params = {
|
|
136
|
+
access_token: "350685531728|62f8ce9f74b12f84c123cc23437a4a32",
|
|
137
|
+
format: "json",
|
|
138
|
+
sdk_version: 2,
|
|
139
|
+
email: credentials.email,
|
|
140
|
+
locale: "en_US",
|
|
141
|
+
password: credentials.password,
|
|
142
|
+
generate_session_cookies: 1,
|
|
143
|
+
sig: "c1c640010993db92e5afd11634ced864",
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (credentials.totpSecret) {
|
|
147
|
+
try {
|
|
148
|
+
const totpCode = await genTotp(credentials.totpSecret);
|
|
149
|
+
params.credentials_type = "two_factor";
|
|
150
|
+
params.twofactor_code = totpCode;
|
|
151
|
+
utils.log("TOTP code generated successfully");
|
|
152
|
+
} catch (totpError) {
|
|
153
|
+
utils.warn("Failed to generate TOTP code:", totpError.message);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const query = qs.stringify(params);
|
|
158
|
+
const xurl = `${url}?${query}`;
|
|
159
|
+
try {
|
|
160
|
+
const resp = await axios.get(xurl);
|
|
161
|
+
if (resp.status !== 200) {
|
|
162
|
+
throw new Error("Wrong password / email");
|
|
163
|
+
}
|
|
164
|
+
let cstrs = resp.data["session_cookies"].map(c => `${c.name}=${c.value}`);
|
|
165
|
+
setJarFromPairs(jar, cstrs);
|
|
166
|
+
utils.log("Login successful with email/password");
|
|
167
|
+
} catch (e) {
|
|
168
|
+
if (credentials.totpSecret && !params.twofactor_code) {
|
|
169
|
+
throw new Error("2FA required but TOTP code generation failed");
|
|
170
|
+
}
|
|
171
|
+
throw new Error("Wrong password / email");
|
|
172
|
+
}
|
|
173
|
+
} else {
|
|
174
|
+
throw new Error("No cookie or credentials found. Please provide cookies or credentials.");
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (!api) {
|
|
178
|
+
api = {
|
|
179
|
+
setOptions: setOptionsFunc.bind(null, globalOptions),
|
|
180
|
+
getAppState() {
|
|
181
|
+
const appState = utils.getAppState(jar);
|
|
182
|
+
if (!Array.isArray(appState)) return [];
|
|
183
|
+
const uniqueAppState = appState.filter((item, index, self) => self.findIndex((t) => t.key === item.key) === index);
|
|
184
|
+
return uniqueAppState.length > 0 ? uniqueAppState : appState;
|
|
185
|
+
},
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Enable warm-up mode for fresh logins — activity ramps up gradually
|
|
190
|
+
// which mimics a human just starting to use the app.
|
|
191
|
+
try {
|
|
192
|
+
const { globalAntiSuspension } = require('../../utils/antiSuspension');
|
|
193
|
+
globalAntiSuspension.resetCircuitBreaker();
|
|
194
|
+
globalAntiSuspension.enableWarmup();
|
|
195
|
+
} catch (_) {}
|
|
196
|
+
|
|
197
|
+
const resp = await utils.get(fbLinkFunc(), jar, null, globalOptions, { noRef: true }).then(utils.saveCookies(jar));
|
|
198
|
+
const extractNetData = (html) => {
|
|
199
|
+
const allScriptsData = [];
|
|
200
|
+
const scriptRegex = /<script type="application\/json"[^>]*>(.*?)<\/script>/g;
|
|
201
|
+
let match;
|
|
202
|
+
while ((match = scriptRegex.exec(html)) !== null) {
|
|
203
|
+
try {
|
|
204
|
+
allScriptsData.push(JSON.parse(match[1]));
|
|
205
|
+
} catch (e) {
|
|
206
|
+
utils.error(`Failed to parse a JSON blob from HTML`, e.message);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return allScriptsData;
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
const netData = extractNetData(resp.body);
|
|
213
|
+
|
|
214
|
+
const [newCtx, newDefaultFuncs] = await buildAPIFunc(resp.body, jar, netData, globalOptions, fbLinkFunc, errorRetrievingMsg);
|
|
215
|
+
ctx = newCtx;
|
|
216
|
+
defaultFuncs = newDefaultFuncs;
|
|
217
|
+
|
|
218
|
+
const region = parseRegion(resp.body);
|
|
219
|
+
ctx.region = region;
|
|
220
|
+
utils.log("Detected Facebook region:", region);
|
|
221
|
+
|
|
222
|
+
api.message = new Map();
|
|
223
|
+
api.timestamp = {};
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Loads API modules from the apis directory.
|
|
227
|
+
*
|
|
228
|
+
* @returns {void}
|
|
229
|
+
*/
|
|
230
|
+
const loadApiModules = () => {
|
|
231
|
+
const apiPath = path.join(__dirname, '..', '..', 'apis');
|
|
232
|
+
|
|
233
|
+
if (!fs.existsSync(apiPath)) {
|
|
234
|
+
utils.error('API directory not found:', apiPath);
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const helperModules = ['mqttDeltaValue'];
|
|
239
|
+
|
|
240
|
+
fs.readdirSync(apiPath)
|
|
241
|
+
.filter(file => file.endsWith('.js'))
|
|
242
|
+
.forEach(file => {
|
|
243
|
+
const moduleName = path.basename(file, '.js');
|
|
244
|
+
|
|
245
|
+
if (helperModules.includes(moduleName)) {
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const fullPath = path.join(apiPath, file);
|
|
250
|
+
try {
|
|
251
|
+
const moduleExport = require(fullPath);
|
|
252
|
+
if (typeof moduleExport === 'function') {
|
|
253
|
+
api[moduleName] = moduleExport(defaultFuncs, api, ctx);
|
|
254
|
+
}
|
|
255
|
+
} catch (e) {
|
|
256
|
+
utils.error(`Failed to load API module ${moduleName}:`, e);
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
api.getCurrentUserID = () => ctx.userID;
|
|
262
|
+
api.getOptions = (key) => key ? globalOptions[key] : globalOptions;
|
|
263
|
+
loadApiModules();
|
|
264
|
+
|
|
265
|
+
if (api.nickname && typeof api.nickname === 'function') {
|
|
266
|
+
api.changeNickname = api.nickname;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
api.ctx = ctx;
|
|
270
|
+
api.defaultFuncs = defaultFuncs;
|
|
271
|
+
api.globalOptions = globalOptions;
|
|
272
|
+
|
|
273
|
+
const { TokenRefreshManager } = require('../../utils/tokenRefresh');
|
|
274
|
+
if (api.tokenRefreshManager) {
|
|
275
|
+
api.tokenRefreshManager.stopAutoRefresh();
|
|
276
|
+
} else {
|
|
277
|
+
api.tokenRefreshManager = new TokenRefreshManager();
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const { globalAutoReLoginManager } = require('../../utils/autoReLogin');
|
|
281
|
+
|
|
282
|
+
if (globalOptions.autoReLogin !== false) {
|
|
283
|
+
globalAutoReLoginManager.setCredentials(credentials, globalOptions, callback);
|
|
284
|
+
utils.log("AutoReLogin", "Auto re-login enabled with stored credentials");
|
|
285
|
+
// NOTE: startSessionMonitoring(api) is called later, after api.isSessionValid
|
|
286
|
+
// is registered, so the health-check interval can actually call it.
|
|
287
|
+
try {
|
|
288
|
+
const appState = api.getAppState();
|
|
289
|
+
globalAutoReLoginManager.updateAppState(appState);
|
|
290
|
+
} catch (_) {}
|
|
291
|
+
|
|
292
|
+
api.tokenRefreshManager.setSessionExpiryCallback((error) => {
|
|
293
|
+
utils.warn("TokenRefresh", "Session expiry detected. Triggering auto re-login...");
|
|
294
|
+
globalAutoReLoginManager.handleSessionExpiry(api, fbLinkFunc(), errorRetrievingMsg);
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
// Wire ctx.performAutoLogin so the axios response inspector can
|
|
298
|
+
// trigger re-login directly when it detects a login-redirect in any
|
|
299
|
+
// API response, without waiting for the next scheduled health check.
|
|
300
|
+
ctx.performAutoLogin = async () => {
|
|
301
|
+
try {
|
|
302
|
+
const result = await globalAutoReLoginManager.handleSessionExpiry(
|
|
303
|
+
api,
|
|
304
|
+
fbLinkFunc(),
|
|
305
|
+
errorRetrievingMsg
|
|
306
|
+
);
|
|
307
|
+
return result !== false;
|
|
308
|
+
} catch (_) {
|
|
309
|
+
return false;
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
api.logout = () => {
|
|
315
|
+
const logoutFn = require('../../apis/logout')(defaultFuncs, api, ctx);
|
|
316
|
+
return logoutFn();
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
// Graceful shutdown handler - clean up all resources
|
|
320
|
+
const cleanup = () => {
|
|
321
|
+
utils.log("Shutdown", "Cleaning up resources...");
|
|
322
|
+
|
|
323
|
+
// Stop token refresh
|
|
324
|
+
if (api.tokenRefreshManager) {
|
|
325
|
+
api.tokenRefreshManager.stopAutoRefresh();
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Stop session monitoring
|
|
329
|
+
if (globalAutoReLoginManager) {
|
|
330
|
+
globalAutoReLoginManager.stopSessionMonitoring();
|
|
331
|
+
globalAutoReLoginManager.disable();
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// Stop MQTT
|
|
335
|
+
if (ctx.mqttClient && typeof ctx.mqttClient.end === 'function') {
|
|
336
|
+
try {
|
|
337
|
+
ctx.mqttClient.end(true);
|
|
338
|
+
} catch (_) {}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// Stop listening
|
|
342
|
+
if (ctx._emitter) {
|
|
343
|
+
ctx._emitter.removeAllListeners();
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// Clear intervals
|
|
347
|
+
if (ctx._mqttWatchdog) clearInterval(ctx._mqttWatchdog);
|
|
348
|
+
if (ctx._tmsTimeout) clearTimeout(ctx._tmsTimeout);
|
|
349
|
+
if (ctx._autoCycleTimer) clearInterval(ctx._autoCycleTimer);
|
|
350
|
+
if (ctx._reconnectTimer) clearTimeout(ctx._reconnectTimer);
|
|
351
|
+
if (ctx._periodicBackupInterval) clearInterval(ctx._periodicBackupInterval);
|
|
352
|
+
|
|
353
|
+
utils.log("Shutdown", "Cleanup complete");
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
// Register cleanup handlers using named functions so they can be removed
|
|
357
|
+
// if loginHelper is called again (e.g. after auto re-login), preventing
|
|
358
|
+
// accumulation of stale handlers across restarts.
|
|
359
|
+
if (!process._nkxfcaCleanupRegistered) {
|
|
360
|
+
process._nkxfcaCleanupRegistered = true;
|
|
361
|
+
process.on('exit', () => cleanup());
|
|
362
|
+
process.on('SIGINT', () => { cleanup(); process.exit(0); });
|
|
363
|
+
process.on('SIGTERM', () => { cleanup(); process.exit(0); });
|
|
364
|
+
process.on('uncaughtException', (err) => {
|
|
365
|
+
utils.error("Uncaught Exception", err.message);
|
|
366
|
+
cleanup();
|
|
367
|
+
process.exit(1);
|
|
368
|
+
});
|
|
369
|
+
process.on('unhandledRejection', (reason) => {
|
|
370
|
+
utils.error("Unhandled Rejection", String(reason && reason.message ? reason.message : reason));
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
api.tokenRefreshManager.startAutoRefresh(ctx, defaultFuncs, fbLinkFunc());
|
|
375
|
+
|
|
376
|
+
api.refreshTokens = () => api.tokenRefreshManager.refreshTokens(ctx, defaultFuncs, fbLinkFunc());
|
|
377
|
+
api.getTokenRefreshStatus = () => api.tokenRefreshManager.getStatus();
|
|
378
|
+
api.getHealthStatus = () => {
|
|
379
|
+
const mqttConnected = !!(ctx.mqttClient && ctx.mqttClient.connected);
|
|
380
|
+
const rateStats = (() => {
|
|
381
|
+
try {
|
|
382
|
+
const { getRateLimiterStats } = require('../../utils/rateLimiter');
|
|
383
|
+
return getRateLimiterStats();
|
|
384
|
+
} catch (_e) {
|
|
385
|
+
return null;
|
|
386
|
+
}
|
|
387
|
+
})();
|
|
388
|
+
return {
|
|
389
|
+
mqttConnected,
|
|
390
|
+
autoReconnect: !!ctx.globalOptions.autoReconnect,
|
|
391
|
+
tokenRefresh: {
|
|
392
|
+
lastRefresh: api.tokenRefreshManager.lastRefresh,
|
|
393
|
+
nextRefresh: api.tokenRefreshManager.getTimeUntilNextRefresh(),
|
|
394
|
+
failureCount: api.tokenRefreshManager.getFailureCount()
|
|
395
|
+
},
|
|
396
|
+
autoReLogin: {
|
|
397
|
+
enabled: globalAutoReLoginManager.isEnabled(),
|
|
398
|
+
sessionMonitoring: !!globalAutoReLoginManager.sessionMonitorInterval
|
|
399
|
+
},
|
|
400
|
+
rateLimiter: rateStats
|
|
401
|
+
};
|
|
402
|
+
};
|
|
403
|
+
api.enableAutoReLogin = (enable = true) => {
|
|
404
|
+
if (enable) {
|
|
405
|
+
globalAutoReLoginManager.setCredentials(credentials, globalOptions, callback);
|
|
406
|
+
} else {
|
|
407
|
+
globalAutoReLoginManager.disable();
|
|
408
|
+
}
|
|
409
|
+
};
|
|
410
|
+
api.isAutoReLoginEnabled = () => globalAutoReLoginManager.isEnabled();
|
|
411
|
+
|
|
412
|
+
api.isSessionValid = () => {
|
|
413
|
+
return new Promise(async (resolve) => {
|
|
414
|
+
try {
|
|
415
|
+
// Use the lightweight presence endpoint instead of fetching the
|
|
416
|
+
// full homepage (~400 kB). Returns 200 JSON when authenticated,
|
|
417
|
+
// 302→login when the session is expired.
|
|
418
|
+
//
|
|
419
|
+
// IMPORTANT: use _skipSessionInspect so the axios response
|
|
420
|
+
// inspector does NOT try to trigger performAutoLogin from inside
|
|
421
|
+
// this check — that would cause a reentrant re-login call.
|
|
422
|
+
const probeCtx = { noRef: true, _skipSessionInspect: true };
|
|
423
|
+
const resp = await utils.get(
|
|
424
|
+
'https://www.facebook.com/ajax/presence/reconnect.php?reason=14&fb_dtsg_ag=&__a=1',
|
|
425
|
+
ctx.jar, null, ctx.globalOptions, probeCtx
|
|
426
|
+
);
|
|
427
|
+
const html = resp.body || '';
|
|
428
|
+
|
|
429
|
+
// Any redirect to /login indicates a dead session.
|
|
430
|
+
const isLoginPage = html.includes('<form id="login_form"') ||
|
|
431
|
+
html.includes('id="loginbutton"') ||
|
|
432
|
+
html.includes('"login_page"') ||
|
|
433
|
+
html.includes('id="email" name="email"') ||
|
|
434
|
+
html.includes('name="pass"') ||
|
|
435
|
+
html.includes('action="/login.php');
|
|
436
|
+
if (isLoginPage) return resolve(false);
|
|
437
|
+
|
|
438
|
+
const isCheckpoint = html.includes('"checkpoint"') && html.includes('"flow_type"');
|
|
439
|
+
if (isCheckpoint) {
|
|
440
|
+
try {
|
|
441
|
+
const { globalAntiSuspension } = require('../../utils/antiSuspension');
|
|
442
|
+
globalAntiSuspension.tripCircuitBreaker('checkpoint_detected', 60 * 60 * 1000);
|
|
443
|
+
} catch (_) {}
|
|
444
|
+
return resolve(false);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// Verify we have valid tokens in context — the presence endpoint
|
|
448
|
+
// returning a non-login page is sufficient proof the session is alive.
|
|
449
|
+
const hasValidTokens = !!(ctx.fb_dtsg && ctx.fb_dtsg.length > 10);
|
|
450
|
+
resolve(hasValidTokens);
|
|
451
|
+
} catch (error) {
|
|
452
|
+
const msg = error.message || String(error || '');
|
|
453
|
+
const code = error.code || '';
|
|
454
|
+
|
|
455
|
+
// Distinguish transient network errors from real auth failures.
|
|
456
|
+
// Network errors should NOT be treated as session expiry — the
|
|
457
|
+
// session is likely fine, just the network blipped.
|
|
458
|
+
const NETWORK_CODES = ['ECONNRESET','ETIMEDOUT','ECONNREFUSED','ENETUNREACH',
|
|
459
|
+
'EHOSTUNREACH','EAI_AGAIN','ENOTFOUND','ESOCKETTIMEDOUT'];
|
|
460
|
+
const isNetworkErr = NETWORK_CODES.some(c => code === c || msg.includes(c)) ||
|
|
461
|
+
msg.includes('socket hang up') || msg.includes('network error') ||
|
|
462
|
+
msg.includes('connect ETIMEDOUT');
|
|
463
|
+
if (isNetworkErr) {
|
|
464
|
+
utils.warn("Session validation — network error (treating as valid, not triggering re-login):", msg);
|
|
465
|
+
return resolve('network_error');
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
utils.error("Session validation failed:", msg);
|
|
469
|
+
resolve(false);
|
|
470
|
+
}
|
|
471
|
+
});
|
|
472
|
+
};
|
|
473
|
+
|
|
474
|
+
// Start session monitoring now that api.isSessionValid is defined.
|
|
475
|
+
if (globalOptions.autoReLogin !== false) {
|
|
476
|
+
try {
|
|
477
|
+
const { globalAutoReLoginManager: arm } = require('../../utils/autoReLogin');
|
|
478
|
+
arm.startSessionMonitoring(api);
|
|
479
|
+
utils.log("AutoReLogin", "Session monitoring started");
|
|
480
|
+
} catch (_) {}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
// Expose anti-suspension controls on the API object
|
|
484
|
+
try {
|
|
485
|
+
const { globalAntiSuspension } = require('../../utils/antiSuspension');
|
|
486
|
+
api.antiSuspension = {
|
|
487
|
+
getConfig: () => globalAntiSuspension.getConfig(),
|
|
488
|
+
getHealth: () => globalAntiSuspension.checkAccountHealth(null),
|
|
489
|
+
tripCircuitBreaker: (reason, ms) => globalAntiSuspension.tripCircuitBreaker(reason, ms),
|
|
490
|
+
resetCircuitBreaker: () => globalAntiSuspension.resetCircuitBreaker(),
|
|
491
|
+
isCircuitBreakerTripped: () => globalAntiSuspension.isCircuitBreakerTripped(),
|
|
492
|
+
getDailyStats: () => globalAntiSuspension.dailyStats,
|
|
493
|
+
getHourlyStats: () => globalAntiSuspension.hourlyBucket,
|
|
494
|
+
detectSignal: (text) => globalAntiSuspension.detectSuspensionSignal(text)
|
|
495
|
+
};
|
|
496
|
+
} catch (_) {}
|
|
497
|
+
|
|
498
|
+
api.validateSession = async () => {
|
|
499
|
+
const isValid = await api.isSessionValid();
|
|
500
|
+
if (!isValid) {
|
|
501
|
+
utils.warn("Session validation failed - session may be expired");
|
|
502
|
+
// Trigger token refresh which will handle session expiry
|
|
503
|
+
try {
|
|
504
|
+
await api.tokenRefreshManager.refreshTokens(ctx, defaultFuncs, 'https://www.facebook.com');
|
|
505
|
+
} catch (error) {
|
|
506
|
+
utils.error("Failed to refresh session:", error.message);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
return isValid;
|
|
510
|
+
};
|
|
511
|
+
|
|
512
|
+
return callback(null, api);
|
|
513
|
+
} catch (error) {
|
|
514
|
+
utils.error("loginHelper", error.error || error);
|
|
515
|
+
return callback(error);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
module.exports = loginHelper;
|