hridoy-ultimate 9.0.0 → 9.0.2
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 +346 -69
- package/{docs/UPSTREAM_API_DOCS.md → DOCS.md} +3 -3
- package/README.md +536 -161
- package/func/checkUpdate.js +1 -0
- package/index.d.ts +13 -74
- package/index.js +0 -6
- package/module/config.js +2 -5
- package/module/login.js +1 -2
- package/module/loginHelper.js +9 -41
- package/module/options.js +6 -6
- package/package.json +44 -39
- package/src/api/action/logout.js +0 -19
- package/src/api/messaging/sendMessage.js +1 -1
- package/src/api/messaging/sessionGuard.js +8 -0
- package/src/api/messaging/setMessageReaction.js +2 -7
- package/src/api/socket/core/connectMqtt.js +72 -99
- package/src/api/socket/core/emitAuth.js +17 -9
- package/src/api/socket/e2ee/index.js +15 -33
- package/src/api/socket/e2ee/native/NOTICE.md +2 -2
- package/src/api/socket/e2ee/native/lib/index.mjs +1 -2
- package/src/api/socket/e2ee/native/nativeMediaBridge.js +4 -4
- package/src/api/socket/listenE2EE.js +1 -3
- package/src/api/socket/listenMqtt.js +60 -67
- package/src/utils/antiSuspension.js +3 -3
- package/src/utils/fingerprint.js +35 -16
- package/src/utils/headers.js +1 -1
- package/src/utils/versionCheck.js +47 -0
- package/LICENSE +0 -23
- package/MERGE_REPORT.md +0 -54
- package/THIRD_PARTY_NOTICES.md +0 -36
- package/docs/TEST_RESULTS.md +0 -68
- package/scripts/run-tests.js +0 -19
- package/scripts/syntax-check.js +0 -22
- package/src/api/socket/core/lifecycle.js +0 -117
- package/src/api/socket/e2ee/proto/proto-writer.ts +0 -76
- package/src/utils/typingIndicator.js +0 -202
- package/test/helpers/stub-modules.js +0 -122
- package/test/integration/logout.test.js +0 -69
- package/test/integration/mqtt-lifecycle.test.js +0 -195
- package/test/integration/package.test.js +0 -116
- package/test/integration/typing-wiring.test.js +0 -71
- package/test/unit/fingerprint.test.js +0 -19
- package/test/unit/humanizer.test.js +0 -32
- package/test/unit/lifecycle.test.js +0 -92
- package/test/unit/parsers.test.js +0 -43
- package/test/unit/typing.test.js +0 -136
|
@@ -5,14 +5,9 @@
|
|
|
5
5
|
*/
|
|
6
6
|
const { formatID } = require("../../../utils/format");
|
|
7
7
|
const { getDefaultUserAgent } = require("../../../utils/fingerprint");
|
|
8
|
-
const lifecycle = require("./lifecycle");
|
|
9
8
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
// Configurable through listenMqtt({ tmsWaitTimeoutMs, connectTimeoutMs }).
|
|
13
|
-
const T_MS_WAIT_TIMEOUT_MS = 8000;
|
|
14
|
-
const CONNECT_TIMEOUT_MS = 12000;
|
|
15
|
-
const SUBACK_TIMEOUT_MS = 5000;
|
|
9
|
+
const DEFAULT_RECONNECT_DELAY_MS = 2000;
|
|
10
|
+
const T_MS_WAIT_TIMEOUT_MS = 5000;
|
|
16
11
|
|
|
17
12
|
module.exports = function createListenMqtt(deps) {
|
|
18
13
|
const { WebSocket, mqtt, HttpsProxyAgent, buildStream, buildProxy,
|
|
@@ -21,33 +16,29 @@ module.exports = function createListenMqtt(deps) {
|
|
|
21
16
|
|
|
22
17
|
return function listenMqtt(defaultFuncs, api, ctx, globalCallback) {
|
|
23
18
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
19
|
+
function scheduleReconnect(delayMs) {
|
|
20
|
+
const d = (ctx._mqttOpt && ctx._mqttOpt.reconnectDelayMs) || DEFAULT_RECONNECT_DELAY_MS;
|
|
21
|
+
const ms = typeof delayMs === "number" ? delayMs : d;
|
|
22
|
+
if (ctx._reconnectTimer) {
|
|
23
|
+
logger("mqtt reconnect already scheduled", "warn");
|
|
24
|
+
return; // debounce
|
|
25
|
+
}
|
|
26
|
+
if (ctx._ending) {
|
|
27
|
+
logger("mqtt reconnect skipped - ending", "warn");
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
logger(`mqtt will reconnect in ${ms}ms`, "warn");
|
|
31
|
+
ctx._reconnectTimer = setTimeout(() => {
|
|
32
|
+
ctx._reconnectTimer = null;
|
|
33
|
+
if (!ctx._ending) {
|
|
34
|
+
listenMqtt(defaultFuncs, api, ctx, globalCallback);
|
|
35
|
+
}
|
|
36
|
+
}, ms);
|
|
42
37
|
}
|
|
43
|
-
function
|
|
44
|
-
|
|
45
|
-
listenMqtt(defaultFuncs, api, ctx, globalCallback);
|
|
46
|
-
}, reason);
|
|
47
|
-
if (res === "exhausted") giveUp(reason);
|
|
48
|
-
else if (res === "stopped") logger("mqtt reconnect skipped - stopped", "info");
|
|
49
|
-
return res;
|
|
38
|
+
function isEndingLikeError(msg) {
|
|
39
|
+
return /No subscription existed|client disconnecting|socket hang up|ECONNRESET/i.test(msg || "");
|
|
50
40
|
}
|
|
41
|
+
|
|
51
42
|
// Default to online unless explicitly disabled — GoatBot's optionsFca
|
|
52
43
|
// has no `online` key, which used to make this undefined (falsy), so the
|
|
53
44
|
// account sat permanently "inactive" while actively sending messages.
|
|
@@ -87,7 +78,7 @@ module.exports = function createListenMqtt(deps) {
|
|
|
87
78
|
// Must match the HTTP layer's Accept-Language (src/utils/headers.js)
|
|
88
79
|
// — a hardcoded different value here made the WS handshake and the
|
|
89
80
|
// HTTP requests disagree about the same "browser".
|
|
90
|
-
"Accept-Language": ctx.globalOptions.acceptLanguage || "en-US,en;q=0.9
|
|
81
|
+
"Accept-Language": ctx.globalOptions.acceptLanguage || "en-US,en;q=0.9",
|
|
91
82
|
"Sec-WebSocket-Extensions": "permessage-deflate; client_max_window_bits"
|
|
92
83
|
},
|
|
93
84
|
origin: "https://www.facebook.com",
|
|
@@ -97,7 +88,7 @@ module.exports = function createListenMqtt(deps) {
|
|
|
97
88
|
keepalive: 30,
|
|
98
89
|
reschedulePings: true,
|
|
99
90
|
reconnectPeriod: 0,
|
|
100
|
-
connectTimeout:
|
|
91
|
+
connectTimeout: 5000
|
|
101
92
|
};
|
|
102
93
|
if (ctx.globalOptions.proxy !== undefined) {
|
|
103
94
|
const agent = new HttpsProxyAgent(ctx.globalOptions.proxy);
|
|
@@ -109,11 +100,8 @@ module.exports = function createListenMqtt(deps) {
|
|
|
109
100
|
options
|
|
110
101
|
);
|
|
111
102
|
const mqttClient = ctx.mqttClient;
|
|
112
|
-
// A late event from a replaced/stopped client must never touch current state.
|
|
113
|
-
const isActive = function () { return ctx.mqttClient === mqttClient; };
|
|
114
103
|
|
|
115
104
|
mqttClient.on("error", function (err) {
|
|
116
|
-
if (!isActive()) return;
|
|
117
105
|
const msg = String(err && err.message ? err.message : err || "");
|
|
118
106
|
if ((ctx._ending || ctx._cycling) && /No subscription existed|client disconnecting/i.test(msg)) {
|
|
119
107
|
logger(`mqtt expected during shutdown: ${msg}`, "info");
|
|
@@ -145,80 +133,67 @@ module.exports = function createListenMqtt(deps) {
|
|
|
145
133
|
if (ctx._ending || ctx._cycling) return;
|
|
146
134
|
|
|
147
135
|
if (ctx.globalOptions.autoReconnect && !ctx._ending) {
|
|
148
|
-
|
|
136
|
+
const d = (ctx._mqttOpt && ctx._mqttOpt.reconnectDelayMs) || DEFAULT_RECONNECT_DELAY_MS;
|
|
137
|
+
logger(`mqtt autoReconnect listenMqtt() in ${d}ms`, "warn");
|
|
138
|
+
scheduleReconnect(d);
|
|
149
139
|
} else {
|
|
150
140
|
globalCallback({ type: "stop_listen", error: msg || "Connection refused" }, null);
|
|
151
141
|
}
|
|
152
142
|
});
|
|
153
143
|
|
|
154
144
|
mqttClient.on("connect", function () {
|
|
155
|
-
if (
|
|
145
|
+
if (process.env.OnStatus === undefined) {
|
|
146
|
+
logger("fca-unofficial", "info");
|
|
147
|
+
process.env.OnStatus = true;
|
|
148
|
+
}
|
|
156
149
|
ctx._cycling = false;
|
|
157
150
|
|
|
158
|
-
|
|
159
|
-
// timeout). Publishing before the subscriptions exist can make the
|
|
160
|
-
// server close the socket with "No subscription existed". This ordering
|
|
161
|
-
// is protocol-level behaviour of mqtt.js; NOT verified against live
|
|
162
|
-
// Facebook servers (see README "Known limitations").
|
|
163
|
-
let started = false;
|
|
164
|
-
const startSync = function () {
|
|
165
|
-
if (started || !isActive()) return;
|
|
166
|
-
started = true;
|
|
167
|
-
clearTimeout(subTimer);
|
|
168
|
-
const queue = {
|
|
169
|
-
sync_api_version: 11, max_deltas_able_to_process: 100, delta_batch_size: 500,
|
|
170
|
-
encoding: "JSON", entity_fbid: ctx.userID, initial_titan_sequence_id: ctx.lastSeqId, device_params: null
|
|
171
|
-
};
|
|
172
|
-
const topic = ctx.syncToken ? "/messenger_sync_get_diffs" : "/messenger_sync_create_queue";
|
|
173
|
-
if (ctx.syncToken) { queue.last_seq_id = ctx.lastSeqId; queue.sync_token = ctx.syncToken; }
|
|
174
|
-
mqttClient.publish(topic, JSON.stringify(queue), { qos: 1, retain: false });
|
|
175
|
-
mqttClient.publish("/foreground_state", JSON.stringify({ foreground: chatOn }), { qos: 1 });
|
|
176
|
-
mqttClient.publish("/set_client_settings", JSON.stringify({ make_user_available_when_in_foreground: true }), { qos: 1 });
|
|
151
|
+
topics.forEach(t => mqttClient.subscribe(t));
|
|
177
152
|
|
|
178
|
-
const waitMs = (ctx._mqttOpt && ctx._mqttOpt.tmsWaitTimeoutMs) || T_MS_WAIT_TIMEOUT_MS;
|
|
179
|
-
let rTimeout = setTimeout(function () {
|
|
180
|
-
rTimeout = null;
|
|
181
|
-
if (!isActive() || ctx._ending) {
|
|
182
|
-
logger("mqtt t_ms timeout skipped - ending/replaced", "warn");
|
|
183
|
-
return;
|
|
184
|
-
}
|
|
185
|
-
logger("mqtt t_ms timeout, cycling", "warn");
|
|
186
|
-
try { mqttClient.end(true); } catch (_) { /* already closed */ }
|
|
187
|
-
scheduleReconnect("t_ms timeout");
|
|
188
|
-
}, waitMs);
|
|
189
|
-
ctx._rTimeout = rTimeout;
|
|
190
153
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
// First real sync response: the connection is healthy, reset backoff.
|
|
195
|
-
ctx._reconnectAttempt = 0;
|
|
196
|
-
if (ctx.globalOptions.emitReady) globalCallback({ type: "ready", error: null });
|
|
197
|
-
delete ctx.tmsWait;
|
|
198
|
-
};
|
|
154
|
+
const queue = {
|
|
155
|
+
sync_api_version: 11, max_deltas_able_to_process: 100, delta_batch_size: 500,
|
|
156
|
+
encoding: "JSON", entity_fbid: ctx.userID, initial_titan_sequence_id: ctx.lastSeqId, device_params: null
|
|
199
157
|
};
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
158
|
+
const topic = ctx.syncToken ? "/messenger_sync_get_diffs" : "/messenger_sync_create_queue";
|
|
159
|
+
if (ctx.syncToken) { queue.last_seq_id = ctx.lastSeqId; queue.sync_token = ctx.syncToken; }
|
|
160
|
+
mqttClient.publish(topic, JSON.stringify(queue), { qos: 1, retain: false });
|
|
161
|
+
mqttClient.publish("/foreground_state", JSON.stringify({ foreground: chatOn }), { qos: 1 });
|
|
162
|
+
mqttClient.publish("/set_client_settings", JSON.stringify({ make_user_available_when_in_foreground: true }), { qos: 1 });
|
|
163
|
+
const d = (ctx._mqttOpt && ctx._mqttOpt.reconnectDelayMs) || DEFAULT_RECONNECT_DELAY_MS;
|
|
164
|
+
let rTimeout = setTimeout(function () {
|
|
165
|
+
rTimeout = null;
|
|
166
|
+
if (ctx._ending) {
|
|
167
|
+
logger("mqtt t_ms timeout skipped - ending", "warn");
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
logger(`mqtt t_ms timeout, cycling in ${d}ms`, "warn");
|
|
208
171
|
try {
|
|
209
|
-
mqttClient
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
172
|
+
if (mqttClient && mqttClient.connected) {
|
|
173
|
+
mqttClient.end(true);
|
|
174
|
+
}
|
|
175
|
+
} catch (_) { }
|
|
176
|
+
scheduleReconnect(d);
|
|
177
|
+
}, T_MS_WAIT_TIMEOUT_MS);
|
|
178
|
+
|
|
179
|
+
// Store timeout reference for cleanup
|
|
180
|
+
ctx._rTimeout = rTimeout;
|
|
181
|
+
|
|
182
|
+
ctx.tmsWait = function () {
|
|
183
|
+
if (rTimeout) {
|
|
184
|
+
clearTimeout(rTimeout);
|
|
185
|
+
rTimeout = null;
|
|
216
186
|
}
|
|
217
|
-
|
|
187
|
+
if (ctx._rTimeout) {
|
|
188
|
+
delete ctx._rTimeout;
|
|
189
|
+
}
|
|
190
|
+
if (ctx.globalOptions.emitReady) globalCallback({ type: "ready", error: null });
|
|
191
|
+
delete ctx.tmsWait;
|
|
192
|
+
};
|
|
218
193
|
});
|
|
219
194
|
|
|
220
195
|
mqttClient.on("message", function (topic, message) {
|
|
221
|
-
if (
|
|
196
|
+
if (ctx._ending) return; // Ignore messages if ending
|
|
222
197
|
ctx.lastMqttActivity = Date.now(); // packet-level liveness for the heartbeat
|
|
223
198
|
try {
|
|
224
199
|
let jsonMessage = Buffer.isBuffer(message) ? Buffer.from(message).toString() : message;
|
|
@@ -278,26 +253,24 @@ module.exports = function createListenMqtt(deps) {
|
|
|
278
253
|
});
|
|
279
254
|
|
|
280
255
|
mqttClient.on("close", function () {
|
|
281
|
-
if (!isActive()) return;
|
|
282
256
|
if (ctx._ending || ctx._cycling) {
|
|
283
257
|
logger("mqtt close expected", "info");
|
|
284
258
|
return;
|
|
285
259
|
}
|
|
286
260
|
logger("mqtt connection closed", "warn");
|
|
287
261
|
if (ctx.globalOptions.autoReconnect && !ctx._ending) {
|
|
288
|
-
scheduleReconnect(
|
|
262
|
+
scheduleReconnect();
|
|
289
263
|
}
|
|
290
264
|
});
|
|
291
265
|
|
|
292
266
|
mqttClient.on("disconnect", () => {
|
|
293
|
-
if (!isActive()) return;
|
|
294
267
|
if (ctx._ending || ctx._cycling) {
|
|
295
268
|
logger("mqtt disconnect expected", "info");
|
|
296
269
|
return;
|
|
297
270
|
}
|
|
298
271
|
logger("mqtt disconnected", "warn");
|
|
299
272
|
if (ctx.globalOptions.autoReconnect && !ctx._ending) {
|
|
300
|
-
scheduleReconnect(
|
|
273
|
+
scheduleReconnect();
|
|
301
274
|
}
|
|
302
275
|
});
|
|
303
276
|
};
|
|
@@ -3,14 +3,21 @@
|
|
|
3
3
|
* Emits account_inactive to the global callback and cleans up MQTT/timers/scheduler.
|
|
4
4
|
* Used when login is invalid, blocked, or session is lost.
|
|
5
5
|
*/
|
|
6
|
-
const lifecycle = require("./lifecycle");
|
|
7
|
-
|
|
8
6
|
module.exports = function createEmitAuth({ logger }) {
|
|
9
7
|
return function emitAuth(ctx, api, globalCallback, reason, detail) {
|
|
10
|
-
// Clean up all timers
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
8
|
+
// Clean up all timers
|
|
9
|
+
try {
|
|
10
|
+
if (ctx._autoCycleTimer) {
|
|
11
|
+
clearInterval(ctx._autoCycleTimer);
|
|
12
|
+
ctx._autoCycleTimer = null;
|
|
13
|
+
}
|
|
14
|
+
} catch (_) { }
|
|
15
|
+
try {
|
|
16
|
+
if (ctx._reconnectTimer) {
|
|
17
|
+
clearTimeout(ctx._reconnectTimer);
|
|
18
|
+
ctx._reconnectTimer = null;
|
|
19
|
+
}
|
|
20
|
+
} catch (_) { }
|
|
14
21
|
|
|
15
22
|
try {
|
|
16
23
|
ctx._ending = true;
|
|
@@ -20,9 +27,10 @@ module.exports = function createEmitAuth({ logger }) {
|
|
|
20
27
|
// Clean up MQTT client
|
|
21
28
|
try {
|
|
22
29
|
if (ctx.mqttClient) {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
30
|
+
ctx.mqttClient.removeAllListeners();
|
|
31
|
+
if (ctx.mqttClient.connected) {
|
|
32
|
+
ctx.mqttClient.end(true);
|
|
33
|
+
}
|
|
26
34
|
}
|
|
27
35
|
} catch (_) { }
|
|
28
36
|
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Wraps the `fb-messenger-e2ee` npm package (real dependency, not a bundled
|
|
7
7
|
* vendor copy) and exposes it through NEXCA's own api.e2ee / connectE2EE /
|
|
8
|
-
* listenE2EE surface, so the rest of
|
|
8
|
+
* listenE2EE surface, so the rest of hridoy-ultimate and GoatBot never need to
|
|
9
9
|
* know which E2EE engine is underneath.
|
|
10
10
|
*
|
|
11
11
|
|
|
@@ -17,36 +17,18 @@
|
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
19
|
const path = require("path");
|
|
20
|
-
const fs = require("fs");
|
|
21
20
|
const logger = require("../../../utils/nexca-logger");
|
|
22
21
|
const nativeMediaBridge = require("./native/nativeMediaBridge");
|
|
23
22
|
const { decodeIncomingMedia } = require("./mediaDecode");
|
|
24
23
|
const localMediaServer = require("./localMediaServer");
|
|
25
24
|
|
|
26
|
-
// Native media engine (image/video/audio send,
|
|
27
|
-
//
|
|
28
|
-
//
|
|
29
|
-
//
|
|
30
|
-
//
|
|
31
|
-
// messagix.dll under native/build/ (SHA-256 pinned in THIRD_PARTY_NOTICES.md),
|
|
32
|
-
// but the engine stays OPT-IN: set FCA_E2EE_NATIVE=1 (FCA_E2EE_NATIVE_LIB can
|
|
33
|
-
// still override the bundled path). Upstream 1.0.8 flipped this to default-on;
|
|
34
|
-
// that is rejected here because the native and vendored engines share one E2EE
|
|
35
|
-
// device and kick each other (see the note in connect()).
|
|
36
|
-
function _bundledNativeLib() {
|
|
37
|
-
const base = path.join(__dirname, "native", "build");
|
|
38
|
-
if (process.platform === "win32") return path.join(base, "messagix.dll");
|
|
39
|
-
if (process.platform === "darwin") return path.join(base, "messagix.dylib");
|
|
40
|
-
return path.join(base, "messagix.so");
|
|
41
|
-
}
|
|
42
|
-
function _nativeLibAvailable() {
|
|
43
|
-
try {
|
|
44
|
-
if (process.env.FCA_E2EE_NATIVE_LIB) return fs.existsSync(process.env.FCA_E2EE_NATIVE_LIB);
|
|
45
|
-
return fs.existsSync(_bundledNativeLib());
|
|
46
|
-
} catch (_) { return false; }
|
|
47
|
-
}
|
|
25
|
+
// Native media engine (image/video/audio send, reactions, unsend, markRead,
|
|
26
|
+
// group receive) is ON by default — matches pre-1.0.7 behavior. Set
|
|
27
|
+
// FCA_E2EE_NATIVE=0 to force it off (falls back to the legacy vendor engine)
|
|
28
|
+
// if the native/vendored device-kick conflict described below becomes an
|
|
29
|
+
// issue for your deployment.
|
|
48
30
|
function _nativeMediaEnabled() {
|
|
49
|
-
return process.env.FCA_E2EE_NATIVE
|
|
31
|
+
return process.env.FCA_E2EE_NATIVE !== "0";
|
|
50
32
|
}
|
|
51
33
|
|
|
52
34
|
// Simple FIFO-bounded Map.set — evicts the oldest entry once maxSize is
|
|
@@ -70,7 +52,7 @@ function loadFBClient() {
|
|
|
70
52
|
// 1. fb-messenger-e2ee is NOT published on the npm registry — only
|
|
71
53
|
// available as TypeScript source on GitHub.
|
|
72
54
|
// 2. Depending on it as a git/file dependency nested inside
|
|
73
|
-
//
|
|
55
|
+
// hridoy-ultimate (itself installed via `github:` in GoatBot-Pro)
|
|
74
56
|
// is unreliable — npm does not consistently build nested
|
|
75
57
|
// non-registry dependencies during a git-dependency install.
|
|
76
58
|
// Shipping plain compiled JS here means zero build step is needed
|
|
@@ -404,11 +386,11 @@ class E2EEBridge {
|
|
|
404
386
|
// loop (a fresh Noise handshake every few seconds). Until the two
|
|
405
387
|
// engines can use distinct device ids, the native engine must stay
|
|
406
388
|
// OFF for the vendored socket to stay alive. Opt back in with
|
|
407
|
-
//
|
|
389
|
+
// FCA_E2EE_NATIVE=0 (group E2EE receive + native media sends) if you
|
|
408
390
|
// accept the resulting socket churn.
|
|
409
391
|
const _nativeEnabled = _nativeMediaEnabled();
|
|
410
392
|
if (!_nativeEnabled) {
|
|
411
|
-
logger.info("E2EE", "[native-group] native engine disabled (
|
|
393
|
+
logger.info("E2EE", "[native-group] native engine disabled (FCA_E2EE_NATIVE=0) — vendored socket kept exclusive to avoid server-side device kicks");
|
|
412
394
|
}
|
|
413
395
|
try {
|
|
414
396
|
if (!_nativeEnabled) throw { _skipNative: true };
|
|
@@ -542,7 +524,7 @@ class E2EEBridge {
|
|
|
542
524
|
// Native engine disabled (default) — it shares the same E2EE
|
|
543
525
|
// device id as the vendored socket and triggers server-side
|
|
544
526
|
// device kicks. Group E2EE text send unavailable in this mode.
|
|
545
|
-
throw new Error("E2EE group text send requires the native engine, which is disabled (
|
|
527
|
+
throw new Error("E2EE group text send requires the native engine, which is disabled (FCA_E2EE_NATIVE=0) to protect the vendored socket from server-side device kicks.");
|
|
546
528
|
}
|
|
547
529
|
// Vendor's own group Sender-Key encryption is broken (see native-group
|
|
548
530
|
// listener above) — group text sends go through native instead.
|
|
@@ -621,7 +603,7 @@ class E2EEBridge {
|
|
|
621
603
|
let result;
|
|
622
604
|
try {
|
|
623
605
|
if (!_nativeMediaEnabled()) {
|
|
624
|
-
throw new Error("native engine disabled (
|
|
606
|
+
throw new Error("native engine disabled (FCA_E2EE_NATIVE=0) — see native-group note");
|
|
625
607
|
}
|
|
626
608
|
result = await nativeMediaBridge.sendMedia(appState, threadId, mediaType, data, mimeType, {
|
|
627
609
|
caption: text || undefined,
|
|
@@ -739,7 +721,7 @@ class E2EEBridge {
|
|
|
739
721
|
const isGroup = this._knownE2EEGroups.has(String(threadId));
|
|
740
722
|
try {
|
|
741
723
|
if (!_nativeMediaEnabled())
|
|
742
|
-
throw new Error("native engine disabled (
|
|
724
|
+
throw new Error("native engine disabled (FCA_E2EE_NATIVE=0)");
|
|
743
725
|
return await nativeMediaBridge.sendReaction(this.api.getAppState(), threadId, messageId, senderJid, reaction, isGroup);
|
|
744
726
|
} catch (err) {
|
|
745
727
|
if (isGroup) {
|
|
@@ -762,7 +744,7 @@ class E2EEBridge {
|
|
|
762
744
|
const isGroup = this._knownE2EEGroups.has(String(threadId));
|
|
763
745
|
try {
|
|
764
746
|
if (!_nativeMediaEnabled())
|
|
765
|
-
throw new Error("native engine disabled (
|
|
747
|
+
throw new Error("native engine disabled (FCA_E2EE_NATIVE=0)");
|
|
766
748
|
return await nativeMediaBridge.unsendMessage(this.api.getAppState(), threadId, messageId, isGroup);
|
|
767
749
|
} catch (err) {
|
|
768
750
|
if (isGroup) {
|
|
@@ -780,7 +762,7 @@ class E2EEBridge {
|
|
|
780
762
|
const isGroup = this._knownE2EEGroups.has(String(threadId));
|
|
781
763
|
if (isGroup) {
|
|
782
764
|
if (!_nativeMediaEnabled())
|
|
783
|
-
throw new Error("E2EE group edit requires the native engine, which is disabled (
|
|
765
|
+
throw new Error("E2EE group edit requires the native engine, which is disabled (FCA_E2EE_NATIVE=0).");
|
|
784
766
|
// Vendor's group Sender-Key encryption is broken, so route
|
|
785
767
|
// group edits through native (it doesn't need a chatJid — the
|
|
786
768
|
// messageId alone identifies which chat/message to edit).
|
|
@@ -16,12 +16,12 @@ https://www.mozilla.org/en-US/MPL/2.0/
|
|
|
16
16
|
into the compiled binary, adapted from the same upstream project's Node
|
|
17
17
|
bindings.
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
hridoy-ultimate uses this native engine only for sending E2EE media attachments
|
|
20
20
|
(images/video/audio/documents) in end-to-end encrypted Messenger threads,
|
|
21
21
|
because the project's own hand-written Signal Protocol media encoder
|
|
22
22
|
(`../vendor/fme`) produces payloads that Facebook's servers accept but that
|
|
23
23
|
Messenger clients fail to render. Text messages and all non-media E2EE
|
|
24
|
-
functionality continue to use
|
|
24
|
+
functionality continue to use hridoy-ultimate's original implementation.
|
|
25
25
|
|
|
26
26
|
In accordance with MPL-2.0 §3.3, this NOTICE file must be preserved and
|
|
27
27
|
distributed alongside these files, and any modifications to the covered
|
|
@@ -19,7 +19,6 @@ function resolveDirname() {
|
|
|
19
19
|
}
|
|
20
20
|
__name(resolveDirname, "resolveDirname");
|
|
21
21
|
function libPath() {
|
|
22
|
-
if (process.env.FCA_E2EE_NATIVE_LIB) return process.env.FCA_E2EE_NATIVE_LIB;
|
|
23
22
|
const base = path.join(resolveDirname(), "..", "build");
|
|
24
23
|
if (process.platform === "win32") return path.join(base, "messagix.dll");
|
|
25
24
|
if (process.platform === "darwin") return path.join(base, "messagix.dylib");
|
|
@@ -28,7 +27,7 @@ function libPath() {
|
|
|
28
27
|
__name(libPath, "libPath");
|
|
29
28
|
var LIB_FILE = libPath();
|
|
30
29
|
if (!fs.existsSync(LIB_FILE)) {
|
|
31
|
-
throw new Error(`Native library not found at ${LIB_FILE}.
|
|
30
|
+
throw new Error(`Native library not found at ${LIB_FILE}. Run: npm run build:go`);
|
|
32
31
|
}
|
|
33
32
|
var lib = koffi.load(LIB_FILE);
|
|
34
33
|
var mk = /* @__PURE__ */ __name((ret, name, args) => lib.func(name, ret, args), "mk");
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* Upstream: https://github.com/mautrix/go, https://github.com/mautrix/meta
|
|
7
7
|
* License: https://www.mozilla.org/en-US/MPL/2.0/
|
|
8
8
|
*
|
|
9
|
-
*
|
|
9
|
+
* hridoy-ultimate's own hand-written Signal/Noise media encoder (vendor/fme)
|
|
10
10
|
* sends E2EE image/video/audio/document messages that Facebook's server
|
|
11
11
|
* accepts (valid messageId + CDN path returned) but that the real Messenger
|
|
12
12
|
* client silently fails to render — the reverse-engineered .proto schema
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* mautrix-go/mautrix-meta (a maintained, protocol-accurate Go implementation
|
|
17
17
|
* of the same E2EE stack) via the `koffi` FFI, exactly like the reference
|
|
18
18
|
* "stfca" package does. Only MEDIA sending is routed through here — text
|
|
19
|
-
* messages keep using
|
|
19
|
+
* messages keep using hridoy-ultimate's existing, already-working E2EE path.
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
22
|
const path = require("path");
|
|
@@ -101,7 +101,7 @@ let _connected = false;
|
|
|
101
101
|
* on every reconnect was never releasing the old handle fast enough and
|
|
102
102
|
* leaked native memory over a multi-hour uptime — reusing the instance and
|
|
103
103
|
* just re-calling .connect() avoids that entirely.
|
|
104
|
-
* @param {Array} appState
|
|
104
|
+
* @param {Array} appState hridoy-ultimate's appState (from api.getAppState())
|
|
105
105
|
*/
|
|
106
106
|
async function getClient(appState) {
|
|
107
107
|
if (_connected && _clientInstance) return _clientInstance;
|
|
@@ -184,7 +184,7 @@ async function sendMedia(appState, chatJid, mediaType, data, mimeType, opts) {
|
|
|
184
184
|
const client = await getClient(appState);
|
|
185
185
|
opts = opts || {};
|
|
186
186
|
|
|
187
|
-
// Native client needs a full JID (id@server).
|
|
187
|
+
// Native client needs a full JID (id@server). hridoy-ultimate normalizes
|
|
188
188
|
// E2EE thread ids down to bare numeric strings for GoatBot's benefit,
|
|
189
189
|
// so re-attach the domain here if it's missing — groups use "@g.us",
|
|
190
190
|
// DMs use "@msgr". Using the wrong domain makes native try to look up
|
|
@@ -27,9 +27,7 @@ module.exports = function (defaultFuncs, api, ctx) {
|
|
|
27
27
|
cb([]);
|
|
28
28
|
}
|
|
29
29
|
if (ctx.e2ee && ctx.e2ee.connected) {
|
|
30
|
-
ctx.e2ee.disconnect().catch((
|
|
31
|
-
require("../../../func/logger")("E2EE disconnect failed: " + (e && e.message ? e.message : String(e)), "warn");
|
|
32
|
-
});
|
|
30
|
+
ctx.e2ee.disconnect().catch(() => {});
|
|
33
31
|
}
|
|
34
32
|
}
|
|
35
33
|
stopListeningAsync() {
|