@lazyneoaz/testfca 1.0.1 → 1.0.4
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/package.json +5 -6
- package/prebuilt/linux-x64-gnu/messagix.so +0 -0
- package/scripts/build-go.mjs +8 -1
- package/scripts/download-prebuilt.mjs +6 -13
- package/scripts/postinstall.js +98 -0
- package/scripts/postinstall.mjs +17 -35
- package/src/e2ee/bridge.js +284 -229
- package/src/e2ee/index.js +5 -16
- package/src/utils/axios.js +3 -0
- package/src/utils/clients.js +8 -0
- package/src/utils/formatters.js +7 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lazyneoaz/testfca",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
4
4
|
"type": "commonjs",
|
|
5
5
|
"description": "Advanced Facebook Chat API client for building Messenger bots — supports real-time messaging, thread management, MQTT, session stability, anti-automation protection, and real E2EE via Signal Protocol.",
|
|
6
6
|
"main": "index.js",
|
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
"files": [
|
|
12
12
|
"index.js",
|
|
13
13
|
"src/",
|
|
14
|
-
"scripts/",
|
|
15
14
|
"prebuilt/",
|
|
15
|
+
"scripts/",
|
|
16
16
|
"LICENSE",
|
|
17
17
|
"README.md",
|
|
18
18
|
"CHANGELOG.md",
|
|
@@ -61,8 +61,8 @@
|
|
|
61
61
|
"form-data": "^4.0.4",
|
|
62
62
|
"https-proxy-agent": "^7.0.6",
|
|
63
63
|
"jsonpath-plus": "^10.3.0",
|
|
64
|
-
"koffi": "^3.0.2",
|
|
65
64
|
"lodash": "^4.17.21",
|
|
65
|
+
"meta-messenger.js": "^1.1.3",
|
|
66
66
|
"mqtt": "^4.3.8",
|
|
67
67
|
"node-cron": "^3.0.3",
|
|
68
68
|
"npmlog": "^7.0.1",
|
|
@@ -84,15 +84,14 @@
|
|
|
84
84
|
"typescript": "^5.8.3"
|
|
85
85
|
},
|
|
86
86
|
"engines": {
|
|
87
|
-
"node": ">=
|
|
87
|
+
"node": ">=20.18.1"
|
|
88
88
|
},
|
|
89
89
|
"scripts": {
|
|
90
|
+
"postinstall": "node scripts/postinstall.js",
|
|
90
91
|
"prepack": "echo 'Preparing package for npm...'",
|
|
91
92
|
"test": "echo 'No tests configured yet'",
|
|
92
93
|
"lint": "eslint src/",
|
|
93
94
|
"format": "prettier --write src/**/*.js",
|
|
94
|
-
"postinstall": "node scripts/postinstall.mjs",
|
|
95
|
-
"build:go": "node scripts/build-go.mjs",
|
|
96
95
|
"validate": "npm pack --dry-run"
|
|
97
96
|
},
|
|
98
97
|
"overrides": {
|
|
Binary file
|
package/scripts/build-go.mjs
CHANGED
|
@@ -17,7 +17,14 @@ function runGo(args) {
|
|
|
17
17
|
const res = spawnSync(process.env.GO_BIN || "go", args, {
|
|
18
18
|
cwd: bridgeDir,
|
|
19
19
|
stdio: "inherit",
|
|
20
|
-
env: {
|
|
20
|
+
env: {
|
|
21
|
+
...process.env,
|
|
22
|
+
CGO_ENABLED: "1",
|
|
23
|
+
// Prevent Go from downloading a newer toolchain when go.mod specifies
|
|
24
|
+
// a version higher than what is locally installed. The local compiler
|
|
25
|
+
// should always be used in sandboxed / offline environments.
|
|
26
|
+
GOTOOLCHAIN: "local",
|
|
27
|
+
},
|
|
21
28
|
});
|
|
22
29
|
if (res.error) {
|
|
23
30
|
console.error(`[${name}] Failed to spawn Go: ${res.error.message}`);
|
|
@@ -9,20 +9,13 @@ import { packageJson as pkg } from "./package.mjs";
|
|
|
9
9
|
|
|
10
10
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
11
11
|
|
|
12
|
-
function defaultRepoSlug() {
|
|
13
|
-
// Use yumi-team/meta-messenger.js which hosts prebuilt messagix binaries
|
|
14
|
-
return "yumi-team/meta-messenger.js";
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function latestTag() {
|
|
18
|
-
return "v1.1.3";
|
|
19
|
-
}
|
|
20
|
-
|
|
21
12
|
function buildBaseURL() {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
13
|
+
// yumi-team/meta-messenger.js hosts prebuilt messagix binaries for all
|
|
14
|
+
// platforms except linux-x64-gnu, which ships directly in this package's
|
|
15
|
+
// prebuilt/ directory and is therefore never fetched from here.
|
|
16
|
+
const repo = "yumi-team/meta-messenger.js";
|
|
17
|
+
const tag = "v1.1.3";
|
|
18
|
+
return `https://github.com/${repo}/releases/download/${tag}`;
|
|
26
19
|
}
|
|
27
20
|
|
|
28
21
|
function httpGet(url, redirectCount = 0) {
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Postinstall: replace meta-messenger.js's binary with our TLS-fixed build.
|
|
5
|
+
*
|
|
6
|
+
* WHY this is needed:
|
|
7
|
+
* The meta-messenger.js binary lacks InsecureTLS=true in its messagix HTTP
|
|
8
|
+
* client. In sandboxed/containerised environments (Replit, Docker, CI) the
|
|
9
|
+
* Go runtime cannot locate system CA certificates, so every prekey-bundle
|
|
10
|
+
* HTTPS request to Facebook fails silently. Without prekeys, Signal sessions
|
|
11
|
+
* are never established and sendE2EEMessage always times out.
|
|
12
|
+
*
|
|
13
|
+
* Our prebuilt/ ships the same binary compiled with InsecureTLS=true.
|
|
14
|
+
*
|
|
15
|
+
* INSTALL ORDER (guaranteed by npm):
|
|
16
|
+
* 1. meta-messenger.js postinstall runs first (copies/downloads their binary)
|
|
17
|
+
* 2. Our postinstall runs second and overwrites it with ours.
|
|
18
|
+
*
|
|
19
|
+
* PATH RESOLUTION:
|
|
20
|
+
* Uses require.resolve() so the path is correct whether meta-messenger.js is
|
|
21
|
+
* at the project root node_modules/ or nested — works in all npm versions.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
const fs = require('fs');
|
|
25
|
+
const path = require('path');
|
|
26
|
+
|
|
27
|
+
function platformKey() {
|
|
28
|
+
const p = process.platform;
|
|
29
|
+
const a = process.arch;
|
|
30
|
+
if (p === 'linux' && a === 'x64') return 'linux-x64-gnu';
|
|
31
|
+
if (p === 'linux' && a === 'arm64') return 'linux-arm64-gnu';
|
|
32
|
+
if (p === 'darwin' && a === 'x64') return 'darwin-x64';
|
|
33
|
+
if (p === 'darwin' && a === 'arm64') return 'darwin-arm64';
|
|
34
|
+
if (p === 'win32' && a === 'x64') return 'win32-x64-msvc';
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function ext() {
|
|
39
|
+
if (process.platform === 'win32') return '.dll';
|
|
40
|
+
if (process.platform === 'darwin') return '.dylib';
|
|
41
|
+
return '.so';
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function findMetaMessengerBuildDir() {
|
|
45
|
+
try {
|
|
46
|
+
// resolve() finds the package regardless of hoisting depth
|
|
47
|
+
const pkgJson = require.resolve('meta-messenger.js/package.json');
|
|
48
|
+
return path.join(path.dirname(pkgJson), 'build');
|
|
49
|
+
} catch (_) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function main() {
|
|
55
|
+
if (process.env.NKXFCA_SKIP_POSTINSTALL === 'true') {
|
|
56
|
+
console.log('[nkxfca] postinstall skipped (NKXFCA_SKIP_POSTINSTALL=true)');
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const key = platformKey();
|
|
61
|
+
if (!key) {
|
|
62
|
+
console.log('[nkxfca] postinstall: unsupported platform (' + process.platform + '/' + process.arch + '), skipping binary patch.');
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const binaryExt = ext();
|
|
67
|
+
const ourBin = path.join(__dirname, '..', 'prebuilt', key, 'messagix' + binaryExt);
|
|
68
|
+
|
|
69
|
+
if (!fs.existsSync(ourBin)) {
|
|
70
|
+
console.log('[nkxfca] postinstall: no prebuilt for ' + key + ', skipping binary patch.');
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const mmBuildDir = findMetaMessengerBuildDir();
|
|
75
|
+
if (!mmBuildDir) {
|
|
76
|
+
console.log('[nkxfca] postinstall: meta-messenger.js not found via require.resolve, skipping patch.');
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const mmBin = path.join(mmBuildDir, 'messagix' + binaryExt);
|
|
81
|
+
|
|
82
|
+
// Ensure the build directory exists (meta-messenger.js postinstall creates it,
|
|
83
|
+
// but guard against edge cases where it hasn't run yet)
|
|
84
|
+
try {
|
|
85
|
+
fs.mkdirSync(mmBuildDir, { recursive: true });
|
|
86
|
+
} catch (_) {}
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
fs.copyFileSync(ourBin, mmBin);
|
|
90
|
+
console.log('[nkxfca] postinstall: patched meta-messenger.js binary with TLS-fixed build (' + key + ')');
|
|
91
|
+
} catch (err) {
|
|
92
|
+
// Non-fatal: warn but don't fail the install
|
|
93
|
+
console.warn('[nkxfca] postinstall: could not patch binary: ' + err.message);
|
|
94
|
+
console.warn('[nkxfca] E2EE messaging may not work in sandboxed environments.');
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
main();
|
package/scripts/postinstall.mjs
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { spawnSync } from "node:child_process";
|
|
2
1
|
import { existsSync } from "node:fs";
|
|
3
|
-
import { copyFile, mkdir
|
|
2
|
+
import { copyFile, mkdir } from "node:fs/promises";
|
|
4
3
|
import { dirname, join } from "node:path";
|
|
5
4
|
import { fileURLToPath } from "node:url";
|
|
6
5
|
|
|
@@ -49,13 +48,8 @@ async function run() {
|
|
|
49
48
|
console.log(`[${pkg.name}] Checking local prebuilt at: ${prebuilt}`);
|
|
50
49
|
if (await copyIfExists(prebuilt, buildOut)) {
|
|
51
50
|
console.log(`[${pkg.name}] Using local prebuilt for ${triplet}`);
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
await rm(prebuiltDir, { recursive: true, force: true });
|
|
55
|
-
} catch {
|
|
56
|
-
//
|
|
57
|
-
}
|
|
58
|
-
}
|
|
51
|
+
// Keep the prebuilt copy so reinstalls (e.g. npm rebuild, CI caches) don't
|
|
52
|
+
// lose the patched binary and fall back to downloading an unpatched one.
|
|
59
53
|
console.log(`[${pkg.name}] postinstall completed in ${Date.now() - startTime}ms`);
|
|
60
54
|
return;
|
|
61
55
|
}
|
|
@@ -77,32 +71,20 @@ async function run() {
|
|
|
77
71
|
);
|
|
78
72
|
}
|
|
79
73
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
} catch (err) {
|
|
95
|
-
console.warn(`[${pkg.name}] Local build failed:`, err?.message || String(err));
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
console.warn(`[${pkg.name}] No prebuilt found (local/remote) and no local build completed.`);
|
|
100
|
-
console.warn(`[${pkg.name}] Expected triplet: ${triplet}, file: messagix-${triplet}.${ext}`);
|
|
101
|
-
console.warn(
|
|
102
|
-
`[${pkg.name}] You can:\n` +
|
|
103
|
-
" - set MESSAGIX_BUILD_FROM_SOURCE=true and re-run install\n" +
|
|
104
|
-
" - or build manually with: npm run build:go",
|
|
105
|
-
);
|
|
74
|
+
console.warn(`[${pkg.name}] ──────────────────────────────────────────────────────────`);
|
|
75
|
+
console.warn(`[${pkg.name}] No prebuilt E2EE bridge found for your platform.`);
|
|
76
|
+
console.warn(`[${pkg.name}] Platform detected: ${triplet}`);
|
|
77
|
+
console.warn(`[${pkg.name}]`);
|
|
78
|
+
console.warn(`[${pkg.name}] Supported out-of-the-box:`);
|
|
79
|
+
console.warn(`[${pkg.name}] linux-x64-gnu (ships inside the npm package)`);
|
|
80
|
+
console.warn(`[${pkg.name}] linux-x64-musl / darwin-x64 / darwin-arm64 / win32-x64`);
|
|
81
|
+
console.warn(`[${pkg.name}] (downloaded automatically from yumi-team/meta-messenger.js)`);
|
|
82
|
+
console.warn(`[${pkg.name}]`);
|
|
83
|
+
console.warn(`[${pkg.name}] If the download failed, check your internet connection and retry:`);
|
|
84
|
+
console.warn(`[${pkg.name}] npm install`);
|
|
85
|
+
console.warn(`[${pkg.name}]`);
|
|
86
|
+
console.warn(`[${pkg.name}] Non-E2EE features (sendMessage, listen, etc.) work without the bridge.`);
|
|
87
|
+
console.warn(`[${pkg.name}] ──────────────────────────────────────────────────────────`);
|
|
106
88
|
}
|
|
107
89
|
|
|
108
90
|
run()
|
package/src/e2ee/bridge.js
CHANGED
|
@@ -1,275 +1,330 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Lightweight E2EE bridge — delegates to the `meta-messenger.js` npm package.
|
|
5
|
+
*
|
|
6
|
+
* Our postinstall replaces meta-messenger.js's binary with our TLS-fixed build
|
|
7
|
+
* so that prekey HTTPS requests succeed in sandboxed environments (Replit, Docker).
|
|
8
|
+
*
|
|
9
|
+
* Key design decisions:
|
|
10
|
+
* - enableE2EE: false → we call connectE2EE() explicitly; no auto-start race
|
|
11
|
+
* - e2eeMemoryOnly: false → Signal sessions are persisted to devicePath on disk
|
|
12
|
+
* - devicePath defaults to ".nkxfca_e2ee_device.json" in cwd when not provided
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
let _Client = null;
|
|
16
|
+
let _loadError = null;
|
|
17
|
+
|
|
18
|
+
function loadClient() {
|
|
19
|
+
if (_Client) return _Client;
|
|
20
|
+
if (_loadError) throw _loadError;
|
|
21
|
+
try {
|
|
22
|
+
const mm = require('meta-messenger.js');
|
|
23
|
+
_Client = mm.Client || mm.default?.Client;
|
|
24
|
+
if (!_Client) throw new Error('meta-messenger.js: Client class not found in exports');
|
|
25
|
+
return _Client;
|
|
26
|
+
} catch (err) {
|
|
27
|
+
_loadError = new Error(
|
|
28
|
+
'E2EE requires meta-messenger.js and its native binary.\n' +
|
|
29
|
+
'Install with: npm install meta-messenger.js\n' +
|
|
30
|
+
'Error: ' + err.message
|
|
27
31
|
);
|
|
32
|
+
throw _loadError;
|
|
28
33
|
}
|
|
29
|
-
koffi = require('koffi');
|
|
30
|
-
lib = koffi.load(LIB_PATH);
|
|
31
|
-
const mk = (ret, name, args) => lib.func(name, ret, args);
|
|
32
|
-
fns = {
|
|
33
|
-
MxFreeCString: mk('void', 'MxFreeCString', ['char*']),
|
|
34
|
-
MxNewClient: mk('str', 'MxNewClient', ['str']),
|
|
35
|
-
MxConnect: mk('str', 'MxConnect', ['str']),
|
|
36
|
-
MxConnectE2EE: mk('str', 'MxConnectE2EE', ['str']),
|
|
37
|
-
MxDisconnect: mk('str', 'MxDisconnect', ['str']),
|
|
38
|
-
MxIsConnected: mk('str', 'MxIsConnected', ['str']),
|
|
39
|
-
MxSendMessage: mk('str', 'MxSendMessage', ['str']),
|
|
40
|
-
MxSendReaction: mk('str', 'MxSendReaction', ['str']),
|
|
41
|
-
MxEditMessage: mk('str', 'MxEditMessage', ['str']),
|
|
42
|
-
MxUnsendMessage: mk('str', 'MxUnsendMessage', ['str']),
|
|
43
|
-
MxSendTyping: mk('str', 'MxSendTyping', ['str']),
|
|
44
|
-
MxMarkRead: mk('str', 'MxMarkRead', ['str']),
|
|
45
|
-
MxPollEvents: mk('str', 'MxPollEvents', ['str']),
|
|
46
|
-
MxGetDeviceData: mk('str', 'MxGetDeviceData', ['str']),
|
|
47
|
-
MxSendE2EEMessage: mk('str', 'MxSendE2EEMessage', ['str']),
|
|
48
|
-
MxSendE2EEReaction: mk('str', 'MxSendE2EEReaction', ['str']),
|
|
49
|
-
MxSendE2EETyping: mk('str', 'MxSendE2EETyping', ['str']),
|
|
50
|
-
MxEditE2EEMessage: mk('str', 'MxEditE2EEMessage', ['str']),
|
|
51
|
-
MxUnsendE2EEMessage: mk('str', 'MxUnsendE2EEMessage', ['str']),
|
|
52
|
-
MxSendE2EEImage: mk('str', 'MxSendE2EEImage', ['str']),
|
|
53
|
-
MxSendE2EEVideo: mk('str', 'MxSendE2EEVideo', ['str']),
|
|
54
|
-
MxSendE2EEAudio: mk('str', 'MxSendE2EEAudio', ['str']),
|
|
55
|
-
MxSendE2EEDocument: mk('str', 'MxSendE2EEDocument', ['str']),
|
|
56
|
-
MxSendE2EESticker: mk('str', 'MxSendE2EESticker', ['str']),
|
|
57
|
-
MxDownloadE2EEMedia: mk('str', 'MxDownloadE2EEMedia', ['str']),
|
|
58
|
-
MxGetCookies: mk('str', 'MxGetCookies', ['str']),
|
|
59
|
-
MxUploadMedia: mk('str', 'MxUploadMedia', ['str']),
|
|
60
|
-
MxSendImage: mk('str', 'MxSendImage', ['str']),
|
|
61
|
-
MxSendVideo: mk('str', 'MxSendVideo', ['str']),
|
|
62
|
-
MxSendVoice: mk('str', 'MxSendVoice', ['str']),
|
|
63
|
-
MxSendFile: mk('str', 'MxSendFile', ['str']),
|
|
64
|
-
MxSendSticker: mk('str', 'MxSendSticker', ['str']),
|
|
65
|
-
MxGetUserInfo: mk('str', 'MxGetUserInfo', ['str']),
|
|
66
|
-
MxCreateThread: mk('str', 'MxCreateThread', ['str']),
|
|
67
|
-
MxSearchUsers: mk('str', 'MxSearchUsers', ['str']),
|
|
68
|
-
MxRenameThread: mk('str', 'MxRenameThread', ['str']),
|
|
69
|
-
MxMuteThread: mk('str', 'MxMuteThread', ['str']),
|
|
70
|
-
MxDeleteThread: mk('str', 'MxDeleteThread', ['str']),
|
|
71
|
-
MxSetGroupPhoto: mk('str', 'MxSetGroupPhoto', ['str']),
|
|
72
|
-
MxRegisterPushNotifications: mk('str', 'MxRegisterPushNotifications', ['str']),
|
|
73
|
-
};
|
|
74
|
-
initialized = true;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function serialize(obj) {
|
|
78
|
-
return JSON.stringify(obj, (_, v) =>
|
|
79
|
-
typeof v === 'bigint' ? Number(v) : v
|
|
80
|
-
);
|
|
81
34
|
}
|
|
82
35
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
36
|
+
const EVENT_TYPES = [
|
|
37
|
+
'message', 'messageEdit', 'messageUnsend', 'reaction',
|
|
38
|
+
'typing', 'readReceipt', 'e2eeMessage', 'e2eeReaction',
|
|
39
|
+
'e2eeReceipt', 'e2eeConnected', 'ready', 'reconnected',
|
|
40
|
+
'disconnected', 'error', 'deviceDataChanged', 'raw',
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
// Registry: handle (string) -> { client, eventBuffer, resolveWaiter, lastDeviceData }
|
|
44
|
+
const registry = new Map();
|
|
45
|
+
let seq = 1;
|
|
46
|
+
|
|
47
|
+
function newClient(cfg) {
|
|
48
|
+
const Client = loadClient();
|
|
49
|
+
const cookies = cfg.cookies || {};
|
|
50
|
+
|
|
51
|
+
const opts = {
|
|
52
|
+
platform: cfg.platform || 'facebook',
|
|
53
|
+
logLevel: cfg.logLevel || 'warn',
|
|
54
|
+
enableE2EE: false, // we call connectE2EE() explicitly
|
|
55
|
+
e2eeMemoryOnly: false, // always persist sessions to disk
|
|
56
|
+
autoReconnect: cfg.autoReconnect !== false,
|
|
57
|
+
};
|
|
86
58
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
return data.data;
|
|
94
|
-
}
|
|
59
|
+
// Device storage: prefer deviceData (in-memory JSON) > devicePath > default file
|
|
60
|
+
if (cfg.deviceData) {
|
|
61
|
+
opts.deviceData = cfg.deviceData;
|
|
62
|
+
} else {
|
|
63
|
+
opts.devicePath = cfg.devicePath || '.nkxfca_e2ee_device.json';
|
|
64
|
+
}
|
|
95
65
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
66
|
+
const client = new Client(cookies, opts);
|
|
67
|
+
const handle = String(seq++);
|
|
68
|
+
const entry = { client, eventBuffer: [], resolveWaiter: null, lastDeviceData: null };
|
|
69
|
+
|
|
70
|
+
for (const type of EVENT_TYPES) {
|
|
71
|
+
client.on(type, (data) => {
|
|
72
|
+
if (type === 'deviceDataChanged' && data?.deviceData) {
|
|
73
|
+
entry.lastDeviceData = data.deviceData;
|
|
74
|
+
}
|
|
75
|
+
entry.eventBuffer.push({ type, data: data ?? null, timestamp: Date.now() });
|
|
76
|
+
if (entry.resolveWaiter) {
|
|
77
|
+
const resolve = entry.resolveWaiter;
|
|
78
|
+
entry.resolveWaiter = null;
|
|
79
|
+
resolve();
|
|
80
|
+
}
|
|
101
81
|
});
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
module.exports = {
|
|
106
|
-
isAvailable() {
|
|
107
|
-
try { init(); return true; } catch (_) { return false; }
|
|
108
|
-
},
|
|
109
|
-
|
|
110
|
-
newClient(cfg) {
|
|
111
|
-
return call('MxNewClient', cfg);
|
|
112
|
-
},
|
|
113
|
-
|
|
114
|
-
connect(handle) {
|
|
115
|
-
return callAsync('MxConnect', { handle });
|
|
116
|
-
},
|
|
117
|
-
|
|
118
|
-
connectE2EE(handle) {
|
|
119
|
-
return callAsync('MxConnectE2EE', { handle });
|
|
120
|
-
},
|
|
121
|
-
|
|
122
|
-
disconnect(handle) {
|
|
123
|
-
try { return call('MxDisconnect', { handle }); } catch (_) {}
|
|
124
|
-
},
|
|
125
|
-
|
|
126
|
-
isConnected(handle) {
|
|
127
|
-
try { return call('MxIsConnected', { handle }); }
|
|
128
|
-
catch (_) { return { connected: false, e2eeConnected: false }; }
|
|
129
|
-
},
|
|
82
|
+
}
|
|
130
83
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
84
|
+
registry.set(handle, entry);
|
|
85
|
+
return { handle };
|
|
86
|
+
}
|
|
134
87
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
}
|
|
88
|
+
function getEntry(handle) {
|
|
89
|
+
const entry = registry.get(String(handle));
|
|
90
|
+
if (!entry) throw new Error(`E2EE bridge: unknown handle "${handle}"`);
|
|
91
|
+
return entry;
|
|
92
|
+
}
|
|
138
93
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
94
|
+
async function connect(handle) {
|
|
95
|
+
return getEntry(handle).client.connect();
|
|
96
|
+
}
|
|
142
97
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
98
|
+
/**
|
|
99
|
+
* Initiate E2EE handshake. Returns after the background connect is kicked
|
|
100
|
+
* off — listen for the 'e2eeConnected' event (via pollEvents) to know when
|
|
101
|
+
* the channel is fully ready.
|
|
102
|
+
*/
|
|
103
|
+
async function connectE2EE(handle) {
|
|
104
|
+
return getEntry(handle).client.connectE2EE();
|
|
105
|
+
}
|
|
146
106
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
107
|
+
function disconnect(handle) {
|
|
108
|
+
const entry = registry.get(String(handle));
|
|
109
|
+
if (!entry) return;
|
|
110
|
+
try { entry.client.disconnect(); } catch (_) {}
|
|
111
|
+
registry.delete(String(handle));
|
|
112
|
+
}
|
|
150
113
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
114
|
+
function isConnected(handle) {
|
|
115
|
+
try {
|
|
116
|
+
const { client } = getEntry(handle);
|
|
117
|
+
return { connected: !!client.isConnected, e2eeConnected: !!client.isE2EEConnected };
|
|
118
|
+
} catch (_) {
|
|
119
|
+
return { connected: false, e2eeConnected: false };
|
|
120
|
+
}
|
|
121
|
+
}
|
|
154
122
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
123
|
+
function getDeviceData(handle) {
|
|
124
|
+
return { deviceData: getEntry(handle).lastDeviceData };
|
|
125
|
+
}
|
|
158
126
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
127
|
+
async function pollEvents(handle, timeoutMs = 5000) {
|
|
128
|
+
const entry = getEntry(handle);
|
|
129
|
+
if (entry.eventBuffer.length > 0) {
|
|
130
|
+
return { events: entry.eventBuffer.splice(0) };
|
|
131
|
+
}
|
|
132
|
+
await new Promise((resolve) => {
|
|
133
|
+
const timer = setTimeout(() => {
|
|
134
|
+
entry.resolveWaiter = null;
|
|
135
|
+
resolve();
|
|
136
|
+
}, timeoutMs);
|
|
137
|
+
entry.resolveWaiter = () => {
|
|
138
|
+
clearTimeout(timer);
|
|
139
|
+
resolve();
|
|
140
|
+
};
|
|
141
|
+
});
|
|
142
|
+
return { events: entry.eventBuffer.splice(0) };
|
|
143
|
+
}
|
|
162
144
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
145
|
+
function toBuffer(data) {
|
|
146
|
+
if (Buffer.isBuffer(data)) return data;
|
|
147
|
+
if (typeof data === 'string') return Buffer.from(data, 'base64');
|
|
148
|
+
return Buffer.from(data);
|
|
149
|
+
}
|
|
166
150
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
151
|
+
// Convert JID string ("12345@msgr") or plain number/string to BigInt
|
|
152
|
+
function toBigInt(val) {
|
|
153
|
+
if (typeof val === 'bigint') return val;
|
|
154
|
+
return BigInt(String(val).split('@')[0]);
|
|
155
|
+
}
|
|
170
156
|
|
|
171
|
-
|
|
172
|
-
return callAsync('MxSendE2EETyping', { handle, chatJid, isTyping: !!isTyping });
|
|
173
|
-
},
|
|
157
|
+
// ── E2EE operations ────────────────────────────────────────────────────────────
|
|
174
158
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
159
|
+
async function sendE2EEMessage(handle, chatJid, text, replyToId, replyToSenderJid) {
|
|
160
|
+
const opts = replyToId ? { replyToId, replyToSenderJid } : undefined;
|
|
161
|
+
return getEntry(handle).client.sendE2EEMessage(chatJid, text, opts);
|
|
162
|
+
}
|
|
178
163
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
164
|
+
async function sendE2EEReaction(handle, chatJid, messageId, senderJid, emoji) {
|
|
165
|
+
return getEntry(handle).client.sendE2EEReaction(chatJid, messageId, senderJid, emoji);
|
|
166
|
+
}
|
|
182
167
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
168
|
+
async function sendE2EETyping(handle, chatJid, isTyping) {
|
|
169
|
+
return getEntry(handle).client.sendE2EETyping(chatJid, isTyping);
|
|
170
|
+
}
|
|
186
171
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
172
|
+
async function editE2EEMessage(handle, chatJid, messageId, newText) {
|
|
173
|
+
return getEntry(handle).client.editE2EEMessage(chatJid, messageId, newText);
|
|
174
|
+
}
|
|
190
175
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
176
|
+
async function unsendE2EEMessage(handle, chatJid, messageId) {
|
|
177
|
+
return getEntry(handle).client.unsendE2EEMessage(chatJid, messageId);
|
|
178
|
+
}
|
|
194
179
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
180
|
+
async function sendE2EEImage(handle, options) {
|
|
181
|
+
const { chatJid, data, mimeType, ...rest } = options;
|
|
182
|
+
return getEntry(handle).client.sendE2EEImage(chatJid, toBuffer(data), mimeType, rest);
|
|
183
|
+
}
|
|
198
184
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
185
|
+
async function sendE2EEVideo(handle, options) {
|
|
186
|
+
const { chatJid, data, mimeType, ...rest } = options;
|
|
187
|
+
return getEntry(handle).client.sendE2EEVideo(chatJid, toBuffer(data), mimeType, rest);
|
|
188
|
+
}
|
|
202
189
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
190
|
+
async function sendE2EEAudio(handle, options) {
|
|
191
|
+
const { chatJid, data, mimeType, ...rest } = options;
|
|
192
|
+
return getEntry(handle).client.sendE2EEAudio(chatJid, toBuffer(data), mimeType, rest);
|
|
193
|
+
}
|
|
206
194
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
195
|
+
async function sendE2EEDocument(handle, options) {
|
|
196
|
+
const { chatJid, data, filename, mimeType, ...rest } = options;
|
|
197
|
+
return getEntry(handle).client.sendE2EEDocument(chatJid, toBuffer(data), filename, mimeType, rest);
|
|
198
|
+
}
|
|
210
199
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
200
|
+
async function sendE2EESticker(handle, options) {
|
|
201
|
+
const { chatJid, data, mimeType, ...rest } = options;
|
|
202
|
+
return getEntry(handle).client.sendE2EESticker(chatJid, toBuffer(data), mimeType, rest);
|
|
203
|
+
}
|
|
214
204
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
205
|
+
async function downloadE2EEMedia(handle, options) {
|
|
206
|
+
return getEntry(handle).client.downloadE2EEMedia(options);
|
|
207
|
+
}
|
|
218
208
|
|
|
219
|
-
|
|
220
|
-
return callAsync('MxSendVideo', { handle, options });
|
|
221
|
-
},
|
|
209
|
+
// ── Regular (non-E2EE) Messenger methods ──────────────────────────────────────
|
|
222
210
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
211
|
+
async function uploadMedia(handle, options) {
|
|
212
|
+
const { chatJid, data, filename, mimeType, isVoice } = options;
|
|
213
|
+
return getEntry(handle).client.uploadMedia(
|
|
214
|
+
toBigInt(chatJid), toBuffer(data), filename || 'file', mimeType || 'application/octet-stream', !!isVoice
|
|
215
|
+
);
|
|
216
|
+
}
|
|
226
217
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
}
|
|
218
|
+
async function sendImage(handle, options) {
|
|
219
|
+
const { chatJid, data, filename, caption, replyToId } = options;
|
|
220
|
+
const opts = {};
|
|
221
|
+
if (caption) opts.caption = caption;
|
|
222
|
+
if (replyToId) opts.replyToId = replyToId;
|
|
223
|
+
return getEntry(handle).client.sendImage(
|
|
224
|
+
toBigInt(chatJid), toBuffer(data), filename || 'image.jpg', Object.keys(opts).length ? opts : undefined
|
|
225
|
+
);
|
|
226
|
+
}
|
|
230
227
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
}
|
|
228
|
+
async function sendVideo(handle, options) {
|
|
229
|
+
const { chatJid, data, filename, caption, replyToId } = options;
|
|
230
|
+
const opts = {};
|
|
231
|
+
if (caption) opts.caption = caption;
|
|
232
|
+
if (replyToId) opts.replyToId = replyToId;
|
|
233
|
+
return getEntry(handle).client.sendVideo(
|
|
234
|
+
toBigInt(chatJid), toBuffer(data), filename || 'video.mp4', Object.keys(opts).length ? opts : undefined
|
|
235
|
+
);
|
|
236
|
+
}
|
|
234
237
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
}
|
|
238
|
+
async function sendVoice(handle, options) {
|
|
239
|
+
const { chatJid, data, filename, replyToId } = options;
|
|
240
|
+
const opts = replyToId ? { replyToId } : undefined;
|
|
241
|
+
return getEntry(handle).client.sendVoice(
|
|
242
|
+
toBigInt(chatJid), toBuffer(data), filename || 'audio.ogg', opts
|
|
243
|
+
);
|
|
244
|
+
}
|
|
238
245
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
}
|
|
246
|
+
async function sendFile(handle, options) {
|
|
247
|
+
const { chatJid, data, filename, mimeType, caption, replyToId } = options;
|
|
248
|
+
const opts = {};
|
|
249
|
+
if (caption) opts.caption = caption;
|
|
250
|
+
if (replyToId) opts.replyToId = replyToId;
|
|
251
|
+
return getEntry(handle).client.sendFile(
|
|
252
|
+
toBigInt(chatJid), toBuffer(data), filename || 'file', mimeType || 'application/octet-stream',
|
|
253
|
+
Object.keys(opts).length ? opts : undefined
|
|
254
|
+
);
|
|
255
|
+
}
|
|
242
256
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
}
|
|
257
|
+
async function sendSticker(handle, options) {
|
|
258
|
+
const { chatJid, stickerId, replyToId } = options;
|
|
259
|
+
const opts = replyToId ? { replyToId } : undefined;
|
|
260
|
+
return getEntry(handle).client.sendSticker(
|
|
261
|
+
toBigInt(chatJid), toBigInt(stickerId), opts
|
|
262
|
+
);
|
|
263
|
+
}
|
|
246
264
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
265
|
+
async function getUserInfo(handle, userId) {
|
|
266
|
+
return getEntry(handle).client.getUserInfo(toBigInt(userId));
|
|
267
|
+
}
|
|
250
268
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
269
|
+
async function createThread(handle, userId) {
|
|
270
|
+
return getEntry(handle).client.createThread(toBigInt(userId));
|
|
271
|
+
}
|
|
254
272
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
273
|
+
async function searchUsers(handle, query) {
|
|
274
|
+
return getEntry(handle).client.searchUsers(query);
|
|
275
|
+
}
|
|
258
276
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
277
|
+
async function markRead(handle, chatJid, watermarkTs) {
|
|
278
|
+
return getEntry(handle).client.markAsRead(toBigInt(chatJid), watermarkTs);
|
|
279
|
+
}
|
|
262
280
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
281
|
+
function getCookies(handle) {
|
|
282
|
+
return { cookies: {} };
|
|
283
|
+
}
|
|
266
284
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
lib = null;
|
|
271
|
-
fns = null;
|
|
272
|
-
initialized = false;
|
|
273
|
-
}
|
|
285
|
+
function unload() {
|
|
286
|
+
for (const entry of registry.values()) {
|
|
287
|
+
try { entry.client.disconnect(); } catch (_) {}
|
|
274
288
|
}
|
|
289
|
+
registry.clear();
|
|
290
|
+
_Client = null;
|
|
291
|
+
_loadError = null;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
module.exports = {
|
|
295
|
+
isAvailable() {
|
|
296
|
+
try { loadClient(); return true; } catch (_) { return false; }
|
|
297
|
+
},
|
|
298
|
+
newClient,
|
|
299
|
+
connect,
|
|
300
|
+
connectE2EE,
|
|
301
|
+
disconnect,
|
|
302
|
+
isConnected,
|
|
303
|
+
getDeviceData,
|
|
304
|
+
pollEvents,
|
|
305
|
+
// E2EE operations
|
|
306
|
+
sendE2EEMessage,
|
|
307
|
+
sendE2EEReaction,
|
|
308
|
+
sendE2EETyping,
|
|
309
|
+
editE2EEMessage,
|
|
310
|
+
unsendE2EEMessage,
|
|
311
|
+
sendE2EEImage,
|
|
312
|
+
sendE2EEVideo,
|
|
313
|
+
sendE2EEAudio,
|
|
314
|
+
sendE2EEDocument,
|
|
315
|
+
sendE2EESticker,
|
|
316
|
+
downloadE2EEMedia,
|
|
317
|
+
// Regular Messenger operations
|
|
318
|
+
uploadMedia,
|
|
319
|
+
sendImage,
|
|
320
|
+
sendVideo,
|
|
321
|
+
sendVoice,
|
|
322
|
+
sendFile,
|
|
323
|
+
sendSticker,
|
|
324
|
+
getUserInfo,
|
|
325
|
+
createThread,
|
|
326
|
+
searchUsers,
|
|
327
|
+
markRead,
|
|
328
|
+
getCookies,
|
|
329
|
+
unload,
|
|
275
330
|
};
|
package/src/e2ee/index.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
const path = require('path');
|
|
4
|
-
const fs = require('fs');
|
|
5
4
|
const bridge = require('./bridge');
|
|
6
5
|
|
|
7
6
|
const DEFAULT_DEVICE_PATH = path.join(process.cwd(), '.nkxfca_e2ee_device.json');
|
|
@@ -19,20 +18,10 @@ function extractCookies(jar) {
|
|
|
19
18
|
return cookies;
|
|
20
19
|
}
|
|
21
20
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
function loadSavedDeviceData(devicePath) {
|
|
28
|
-
try {
|
|
29
|
-
if (fs.existsSync(devicePath)) {
|
|
30
|
-
return fs.readFileSync(devicePath, 'utf8');
|
|
31
|
-
}
|
|
32
|
-
} catch (_) {}
|
|
33
|
-
return null;
|
|
34
|
-
}
|
|
35
|
-
|
|
21
|
+
/**
|
|
22
|
+
* Return the bridge handle for this context, creating one on first call.
|
|
23
|
+
* meta-messenger.js handles device data persistence automatically via devicePath.
|
|
24
|
+
*/
|
|
36
25
|
async function getOrCreateHandle(ctx, options) {
|
|
37
26
|
if (ctx._e2eeBridgeHandle != null) return ctx._e2eeBridgeHandle;
|
|
38
27
|
|
|
@@ -41,7 +30,7 @@ async function getOrCreateHandle(ctx, options) {
|
|
|
41
30
|
|
|
42
31
|
const cfg = {
|
|
43
32
|
cookies,
|
|
44
|
-
platform:
|
|
33
|
+
platform: 'facebook',
|
|
45
34
|
logLevel: 'warn',
|
|
46
35
|
devicePath,
|
|
47
36
|
...(options || {}),
|
package/src/utils/axios.js
CHANGED
|
@@ -8,6 +8,7 @@ const FormData = require("form-data");
|
|
|
8
8
|
const { getHeaders } = require("./headers");
|
|
9
9
|
const { getType } = require("./constants");
|
|
10
10
|
const { globalRateLimiter } = require("./rateLimiter");
|
|
11
|
+
const { globalAntiSuspension } = require("./antiSuspension");
|
|
11
12
|
|
|
12
13
|
const jar = new CookieJar();
|
|
13
14
|
const client = wrapper(axios.create({ jar }));
|
|
@@ -112,6 +113,8 @@ async function inspectResponseForSessionIssues(adapted, ctx) {
|
|
|
112
113
|
typeof body === 'object' && body !== null && body.error === 1357001;
|
|
113
114
|
|
|
114
115
|
if (isLoginBlocked) {
|
|
116
|
+
// Trip the circuit breaker to stop retries while the account is flagged.
|
|
117
|
+
globalAntiSuspension.tripCircuitBreaker('login_blocked (1357001)', 45 * 60 * 1000);
|
|
115
118
|
const err = new Error('Facebook blocked the login.');
|
|
116
119
|
err.error = 'login_blocked';
|
|
117
120
|
err.res = body;
|
package/src/utils/clients.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const { makeParsable, log, warn } = require("./constants");
|
|
4
4
|
const { globalRateLimiter, configureRateLimiter } = require("./rateLimiter");
|
|
5
|
+
const { globalAntiSuspension } = require("./antiSuspension");
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Formats a cookie array into a string for use in a cookie jar.
|
|
@@ -151,10 +152,17 @@ function parseAndCheckLogin(ctx, http, retryCount = 0) {
|
|
|
151
152
|
err.errorType = res.error === 1357004 ? "CHECKPOINT" : res.error === 1357031 ? "LOCKED" : "BLOCKED";
|
|
152
153
|
err.requiresReLogin = res.error === 1357004 || res.error === 1357031;
|
|
153
154
|
warn("Account Status", `${ACCOUNT_ERROR_CODES[res.error]} (Code: ${res.error})`);
|
|
155
|
+
// Trip the anti-suspension circuit breaker immediately for blocking errors.
|
|
156
|
+
// This prevents the bot from hammering Facebook while the account is flagged.
|
|
157
|
+
if (res.error === 1357001 || res.error === 1357033 || res.error === 2056003) {
|
|
158
|
+
const cooldownMs = res.error === 1357001 ? 45 * 60 * 1000 : 20 * 60 * 1000;
|
|
159
|
+
globalAntiSuspension.tripCircuitBreaker(ACCOUNT_ERROR_CODES[res.error], cooldownMs);
|
|
160
|
+
}
|
|
154
161
|
throw err;
|
|
155
162
|
}
|
|
156
163
|
|
|
157
164
|
if (res.error === 1357001 || (res.errorSummary && res.errorSummary.includes("blocked"))) {
|
|
165
|
+
globalAntiSuspension.tripCircuitBreaker("Facebook blocked the login (errorSummary)", 45 * 60 * 1000);
|
|
158
166
|
const err = new Error("Facebook blocked the login");
|
|
159
167
|
err.error = "Not logged in.";
|
|
160
168
|
err.errorType = "BLOCKED";
|
package/src/utils/formatters.js
CHANGED
|
@@ -1196,6 +1196,13 @@ function formatID(id) {
|
|
|
1196
1196
|
err.error = "Not logged in.";
|
|
1197
1197
|
err.requiresReLogin = true;
|
|
1198
1198
|
err.loginBlocked = res.error === 1357004;
|
|
1199
|
+
// Trip circuit breaker on blocking/locking to stop retries.
|
|
1200
|
+
if (res.error === 1357001) {
|
|
1201
|
+
try {
|
|
1202
|
+
const { globalAntiSuspension } = require('./antiSuspension');
|
|
1203
|
+
globalAntiSuspension.tripCircuitBreaker('login_blocked (1357001 via formatters)', 45 * 60 * 1000);
|
|
1204
|
+
} catch (_) {}
|
|
1205
|
+
}
|
|
1199
1206
|
throw err;
|
|
1200
1207
|
}
|
|
1201
1208
|
|