@dongdev/fca-unofficial 3.0.31 → 4.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.
Files changed (127) hide show
  1. package/LICENSE +191 -0
  2. package/README.md +224 -406
  3. package/dist/index.d.mts +1241 -0
  4. package/dist/index.d.ts +1241 -0
  5. package/dist/index.js +27749 -0
  6. package/dist/index.mjs +27713 -0
  7. package/docs/ARCHITECTURE.md +467 -0
  8. package/docs/DOCS.md +686 -0
  9. package/fca-config.example.json +33 -0
  10. package/package.json +32 -22
  11. package/test/fca.test.cjs +533 -0
  12. package/CHANGELOG.md +0 -296
  13. package/DOCS.md +0 -2712
  14. package/func/checkUpdate.js +0 -222
  15. package/func/logAdapter.js +0 -33
  16. package/func/logger.js +0 -48
  17. package/index.d.ts +0 -751
  18. package/index.js +0 -8
  19. package/module/config.js +0 -40
  20. package/module/login.js +0 -133
  21. package/module/loginHelper.js +0 -1296
  22. package/module/options.js +0 -44
  23. package/src/api/action/addExternalModule.js +0 -25
  24. package/src/api/action/changeAvatar.js +0 -137
  25. package/src/api/action/changeBio.js +0 -75
  26. package/src/api/action/enableAutoSaveAppState.js +0 -73
  27. package/src/api/action/getCurrentUserID.js +0 -7
  28. package/src/api/action/handleFriendRequest.js +0 -57
  29. package/src/api/action/logout.js +0 -76
  30. package/src/api/action/refreshFb_dtsg.js +0 -48
  31. package/src/api/action/setPostReaction.js +0 -106
  32. package/src/api/action/unfriend.js +0 -54
  33. package/src/api/http/httpGet.js +0 -46
  34. package/src/api/http/httpPost.js +0 -52
  35. package/src/api/http/postFormData.js +0 -47
  36. package/src/api/messaging/addUserToGroup.js +0 -68
  37. package/src/api/messaging/changeAdminStatus.js +0 -126
  38. package/src/api/messaging/changeArchivedStatus.js +0 -55
  39. package/src/api/messaging/changeBlockedStatus.js +0 -48
  40. package/src/api/messaging/changeGroupImage.js +0 -91
  41. package/src/api/messaging/changeNickname.js +0 -70
  42. package/src/api/messaging/changeThreadColor.js +0 -79
  43. package/src/api/messaging/changeThreadEmoji.js +0 -111
  44. package/src/api/messaging/createNewGroup.js +0 -88
  45. package/src/api/messaging/createPoll.js +0 -46
  46. package/src/api/messaging/createThemeAI.js +0 -98
  47. package/src/api/messaging/deleteMessage.js +0 -136
  48. package/src/api/messaging/deleteThread.js +0 -56
  49. package/src/api/messaging/editMessage.js +0 -68
  50. package/src/api/messaging/forwardAttachment.js +0 -57
  51. package/src/api/messaging/getEmojiUrl.js +0 -29
  52. package/src/api/messaging/getFriendsList.js +0 -82
  53. package/src/api/messaging/getMessage.js +0 -829
  54. package/src/api/messaging/getThemePictures.js +0 -62
  55. package/src/api/messaging/handleMessageRequest.js +0 -65
  56. package/src/api/messaging/markAsDelivered.js +0 -57
  57. package/src/api/messaging/markAsRead.js +0 -88
  58. package/src/api/messaging/markAsReadAll.js +0 -49
  59. package/src/api/messaging/markAsSeen.js +0 -61
  60. package/src/api/messaging/muteThread.js +0 -50
  61. package/src/api/messaging/removeUserFromGroup.js +0 -62
  62. package/src/api/messaging/resolvePhotoUrl.js +0 -43
  63. package/src/api/messaging/scheduler.js +0 -264
  64. package/src/api/messaging/searchForThread.js +0 -53
  65. package/src/api/messaging/sendMessage.js +0 -270
  66. package/src/api/messaging/sendTypingIndicator.js +0 -74
  67. package/src/api/messaging/setMessageReaction.js +0 -90
  68. package/src/api/messaging/setTitle.js +0 -124
  69. package/src/api/messaging/shareContact.js +0 -49
  70. package/src/api/messaging/threadColors.js +0 -128
  71. package/src/api/messaging/unsendMessage.js +0 -81
  72. package/src/api/messaging/uploadAttachment.js +0 -492
  73. package/src/api/socket/core/connectMqtt.js +0 -258
  74. package/src/api/socket/core/emitAuth.js +0 -103
  75. package/src/api/socket/core/getSeqID.js +0 -320
  76. package/src/api/socket/core/getTaskResponseData.js +0 -25
  77. package/src/api/socket/core/parseDelta.js +0 -377
  78. package/src/api/socket/detail/buildStream.js +0 -215
  79. package/src/api/socket/detail/constants.js +0 -28
  80. package/src/api/socket/listenMqtt.js +0 -377
  81. package/src/api/socket/middleware/index.js +0 -216
  82. package/src/api/threads/getThreadHistory.js +0 -664
  83. package/src/api/threads/getThreadInfo.js +0 -296
  84. package/src/api/threads/getThreadList.js +0 -293
  85. package/src/api/threads/getThreadPictures.js +0 -78
  86. package/src/api/users/getUserID.js +0 -65
  87. package/src/api/users/getUserInfo.js +0 -402
  88. package/src/api/users/getUserInfoV2.js +0 -134
  89. package/src/core/sendReqMqtt.js +0 -96
  90. package/src/database/helpers.js +0 -53
  91. package/src/database/models/index.js +0 -88
  92. package/src/database/models/thread.js +0 -50
  93. package/src/database/models/user.js +0 -46
  94. package/src/database/threadData.js +0 -94
  95. package/src/database/userData.js +0 -98
  96. package/src/remote/remoteClient.js +0 -123
  97. package/src/utils/broadcast.js +0 -51
  98. package/src/utils/client.js +0 -10
  99. package/src/utils/constants.js +0 -23
  100. package/src/utils/cookies.js +0 -68
  101. package/src/utils/format/attachment.js +0 -357
  102. package/src/utils/format/cookie.js +0 -9
  103. package/src/utils/format/date.js +0 -50
  104. package/src/utils/format/decode.js +0 -44
  105. package/src/utils/format/delta.js +0 -194
  106. package/src/utils/format/ids.js +0 -64
  107. package/src/utils/format/index.js +0 -64
  108. package/src/utils/format/message.js +0 -88
  109. package/src/utils/format/presence.js +0 -132
  110. package/src/utils/format/readTyp.js +0 -44
  111. package/src/utils/format/thread.js +0 -42
  112. package/src/utils/format/utils.js +0 -141
  113. package/src/utils/headers.js +0 -115
  114. package/src/utils/loginParser/autoLogin.js +0 -125
  115. package/src/utils/loginParser/helpers.js +0 -43
  116. package/src/utils/loginParser/index.js +0 -10
  117. package/src/utils/loginParser/parseAndCheckLogin.js +0 -220
  118. package/src/utils/loginParser/textUtils.js +0 -28
  119. package/src/utils/request/client.js +0 -26
  120. package/src/utils/request/config.js +0 -23
  121. package/src/utils/request/defaults.js +0 -46
  122. package/src/utils/request/helpers.js +0 -46
  123. package/src/utils/request/index.js +0 -17
  124. package/src/utils/request/methods.js +0 -163
  125. package/src/utils/request/proxy.js +0 -21
  126. package/src/utils/request/retry.js +0 -77
  127. package/src/utils/request/sanitize.js +0 -49
