@dongdev/fca-unofficial 3.0.8 → 3.0.10

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/CHANGELOG.md CHANGED
@@ -152,3 +152,9 @@ Too lazy to write changelog, sorry! (will write changelog in the next release, t
152
152
 
153
153
  ## v3.0.7 - 2025-11-27
154
154
  - Hotfix / auto bump
155
+
156
+ ## v3.0.8 - 2025-11-27
157
+ - Hotfix / auto bump
158
+
159
+ ## v3.0.9 - 2025-12-05
160
+ - Hotfix / auto bump
package/module/login.js CHANGED
@@ -3,10 +3,77 @@ const { setOptions } = require("./options");
3
3
  const { loadConfig } = require("./config");
4
4
  const { checkAndUpdateVersion } = require("../func/checkUpdate");
5
5
  const loginHelper = require("./loginHelper");
6
+ const logger = require("../func/logger");
6
7
 
7
8
  const { config } = loadConfig();
8
9
  global.fca = { config };
9
10
 
11
+ // Global error handlers to prevent bot crashes
12
+ // Handle unhandled promise rejections (e.g., fetch timeouts, network errors)
13
+ if (!global.fca._errorHandlersInstalled) {
14
+ global.fca._errorHandlersInstalled = true;
15
+
16
+ process.on("unhandledRejection", (reason, promise) => {
17
+ try {
18
+ // Check if it's a fetch/network timeout error
19
+ if (reason && typeof reason === "object") {
20
+ const errorCode = reason.code || reason.cause?.code;
21
+ const errorMessage = reason.message || String(reason);
22
+
23
+ // Handle fetch timeout errors gracefully
24
+ if (errorCode === "UND_ERR_CONNECT_TIMEOUT" ||
25
+ errorCode === "ETIMEDOUT" ||
26
+ errorMessage.includes("Connect Timeout") ||
27
+ errorMessage.includes("fetch failed")) {
28
+ logger(`Network timeout error caught (non-fatal): ${errorMessage}`, "warn");
29
+ return; // Don't crash, just log
30
+ }
31
+
32
+ // Handle other network errors
33
+ if (errorCode === "ECONNREFUSED" ||
34
+ errorCode === "ENOTFOUND" ||
35
+ errorCode === "ECONNRESET" ||
36
+ errorMessage.includes("ECONNREFUSED") ||
37
+ errorMessage.includes("ENOTFOUND")) {
38
+ logger(`Network connection error caught (non-fatal): ${errorMessage}`, "warn");
39
+ return; // Don't crash, just log
40
+ }
41
+ }
42
+
43
+ // For other unhandled rejections, log but don't crash
44
+ logger(`Unhandled promise rejection (non-fatal): ${reason && reason.message ? reason.message : String(reason)}`, "error");
45
+ } catch (e) {
46
+ // Fallback if logger fails
47
+ console.error("[FCA-ERROR] Unhandled promise rejection:", reason);
48
+ }
49
+ });
50
+
51
+ // Handle uncaught exceptions (last resort)
52
+ process.on("uncaughtException", (error) => {
53
+ try {
54
+ const errorMessage = error.message || String(error);
55
+ const errorCode = error.code;
56
+
57
+ // Handle fetch/network errors
58
+ if (errorCode === "UND_ERR_CONNECT_TIMEOUT" ||
59
+ errorCode === "ETIMEDOUT" ||
60
+ errorMessage.includes("Connect Timeout") ||
61
+ errorMessage.includes("fetch failed")) {
62
+ logger(`Uncaught network timeout error (non-fatal): ${errorMessage}`, "warn");
63
+ return; // Don't crash
64
+ }
65
+
66
+ // For other uncaught exceptions, log but try to continue
67
+ logger(`Uncaught exception (attempting to continue): ${errorMessage}`, "error");
68
+ // Note: We don't exit here to allow bot to continue running
69
+ // In production, you might want to restart the process instead
70
+ } catch (e) {
71
+ // Fallback if logger fails
72
+ console.error("[FCA-ERROR] Uncaught exception:", error);
73
+ }
74
+ });
75
+ }
76
+
10
77
  function login(loginData, options, callback) {
11
78
  if (getType(options) === "Function" || getType(options) === "AsyncFunction") {
12
79
  callback = options;
@@ -465,14 +465,40 @@ async function hydrateJarFromDB(userID) {
465
465
  }
466
466
  }
467
467
 
468
- async function tryAutoLoginIfNeeded(currentHtml, currentCookies, globalOptions, ctxRef) {
468
+ async function tryAutoLoginIfNeeded(currentHtml, currentCookies, globalOptions, ctxRef, hadAppStateInput = false) {
469
469
  const getUID = cs =>
470
470
  cs.find(c => c.key === "i_user")?.value ||
471
471
  cs.find(c => c.key === "c_user")?.value ||
472
472
  cs.find(c => c.name === "i_user")?.value ||
473
473
  cs.find(c => c.name === "c_user")?.value;
474
+ const htmlUID = body => {
475
+ const s = typeof body === "string" ? body : String(body ?? "");
476
+ return s.match(/"USER_ID"\s*:\s*"(\d+)"/)?.[1] || s.match(/\["CurrentUserInitialData",\[\],\{.*?"USER_ID":"(\d+)".*?\},\d+\]/)?.[1];
477
+ };
474
478
  let userID = getUID(currentCookies);
479
+ // Also try to extract userID from HTML if not found in cookies
480
+ if (!userID) {
481
+ userID = htmlUID(currentHtml);
482
+ }
475
483
  if (userID) return { html: currentHtml, cookies: currentCookies, userID };
484
+ // If appState/Cookie was provided and is not dead (not checkpointed), skip backup
485
+ if (hadAppStateInput) {
486
+ const isCheckpoint = currentHtml.includes("/checkpoint/block/?next");
487
+ if (!isCheckpoint) {
488
+ // AppState provided and not checkpointed, but userID not found
489
+ // This might be a temporary issue - try to refresh cookies from jar
490
+ try {
491
+ const refreshedCookies = await Promise.resolve(jar.getCookies("https://www.facebook.com"));
492
+ userID = getUID(refreshedCookies);
493
+ if (userID) {
494
+ return { html: currentHtml, cookies: refreshedCookies, userID };
495
+ }
496
+ } catch { }
497
+ // If still no userID, skip backup and throw error
498
+ throw new Error("Missing user cookie from provided appState");
499
+ }
500
+ // AppState is dead (checkpointed), proceed to backup/email login
501
+ }
476
502
  const hydrated = await hydrateJarFromDB(null);
477
503
  if (hydrated) {
478
504
  logger("AppState backup live — proceeding to login", "info");
@@ -524,6 +550,26 @@ function makeLogin(j, email, password, globalOptions) {
524
550
  function loginHelper(appState, Cookie, email, password, globalOptions, callback) {
525
551
  try {
526
552
  const domain = ".facebook.com";
553
+ // Helper to extract userID from appState input
554
+ const extractUIDFromAppState = (appStateInput) => {
555
+ if (!appStateInput) return null;
556
+ let parsed = appStateInput;
557
+ if (typeof appStateInput === "string") {
558
+ try {
559
+ parsed = JSON.parse(appStateInput);
560
+ } catch {
561
+ return null;
562
+ }
563
+ }
564
+ if (Array.isArray(parsed)) {
565
+ const cUser = parsed.find(c => (c.key === "c_user" || c.name === "c_user"));
566
+ if (cUser) return cUser.value;
567
+ const iUser = parsed.find(c => (c.key === "i_user" || c.name === "i_user"));
568
+ if (iUser) return iUser.value;
569
+ }
570
+ return null;
571
+ };
572
+ let userIDFromAppState = extractUIDFromAppState(appState);
527
573
  try {
528
574
  if (appState) {
529
575
  if (typeof appState === "string") {
@@ -673,13 +719,27 @@ function loginHelper(appState, Cookie, email, password, globalOptions, callback)
673
719
  const processed = (await ctx.bypassAutomation(res, jar)) || res;
674
720
  let html = processed && processed.data ? processed.data : "";
675
721
  let cookies = await Promise.resolve(jar.getCookies("https://www.facebook.com"));
676
- let userID =
677
- cookies.find(c => c.key === "i_user")?.value ||
678
- cookies.find(c => c.key === "c_user")?.value ||
679
- cookies.find(c => c.name === "i_user")?.value ||
680
- cookies.find(c => c.name === "c_user")?.value;
722
+ const getUIDFromCookies = cs =>
723
+ cs.find(c => c.key === "i_user")?.value ||
724
+ cs.find(c => c.key === "c_user")?.value ||
725
+ cs.find(c => c.name === "i_user")?.value ||
726
+ cs.find(c => c.name === "c_user")?.value;
727
+ const getUIDFromHTML = body => {
728
+ const s = typeof body === "string" ? body : String(body ?? "");
729
+ return s.match(/"USER_ID"\s*:\s*"(\d+)"/)?.[1] || s.match(/\["CurrentUserInitialData",\[\],\{.*?"USER_ID":"(\d+)".*?\},\d+\]/)?.[1];
730
+ };
731
+ let userID = getUIDFromCookies(cookies);
732
+ // Also try to extract userID from HTML if not found in cookies
733
+ if (!userID) {
734
+ userID = getUIDFromHTML(html);
735
+ }
736
+ // If still not found and appState was provided, use userID from appState input as fallback
737
+ if (!userID && userIDFromAppState) {
738
+ userID = userIDFromAppState;
739
+ }
681
740
  if (!userID) {
682
- const retried = await tryAutoLoginIfNeeded(html, cookies, globalOptions, ctx);
741
+ // Pass hadAppStateInput=true if appState/Cookie was originally provided
742
+ const retried = await tryAutoLoginIfNeeded(html, cookies, globalOptions, ctx, !!(appState || Cookie));
683
743
  html = retried.html;
684
744
  cookies = retried.cookies;
685
745
  userID = retried.userID;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dongdev/fca-unofficial",
3
- "version": "3.0.8",
3
+ "version": "3.0.10",
4
4
  "description": "Unofficial Facebook Chat API for Node.js - Interact with Facebook Messenger programmatically",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",