@dongdev/fca-unofficial 3.0.17 → 3.0.20

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
@@ -170,3 +170,9 @@ Too lazy to write changelog, sorry! (will write changelog in the next release, t
170
170
 
171
171
  ## v3.0.15 - 2025-12-12
172
172
  - Hotfix / auto bump
173
+
174
+ ## v3.0.17 - 2025-12-16
175
+ - Hotfix / auto bump
176
+
177
+ ## v3.0.19 - 2025-12-31
178
+ - Hotfix / auto bump
package/module/login.js CHANGED
@@ -20,6 +20,11 @@ if (!global.fca._errorHandlersInstalled) {
20
20
  const errorCode = reason.code || reason.cause?.code;
21
21
  const errorMessage = reason.message || String(reason);
22
22
 
23
+ // Suppress Sequelize instance errors (handled gracefully in getBackupModel)
24
+ if (errorMessage.includes("No Sequelize instance passed")) {
25
+ return; // Silently ignore - already handled
26
+ }
27
+
23
28
  // Handle fetch timeout errors gracefully
24
29
  if (errorCode === "UND_ERR_CONNECT_TIMEOUT" ||
25
30
  errorCode === "ETIMEDOUT" ||
@@ -54,6 +59,11 @@ if (!global.fca._errorHandlersInstalled) {
54
59
  const errorMessage = error.message || String(error);
55
60
  const errorCode = error.code;
56
61
 
62
+ // Suppress Sequelize instance errors (handled gracefully in getBackupModel)
63
+ if (errorMessage.includes("No Sequelize instance passed")) {
64
+ return; // Silently ignore - already handled
65
+ }
66
+
57
67
  // Handle fetch/network errors
58
68
  if (errorCode === "UND_ERR_CONNECT_TIMEOUT" ||
59
69
  errorCode === "ETIMEDOUT" ||
@@ -182,28 +182,45 @@ function cookieHeaderFromJar(j) {
182
182
  let uniqueIndexEnsured = false;
183
183
 
184
184
  function getBackupModel() {
185
- if (!models || !models.sequelize || !models.Sequelize) return null;
186
- const sequelize = models.sequelize;
187
- const { DataTypes } = models.Sequelize;
188
- if (sequelize.models && sequelize.models.AppStateBackup) return sequelize.models.AppStateBackup;
189
- const dialect = typeof sequelize.getDialect === "function" ? sequelize.getDialect() : "sqlite";
190
- const LongText = (dialect === "mysql" || dialect === "mariadb") ? DataTypes.TEXT("long") : DataTypes.TEXT;
191
- const AppStateBackup = sequelize.define(
192
- "AppStateBackup",
193
- {
194
- id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true },
195
- userID: { type: DataTypes.STRING, allowNull: false },
196
- type: { type: DataTypes.STRING, allowNull: false },
197
- data: { type: LongText }
198
- },
199
- { tableName: "app_state_backups", timestamps: true, indexes: [{ unique: true, fields: ["userID", "type"] }] }
200
- );
201
- return AppStateBackup;
185
+ try {
186
+ if (!models || !models.sequelize || !models.Sequelize) return null;
187
+ const sequelize = models.sequelize;
188
+
189
+ // Validate that sequelize is a proper Sequelize instance
190
+ if (!sequelize || typeof sequelize.define !== "function") return null;
191
+
192
+ const { DataTypes } = models.Sequelize;
193
+ if (sequelize.models && sequelize.models.AppStateBackup) return sequelize.models.AppStateBackup;
194
+ const dialect = typeof sequelize.getDialect === "function" ? sequelize.getDialect() : "sqlite";
195
+ const LongText = (dialect === "mysql" || dialect === "mariadb") ? DataTypes.TEXT("long") : DataTypes.TEXT;
196
+
197
+ try {
198
+ const AppStateBackup = sequelize.define(
199
+ "AppStateBackup",
200
+ {
201
+ id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true },
202
+ userID: { type: DataTypes.STRING, allowNull: false },
203
+ type: { type: DataTypes.STRING, allowNull: false },
204
+ data: { type: LongText }
205
+ },
206
+ { tableName: "app_state_backups", timestamps: true, indexes: [{ unique: true, fields: ["userID", "type"] }] }
207
+ );
208
+ return AppStateBackup;
209
+ } catch (defineError) {
210
+ // If define fails, log and return null
211
+ logger(`Failed to define AppStateBackup model: ${defineError && defineError.message ? defineError.message : String(defineError)}`, "warn");
212
+ return null;
213
+ }
214
+ } catch (e) {
215
+ // Silently handle any errors in getBackupModel
216
+ return null;
217
+ }
202
218
  }
203
219
 
204
220
  async function ensureUniqueIndex(sequelize) {
205
- if (uniqueIndexEnsured) return;
221
+ if (uniqueIndexEnsured || !sequelize) return;
206
222
  try {
223
+ if (typeof sequelize.getQueryInterface !== "function") return;
207
224
  await sequelize.getQueryInterface().addIndex("app_state_backups", ["userID", "type"], { unique: true, name: "app_state_user_type_unique" });
208
225
  } catch { }
209
226
  uniqueIndexEnsured = true;
@@ -225,6 +242,7 @@ async function backupAppStateSQL(j, userID) {
225
242
  try {
226
243
  const Model = getBackupModel();
227
244
  if (!Model) return;
245
+ if (!models || !models.sequelize) return;
228
246
  await Model.sync();
229
247
  await ensureUniqueIndex(models.sequelize);
230
248
  const appJson = getAppState(j);
@@ -289,26 +307,101 @@ async function setJarCookies(j, appstate) {
289
307
  const cookiePath = c.path || "/";
290
308
  const dom = cookieDomain.replace(/^\./, "");
291
309
 
292
- // Format expires if provided
310
+ // Handle expirationDate (can be in seconds or milliseconds)
293
311
  let expiresStr = "";
294
- if (c.expires) {
312
+ if (c.expirationDate !== undefined) {
313
+ let expiresDate;
314
+ if (typeof c.expirationDate === "number") {
315
+ // If expirationDate is less than a year from now in seconds, treat as seconds
316
+ // Otherwise treat as milliseconds
317
+ const now = Date.now();
318
+ const oneYearInMs = 365 * 24 * 60 * 60 * 1000;
319
+ if (c.expirationDate < (now + oneYearInMs) / 1000) {
320
+ expiresDate = new Date(c.expirationDate * 1000);
321
+ } else {
322
+ expiresDate = new Date(c.expirationDate);
323
+ }
324
+ } else {
325
+ expiresDate = new Date(c.expirationDate);
326
+ }
327
+ expiresStr = `; expires=${expiresDate.toUTCString()}`;
328
+ } else if (c.expires) {
295
329
  const expiresDate = typeof c.expires === "number" ? new Date(c.expires) : new Date(c.expires);
296
330
  expiresStr = `; expires=${expiresDate.toUTCString()}`;
297
331
  }
298
332
 
299
- // Build cookie string
300
- const str = `${cookieName}=${cookieValue}${expiresStr}; Domain=${cookieDomain}; Path=${cookiePath};`;
333
+ // Helper function to build cookie string
334
+ const buildCookieString = (domainOverride = null) => {
335
+ const domain = domainOverride || cookieDomain;
336
+ let cookieParts = [`${cookieName}=${cookieValue}${expiresStr}`];
337
+ cookieParts.push(`Domain=${domain}`);
338
+ cookieParts.push(`Path=${cookiePath}`);
301
339
 
302
- // Set cookie for both http and https, with and without www
303
- const base1 = `http://${dom}${cookiePath}`;
304
- const base2 = `https://${dom}${cookiePath}`;
305
- const base3 = `http://www.${dom}${cookiePath}`;
306
- const base4 = `https://www.${dom}${cookiePath}`;
340
+ // Add Secure flag if secure is true
341
+ if (c.secure === true) {
342
+ cookieParts.push("Secure");
343
+ }
307
344
 
308
- tasks.push(j.setCookie(str, base1).catch(() => { }));
309
- tasks.push(j.setCookie(str, base2).catch(() => { }));
310
- tasks.push(j.setCookie(str, base3).catch(() => { }));
311
- tasks.push(j.setCookie(str, base4).catch(() => { }));
345
+ // Add HttpOnly flag if httpOnly is true
346
+ if (c.httpOnly === true) {
347
+ cookieParts.push("HttpOnly");
348
+ }
349
+
350
+ // Add SameSite attribute if provided
351
+ if (c.sameSite) {
352
+ const sameSiteValue = String(c.sameSite).toLowerCase();
353
+ if (["strict", "lax", "none"].includes(sameSiteValue)) {
354
+ cookieParts.push(`SameSite=${sameSiteValue.charAt(0).toUpperCase() + sameSiteValue.slice(1)}`);
355
+ }
356
+ }
357
+
358
+ return cookieParts.join("; ");
359
+ };
360
+
361
+ // Determine target URLs and cookie strings based on domain
362
+ const cookieConfigs = [];
363
+
364
+ // For .facebook.com domain, set for both facebook.com and messenger.com
365
+ if (cookieDomain === ".facebook.com" || cookieDomain === "facebook.com") {
366
+ // Set for facebook.com with .facebook.com domain
367
+ cookieConfigs.push({ url: `http://${dom}${cookiePath}`, cookieStr: buildCookieString() });
368
+ cookieConfigs.push({ url: `https://${dom}${cookiePath}`, cookieStr: buildCookieString() });
369
+ cookieConfigs.push({ url: `http://www.${dom}${cookiePath}`, cookieStr: buildCookieString() });
370
+ cookieConfigs.push({ url: `https://www.${dom}${cookiePath}`, cookieStr: buildCookieString() });
371
+
372
+ // Set for messenger.com with .messenger.com domain (or without domain for host-only)
373
+ // Use .messenger.com domain to allow cross-subdomain sharing
374
+ cookieConfigs.push({ url: `http://messenger.com${cookiePath}`, cookieStr: buildCookieString(".messenger.com") });
375
+ cookieConfigs.push({ url: `https://messenger.com${cookiePath}`, cookieStr: buildCookieString(".messenger.com") });
376
+ cookieConfigs.push({ url: `http://www.messenger.com${cookiePath}`, cookieStr: buildCookieString(".messenger.com") });
377
+ cookieConfigs.push({ url: `https://www.messenger.com${cookiePath}`, cookieStr: buildCookieString(".messenger.com") });
378
+ } else if (cookieDomain === ".messenger.com" || cookieDomain === "messenger.com") {
379
+ // Set for messenger.com only
380
+ const messengerDom = cookieDomain.replace(/^\./, "");
381
+ cookieConfigs.push({ url: `http://${messengerDom}${cookiePath}`, cookieStr: buildCookieString() });
382
+ cookieConfigs.push({ url: `https://${messengerDom}${cookiePath}`, cookieStr: buildCookieString() });
383
+ cookieConfigs.push({ url: `http://www.${messengerDom}${cookiePath}`, cookieStr: buildCookieString() });
384
+ cookieConfigs.push({ url: `https://www.${messengerDom}${cookiePath}`, cookieStr: buildCookieString() });
385
+ } else {
386
+ // For other domains, set normally
387
+ cookieConfigs.push({ url: `http://${dom}${cookiePath}`, cookieStr: buildCookieString() });
388
+ cookieConfigs.push({ url: `https://${dom}${cookiePath}`, cookieStr: buildCookieString() });
389
+ cookieConfigs.push({ url: `http://www.${dom}${cookiePath}`, cookieStr: buildCookieString() });
390
+ cookieConfigs.push({ url: `https://www.${dom}${cookiePath}`, cookieStr: buildCookieString() });
391
+ }
392
+
393
+ // Set cookie for all target URLs, silently catch domain errors
394
+ for (const config of cookieConfigs) {
395
+ tasks.push(j.setCookie(config.cookieStr, config.url).catch((err) => {
396
+ // Silently ignore domain mismatch errors for cross-domain cookies
397
+ // These are expected when setting cookies across domains
398
+ if (err && err.message && err.message.includes("Cookie not in this host's domain")) {
399
+ return; // Expected error, ignore
400
+ }
401
+ // Log other errors but don't throw
402
+ return;
403
+ }));
404
+ }
312
405
  }
313
406
  await Promise.all(tasks);
314
407
  }
@@ -835,8 +928,12 @@ function loginHelper(appState, Cookie, email, password, globalOptions, callback)
835
928
  }
836
929
  })
837
930
  .catch(function (error) {
838
- console.error(error);
839
- console.error("Database connection failed:", error && error.message ? error.message : String(error));
931
+ // Silently handle database errors - they're not critical for login
932
+ const errorMsg = error && error.message ? error.message : String(error);
933
+ if (!errorMsg.includes("No Sequelize instance passed")) {
934
+ // Only log non-Sequelize instance errors
935
+ logger(`Database connection failed: ${errorMsg}`, "warn");
936
+ }
840
937
  });
841
938
  logger("FCA fix/update by DongDev (Donix-VN)", "info");
842
939
  const ctxMain = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dongdev/fca-unofficial",
3
- "version": "3.0.17",
3
+ "version": "3.0.20",
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",
@@ -61,7 +61,6 @@ module.exports = function (defaultFuncs, api, ctx, opts) {
61
61
  }
62
62
  defaultFuncs.post = postSafe;
63
63
  ctx._postGuarded = true;
64
- logger("postSafe guard installed for defaultFuncs.post", "info");
65
64
  return postSafe;
66
65
  }
67
66