@@ -0,0 +1,33 @@
1
+ {
2
+ "autoUpdate": true,
3
+ "checkUpdate": {
4
+ "enabled": true,
5
+ "install": false,
6
+ "notifyIfCurrent": false,
7
+ "packageName": "@dongdev/fca-unofficial",
8
+ "registryUrl": "https://registry.npmjs.org",
9
+ "timeoutMs": 10000
10
+ },
11
+ "mqtt": {
12
+ "enabled": true,
13
+ "reconnectInterval": 3600
14
+ },
15
+ "autoLogin": true,
16
+ "apiServer": "https://donixdev.com",
17
+ "apiKey": "",
18
+ "credentials": {
19
+ "email": "",
20
+ "password": "",
21
+ "twofactor": ""
22
+ },
23
+ "antiGetInfo": {
24
+ "AntiGetThreadInfo": false,
25
+ "AntiGetUserInfo": false
26
+ },
27
+ "remoteControl": {
28
+ "enabled": false,
29
+ "url": "",
30
+ "token": "",
31
+ "autoReconnect": true
32
+ }
33
+ }
package/package.json CHANGED
@@ -1,30 +1,32 @@
1
1
  {
2
2
  "name": "@dongdev/fca-unofficial",
3
- "version": "3.0.31",
3
+ "version": "4.0.0",
4
4
  "description": "Unofficial Facebook Chat API for Node.js - Interact with Facebook Messenger programmatically",
5
- "main": "index.js",
6
- "types": "index.d.ts",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
7
  "exports": {
8
8
  ".": {
9
- "require": "./index.js",
10
- "default": "./index.js",
11
- "types": "./index.d.ts"
9
+ "types": "./dist/index.d.ts",
10
+ "require": "./dist/index.js",
11
+ "import": "./dist/index.mjs",
12
+ "default": "./dist/index.js"
12
13
  }
13
14
  },
14
15
  "files": [
15
- "index.js",
16
- "index.d.ts",
17
- "module/",
18
- "func/",
19
- "src/",
20
- "DOCS.md",
16
+ "dist/",
17
+ "docs/",
21
18
  "README.md",
19
+ "test/",
22
20
  "LICENSE",
23
- "CHANGELOG.md"
21
+ "fca-config.example.json"
24
22
  ],
25
23
  "scripts": {
26
- "test": "mocha",
27
- "lint": "eslint ."
24
+ "test": "node ./test/fca.test.cjs",
25
+ "test:build": "npm run build && node ./test/fca.test.cjs",
26
+ "lint": "eslint .",
27
+ "typecheck": "tsc -p tsconfig.typecheck.json --noEmit",
28
+ "build": "tsup src/index.ts --format cjs,esm --dts --clean --out-dir dist --external gradient-string",
29
+ "build:types": "tsc -p tsconfig.build.json --emitDeclarationOnly"
28
30
  },
29
31
  "repository": {
30
32
  "type": "git",
@@ -46,8 +48,8 @@
46
48
  "fca"
47
49
  ],
48
50
  "author": {
49
- "name": "DongDev",
50
- "url": "https://www.facebook.com/mdong.dev"
51
+ "name": "Donix",
52
+ "url": "https://www.facebook.com/minhdong.dev"
51
53
  },
52
54
  "contributors": [
53
55
  {
@@ -55,32 +57,40 @@
55
57
  "url": "https://github.com/dongp06"
56
58
  }
57
59
  ],
58
- "license": "MIT",
60
+ "license": "Apache-2.0",
59
61
  "bugs": {
60
62
  "url": "https://github.com/dongp06/fca-unofficial/issues"
61
63
  },
62
64
  "homepage": "https://github.com/dongp06/fca-unofficial#readme",
63
65
  "engines": {
64
- "node": ">=12.0.0"
66
+ "node": ">=14.0.0"
65
67
  },
66
68
  "dependencies": {
67
69
  "axios": "^1.13.5",
68
70
  "axios-cookiejar-support": "^5.0.5",
69
71
  "bluebird": "^3.7.2",
70
- "chalk": "^4.1.2",
72
+ "boxen": "^8.0.1",
71
73
  "cheerio": "^1.0.0-rc.10",
74
+ "cli-progress": "^3.12.0",
72
75
  "duplexify": "^4.1.3",
73
- "gradient-string": "^2.0.2",
76
+ "gradient-string": "^3.0.0",
74
77
  "https-proxy-agent": "^4.0.0",
75
78
  "mqtt": "^4.3.8",
79
+ "ora": "^9.3.0",
80
+ "picocolors": "^1.1.1",
76
81
  "sequelize": "^6.37.6",
77
82
  "sqlite3": "^5.1.7",
78
83
  "totp-generator": "^1.0.0",
79
84
  "ws": "^8.18.1"
80
85
  },
81
86
  "devDependencies": {
87
+ "@types/cli-progress": "^3.11.6",
88
+ "@types/duplexify": "^3.6.5",
89
+ "@types/node": "^24.6.1",
90
+ "@types/ws": "^8.18.1",
82
91
  "eslint": "^8.50.0",
83
- "mocha": "^10.2.0"
92
+ "tsup": "^8.5.0",
93
+ "typescript": "^5.9.3"
84
94
  },
85
95
  "publishConfig": {
86
96
  "access": "public",
@@ -0,0 +1,533 @@
1
+ "use strict";
2
+
3
+ const assert = require("assert");
4
+ const fs = require("fs");
5
+ const path = require("path");
6
+
7
+ const fca = require("../dist/index.js");
8
+
9
+ const tests = [];
10
+ const COOKIE_ATTRIBUTE_KEYS = new Set([
11
+ "domain",
12
+ "path",
13
+ "expires",
14
+ "max-age",
15
+ "secure",
16
+ "httponly",
17
+ "samesite",
18
+ "priority",
19
+ "partitioned"
20
+ ]);
21
+
22
+ function test(name, fn) {
23
+ tests.push({ name, fn });
24
+ }
25
+
26
+ function toCookiePair(name, value) {
27
+ const cookieName = String(name || "").trim();
28
+ if (!cookieName) {
29
+ return null;
30
+ }
31
+
32
+ const cookieValue = value === undefined || value === null ? "" : String(value).trim();
33
+ return `${cookieName}=${cookieValue}`;
34
+ }
35
+
36
+ function dedupeCookiePairs(pairs) {
37
+ const latestByName = new Map();
38
+
39
+ for (const pair of pairs) {
40
+ const normalized = String(pair || "").trim();
41
+ const eqIndex = normalized.indexOf("=");
42
+ if (eqIndex <= 0) {
43
+ continue;
44
+ }
45
+
46
+ const name = normalized.slice(0, eqIndex).trim();
47
+ if (!name) {
48
+ continue;
49
+ }
50
+
51
+ latestByName.set(name, `${name}=${normalized.slice(eqIndex + 1).trim()}`);
52
+ }
53
+
54
+ return [...latestByName.values()];
55
+ }
56
+
57
+ function parseCookieHeaderString(raw) {
58
+ let header = String(raw || "").trim();
59
+ if (!header) {
60
+ return [];
61
+ }
62
+
63
+ if (/^cookie\s*:/i.test(header)) {
64
+ header = header.replace(/^cookie\s*:/i, "").trim();
65
+ }
66
+
67
+ const pairs = [];
68
+ for (const segment of header.split(/[;\r\n]+/)) {
69
+ const token = segment.trim();
70
+ const eqIndex = token.indexOf("=");
71
+ if (eqIndex <= 0) {
72
+ continue;
73
+ }
74
+
75
+ const name = token.slice(0, eqIndex).trim();
76
+ if (!name || COOKIE_ATTRIBUTE_KEYS.has(name.toLowerCase())) {
77
+ continue;
78
+ }
79
+
80
+ const value = token.slice(eqIndex + 1).trim().replace(/^"(.*)"$/, "$1");
81
+ const pair = toCookiePair(name, value);
82
+ if (pair) {
83
+ pairs.push(pair);
84
+ }
85
+ }
86
+
87
+ return dedupeCookiePairs(pairs);
88
+ }
89
+
90
+ function parseNetscapeCookieText(raw) {
91
+ const pairs = [];
92
+ const lines = String(raw || "").split(/\r?\n/);
93
+
94
+ for (const line of lines) {
95
+ const trimmed = line.trim();
96
+ if (!trimmed) {
97
+ continue;
98
+ }
99
+
100
+ const normalized = trimmed.replace(/^#HttpOnly_/, "");
101
+ if (normalized.startsWith("#")) {
102
+ continue;
103
+ }
104
+
105
+ const columns = normalized.split("\t");
106
+ if (columns.length < 7) {
107
+ continue;
108
+ }
109
+
110
+ const name = columns[5];
111
+ const value = columns.slice(6).join("\t");
112
+ const pair = toCookiePair(name, value);
113
+ if (pair) {
114
+ pairs.push(pair);
115
+ }
116
+ }
117
+
118
+ return dedupeCookiePairs(pairs);
119
+ }
120
+
121
+ function normalizeCookiePayload(payload) {
122
+ if (!payload) {
123
+ return null;
124
+ }
125
+
126
+ if (typeof payload === "string") {
127
+ const netscapePairs = parseNetscapeCookieText(payload);
128
+ if (netscapePairs.length) {
129
+ return netscapePairs.join("; ");
130
+ }
131
+
132
+ const headerPairs = parseCookieHeaderString(payload);
133
+ if (headerPairs.length) {
134
+ return headerPairs.join("; ");
135
+ }
136
+
137
+ return null;
138
+ }
139
+
140
+ if (Array.isArray(payload)) {
141
+ const pairs = [];
142
+
143
+ for (const entry of payload) {
144
+ if (typeof entry === "string") {
145
+ const stringPairs = parseCookieHeaderString(entry);
146
+ if (stringPairs.length) {
147
+ pairs.push(...stringPairs);
148
+ continue;
149
+ }
150
+
151
+ const eqIndex = entry.indexOf("=");
152
+ if (eqIndex > 0) {
153
+ const pair = toCookiePair(entry.slice(0, eqIndex), entry.slice(eqIndex + 1));
154
+ if (pair) {
155
+ pairs.push(pair);
156
+ }
157
+ }
158
+ continue;
159
+ }
160
+
161
+ if (entry && typeof entry === "object") {
162
+ const pair = toCookiePair(entry.name || entry.key, entry.value);
163
+ if (pair) {
164
+ pairs.push(pair);
165
+ }
166
+ }
167
+ }
168
+
169
+ const deduped = dedupeCookiePairs(pairs);
170
+ return deduped.length ? deduped.join("; ") : null;
171
+ }
172
+
173
+ if (typeof payload === "object") {
174
+ if (payload.Cookie || payload.cookie || payload.cookies) {
175
+ return normalizeCookiePayload(payload.Cookie || payload.cookie || payload.cookies);
176
+ }
177
+
178
+ if ((payload.name || payload.key) && Object.prototype.hasOwnProperty.call(payload, "value")) {
179
+ return toCookiePair(payload.name || payload.key, payload.value);
180
+ }
181
+
182
+ const pairs = [];
183
+ for (const [name, value] of Object.entries(payload)) {
184
+ if (value === undefined || value === null || typeof value === "object") {
185
+ continue;
186
+ }
187
+
188
+ const pair = toCookiePair(name, value);
189
+ if (pair) {
190
+ pairs.push(pair);
191
+ }
192
+ }
193
+
194
+ const deduped = dedupeCookiePairs(pairs);
195
+ return deduped.length ? deduped.join("; ") : null;
196
+ }
197
+
198
+ return null;
199
+ }
200
+
201
+ function loadCookieCredentials(candidate, sourceLabel) {
202
+ if (!fs.existsSync(candidate)) {
203
+ return null;
204
+ }
205
+
206
+ const raw = fs.readFileSync(candidate, "utf8").trim();
207
+ if (!raw) {
208
+ return null;
209
+ }
210
+
211
+ let payload = raw;
212
+ if (candidate.endsWith(".json")) {
213
+ payload = JSON.parse(raw);
214
+ }
215
+
216
+ const cookieHeader = normalizeCookiePayload(payload);
217
+ if (!cookieHeader) {
218
+ return null;
219
+ }
220
+
221
+ return {
222
+ mode: "cookie",
223
+ source: sourceLabel,
224
+ credentials: {
225
+ Cookie: cookieHeader
226
+ }
227
+ };
228
+ }
229
+
230
+ function readJsonFileIfValid(candidate) {
231
+ if (!fs.existsSync(candidate)) {
232
+ return null;
233
+ }
234
+
235
+ const raw = fs.readFileSync(candidate, "utf8");
236
+ if (!raw.trim()) {
237
+ return null;
238
+ }
239
+
240
+ try {
241
+ return JSON.parse(raw);
242
+ } catch {
243
+ return null;
244
+ }
245
+ }
246
+
247
+ function loadIntegrationCredentials() {
248
+ const rootDir = process.cwd();
249
+ const cookieCandidates = [
250
+ path.join(rootDir, "cookie.txt"),
251
+ path.join(rootDir, "cookies.txt"),
252
+ path.join(rootDir, "cookie.json"),
253
+ path.join(rootDir, "cookies.json"),
254
+ path.join(rootDir, "test", "cookie.txt"),
255
+ path.join(rootDir, "test", "cookie.json")
256
+ ];
257
+
258
+ for (const candidate of cookieCandidates) {
259
+ const loaded = loadCookieCredentials(candidate, path.relative(rootDir, candidate) || candidate);
260
+ if (loaded) {
261
+ return loaded;
262
+ }
263
+ }
264
+
265
+ const appStateCandidates = [
266
+ path.join(rootDir, "appstate.json"),
267
+ path.join(rootDir, "appState.json"),
268
+ path.join(rootDir, "test", "appstate.json")
269
+ ];
270
+
271
+ for (const candidate of appStateCandidates) {
272
+ const parsedAppState = readJsonFileIfValid(candidate);
273
+ if (!parsedAppState) {
274
+ continue;
275
+ }
276
+
277
+ return {
278
+ mode: "appState",
279
+ source: path.relative(rootDir, candidate) || candidate,
280
+ credentials: {
281
+ appState: parsedAppState
282
+ }
283
+ };
284
+ }
285
+
286
+ const configPath = path.join(rootDir, "fca-config.json");
287
+ if (fs.existsSync(configPath)) {
288
+ const rawConfig = readJsonFileIfValid(configPath);
289
+ if (!rawConfig) {
290
+ return null;
291
+ }
292
+
293
+ const configCookiePath =
294
+ rawConfig.cookiePath ||
295
+ rawConfig.cookiesPath ||
296
+ rawConfig.cookieFile ||
297
+ rawConfig.cookiesFile ||
298
+ rawConfig.credentials?.cookiePath ||
299
+ rawConfig.credentials?.cookiesPath ||
300
+ rawConfig.credentials?.cookieFile ||
301
+ rawConfig.credentials?.cookiesFile;
302
+
303
+ if (configCookiePath) {
304
+ const resolvedCookiePath = path.resolve(rootDir, configCookiePath);
305
+ const loaded = loadCookieCredentials(
306
+ resolvedCookiePath,
307
+ path.relative(rootDir, resolvedCookiePath) || resolvedCookiePath
308
+ );
309
+ if (loaded) {
310
+ return loaded;
311
+ }
312
+ }
313
+
314
+ const configCookie =
315
+ rawConfig.Cookie ||
316
+ rawConfig.cookie ||
317
+ rawConfig.cookies ||
318
+ rawConfig.sessionCookie ||
319
+ rawConfig.credentials?.Cookie ||
320
+ rawConfig.credentials?.cookie ||
321
+ rawConfig.credentials?.cookies ||
322
+ rawConfig.credentials?.sessionCookie;
323
+
324
+ if (configCookie) {
325
+ const normalizedConfigCookie = normalizeCookiePayload(configCookie);
326
+ if (normalizedConfigCookie) {
327
+ return {
328
+ mode: "cookie",
329
+ source: "fca-config.json",
330
+ credentials: {
331
+ Cookie: normalizedConfigCookie
332
+ }
333
+ };
334
+ }
335
+ }
336
+
337
+ const fileAppStatePath =
338
+ rawConfig.appStatePath ||
339
+ rawConfig.appstatePath ||
340
+ rawConfig.appStateFile ||
341
+ rawConfig.appstateFile;
342
+
343
+ if (fileAppStatePath) {
344
+ const resolvedPath = path.resolve(rootDir, fileAppStatePath);
345
+ const parsedAppState = readJsonFileIfValid(resolvedPath);
346
+ if (parsedAppState) {
347
+ return {
348
+ mode: "appState",
349
+ source: path.relative(rootDir, resolvedPath) || resolvedPath,
350
+ credentials: {
351
+ appState: parsedAppState
352
+ }
353
+ };
354
+ }
355
+ }
356
+ }
357
+
358
+ return null;
359
+ }
360
+
361
+ test("public exports are available", () => {
362
+ assert.strictEqual(typeof fca.login, "function");
363
+ assert.strictEqual(typeof fca.createFcaClient, "function");
364
+ assert.strictEqual(typeof fca.attachClientFacade, "function");
365
+ assert.strictEqual(typeof fca.resolveConfig, "function");
366
+ assert.strictEqual(typeof fca.loadConfig, "function");
367
+ });
368
+
369
+ test("resolveConfig honors legacy autoUpdate alias", () => {
370
+ const resolved = fca.resolveConfig({
371
+ autoUpdate: false,
372
+ checkUpdate: {
373
+ notifyIfCurrent: true
374
+ }
375
+ });
376
+
377
+ assert.strictEqual(resolved.autoUpdate, false);
378
+ assert.strictEqual(resolved.checkUpdate.enabled, false);
379
+ assert.strictEqual(resolved.checkUpdate.notifyIfCurrent, true);
380
+ });
381
+
382
+ test("createFcaClient reuses grouped namespaces when they already exist", () => {
383
+ const send = () => "send-ok";
384
+ const getInfo = () => "thread-ok";
385
+ const listen = () => "listen-ok";
386
+
387
+ const api = {
388
+ messages: { send },
389
+ threads: { getInfo },
390
+ users: { getInfo: () => "user-ok" },
391
+ account: { getCurrentUserID: () => "123" },
392
+ realtime: { listen },
393
+ http: { get: () => "http-ok" },
394
+ scheduler: { scheduleMessage: () => "sched-ok" }
395
+ };
396
+
397
+ const client = fca.createFcaClient(api);
398
+
399
+ assert.strictEqual(client.messages.send, send);
400
+ assert.strictEqual(client.threads.getInfo, getInfo);
401
+ assert.strictEqual(client.realtime.listen, listen);
402
+ assert.strictEqual(client.scheduler.scheduleMessage(), "sched-ok");
403
+ });
404
+
405
+ test("attachClientFacade exposes grouped namespaces on legacy api object", () => {
406
+ const api = {
407
+ sendMessage() {
408
+ return "legacy-send";
409
+ },
410
+ getThreadInfo() {
411
+ return "legacy-thread";
412
+ },
413
+ getUserInfo() {
414
+ return "legacy-user";
415
+ },
416
+ getCurrentUserID() {
417
+ return "1000";
418
+ },
419
+ listenMqtt() {
420
+ return "legacy-listen";
421
+ },
422
+ stopListening() {
423
+ return "legacy-stop";
424
+ },
425
+ stopListeningAsync() {
426
+ return Promise.resolve("legacy-stop-async");
427
+ },
428
+ useMiddleware() {
429
+ return "legacy-use";
430
+ },
431
+ removeMiddleware() {
432
+ return "legacy-remove";
433
+ },
434
+ clearMiddleware() {
435
+ return "legacy-clear";
436
+ },
437
+ listMiddleware() {
438
+ return [];
439
+ },
440
+ setMiddlewareEnabled() {
441
+ return true;
442
+ },
443
+ httpGet() {
444
+ return "legacy-http-get";
445
+ },
446
+ httpPost() {
447
+ return "legacy-http-post";
448
+ },
449
+ postFormData() {
450
+ return "legacy-http-form";
451
+ }
452
+ };
453
+
454
+ const client = fca.attachClientFacade(api);
455
+
456
+ assert.strictEqual(api.client, client);
457
+ assert.strictEqual(typeof api.messages.send, "function");
458
+ assert.strictEqual(typeof api.threads.getInfo, "function");
459
+ assert.strictEqual(typeof api.users.getInfo, "function");
460
+ assert.strictEqual(typeof api.account.getCurrentUserID, "function");
461
+ assert.strictEqual(typeof api.realtime.listen, "function");
462
+ assert.strictEqual(typeof api.http.get, "function");
463
+ });
464
+
465
+ test("optional live login works when credentials are provided", async () => {
466
+ const integration = loadIntegrationCredentials();
467
+ if (!integration) {
468
+ return {
469
+ skipped: true,
470
+ reason: "add cookie.txt, cookies.txt, cookie.json, or cookiePath/Cookie in fca-config.json to enable real-cookie login"
471
+ };
472
+ }
473
+
474
+ let ctx;
475
+ try {
476
+ ctx = await fca.login(integration.credentials, {
477
+ online: false,
478
+ forceLogin: false,
479
+ autoMarkRead: false
480
+ });
481
+ } catch (error) {
482
+ return {
483
+ skipped: true,
484
+ reason: `live login failed from ${integration.source}: ${error && error.message ? error.message : String(error)}`
485
+ };
486
+ }
487
+
488
+ assert.ok(ctx);
489
+ assert.ok(ctx.api);
490
+ assert.strictEqual(typeof ctx.api.sendMessage, "function");
491
+ assert.strictEqual(typeof ctx.api.listenMqtt, "function");
492
+ assert.strictEqual(typeof ctx.api.client.messages.send, "function");
493
+ assert.ok(ctx.userID || ctx.fbid);
494
+
495
+ return {
496
+ mode: integration.mode,
497
+ source: integration.source,
498
+ userID: ctx.userID || ctx.fbid || null
499
+ };
500
+ });
501
+
502
+ async function main() {
503
+ let passed = 0;
504
+ let skipped = 0;
505
+
506
+ for (const { name, fn } of tests) {
507
+ try {
508
+ const result = await fn();
509
+ if (result && result.skipped) {
510
+ skipped += 1;
511
+ console.log(`SKIP ${name} - ${result.reason}`);
512
+ continue;
513
+ }
514
+
515
+ passed += 1;
516
+ if (result && result.userID) {
517
+ console.log(`PASS ${name} (${result.mode} from ${result.source}: ${result.userID})`);
518
+ } else {
519
+ console.log(`PASS ${name}`);
520
+ }
521
+ } catch (error) {
522
+ console.error(`FAIL ${name}`);
523
+ throw error;
524
+ }
525
+ }
526
+
527
+ console.log(`SUMMARY passed=${passed} skipped=${skipped} total=${tests.length}`);
528
+ }
529
+
530
+ main().catch((error) => {
531
+ console.error(error && error.stack ? error.stack : error);
532
+ process.exitCode = 1;
533
+ });