fb-messenger-e2ee 0.1.0 → 0.1.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/README.md +34 -6
- package/dist/index.cjs +8811 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +885 -0
- package/dist/index.d.ts +885 -0
- package/dist/index.js +8843 -0
- package/dist/index.js.map +1 -0
- package/package.json +19 -4
- package/bun.lock +0 -1014
- package/examples/basic-usage.ts +0 -52
- package/jest.config.ts +0 -18
- package/src/config/env.ts +0 -15
- package/src/controllers/client.controller.ts +0 -805
- package/src/controllers/dgw-handler.ts +0 -171
- package/src/controllers/e2ee-handler.ts +0 -832
- package/src/controllers/event-mapper.ts +0 -327
- package/src/core/client.ts +0 -151
- package/src/e2ee/application/e2ee-client.ts +0 -376
- package/src/e2ee/application/fanout-planner.ts +0 -79
- package/src/e2ee/application/outbound-message-cache.ts +0 -58
- package/src/e2ee/application/prekey-maintenance.ts +0 -55
- package/src/e2ee/application/retry-manager.ts +0 -156
- package/src/e2ee/facebook/facebook-protocol-utils.ts +0 -230
- package/src/e2ee/facebook/icdc-payload.ts +0 -31
- package/src/e2ee/index.ts +0 -76
- package/src/e2ee/internal.ts +0 -27
- package/src/e2ee/media/media-crypto.ts +0 -147
- package/src/e2ee/media/media-upload.ts +0 -90
- package/src/e2ee/message/builders/client-payload.ts +0 -54
- package/src/e2ee/message/builders/consumer-application.ts +0 -362
- package/src/e2ee/message/builders/message-application.ts +0 -64
- package/src/e2ee/message/builders/message-transport.ts +0 -84
- package/src/e2ee/message/codecs/protobuf-codecs.ts +0 -101
- package/src/e2ee/message/constants.ts +0 -4
- package/src/e2ee/message/message-builder.ts +0 -15
- package/src/e2ee/signal/prekey-manager.ts +0 -140
- package/src/e2ee/signal/signal-manager.ts +0 -365
- package/src/e2ee/store/device-json.ts +0 -47
- package/src/e2ee/store/device-repository.ts +0 -12
- package/src/e2ee/store/device-store.ts +0 -538
- package/src/e2ee/transport/binary/decoder.ts +0 -180
- package/src/e2ee/transport/binary/encoder.ts +0 -143
- package/src/e2ee/transport/binary/stanzas.ts +0 -97
- package/src/e2ee/transport/binary/tokens.ts +0 -64
- package/src/e2ee/transport/binary/wa-binary.ts +0 -8
- package/src/e2ee/transport/dgw/dgw-socket.ts +0 -345
- package/src/e2ee/transport/noise/noise-handshake.ts +0 -417
- package/src/e2ee/transport/noise/noise-socket.ts +0 -230
- package/src/index.ts +0 -34
- package/src/models/client.ts +0 -26
- package/src/models/config.ts +0 -14
- package/src/models/domain.ts +0 -299
- package/src/models/e2ee.ts +0 -295
- package/src/models/media.ts +0 -41
- package/src/models/messaging.ts +0 -88
- package/src/models/thread.ts +0 -69
- package/src/repositories/session.repository.ts +0 -20
- package/src/services/auth.service.ts +0 -55
- package/src/services/e2ee.service.ts +0 -174
- package/src/services/facebook-gateway.service.ts +0 -245
- package/src/services/icdc.service.ts +0 -177
- package/src/services/media.service.ts +0 -304
- package/src/services/messaging.service.ts +0 -28
- package/src/services/thread.service.ts +0 -199
- package/src/types/advanced-types.ts +0 -61
- package/src/types/fca-unofficial.d.ts +0 -212
- package/src/types/protocol-types.ts +0 -43
- package/src/utils/fca-utils.ts +0 -30
- package/src/utils/logger.ts +0 -24
- package/src/utils/mime.ts +0 -51
- package/tests/.env.example +0 -87
- package/tests/data/1x1.png +0 -0
- package/tests/data/example.txt +0 -1
- package/tests/data/file_example.mp3 +0 -0
- package/tests/data/file_example.mp4 +0 -0
- package/tests/integration.test.ts +0 -498
- package/tests/script/echo-e2ee.ts +0 -174
- package/tests/script/send-e2ee-media.ts +0 -227
- package/tests/script/send-e2ee-reaction.ts +0 -108
- package/tests/script/send-e2ee-unsend.ts +0 -115
- package/tests/script/send-typing.ts +0 -107
- package/tests/script/test-group-send.ts +0 -105
- package/tests/setup.ts +0 -3
- package/tests/types.test.ts +0 -57
- package/tests/unit/controllers/dgw-handler.test.ts +0 -60
- package/tests/unit/controllers/e2ee-handler.test.ts +0 -293
- package/tests/unit/controllers/event-mapper.test.ts +0 -252
- package/tests/unit/e2ee/application-helpers.test.ts +0 -418
- package/tests/unit/e2ee/device-store.test.ts +0 -126
- package/tests/unit/e2ee/facebook-protocol-utils.test.ts +0 -134
- package/tests/unit/e2ee/media-crypto.test.ts +0 -55
- package/tests/unit/e2ee/media-upload.test.ts +0 -60
- package/tests/unit/e2ee/message-builder.test.ts +0 -42
- package/tests/unit/e2ee/message-builders.test.ts +0 -230
- package/tests/unit/e2ee/noise-handshake.test.ts +0 -260
- package/tests/unit/e2ee/prekey-manager.test.ts +0 -55
- package/tests/unit/e2ee/wa-binary.test.ts +0 -127
- package/tests/unit/services/e2ee.service.test.ts +0 -101
- package/tests/unit/services/facebook-gateway.service.test.ts +0 -138
- package/tests/unit/services/media.service.test.ts +0 -169
- package/tests/unit/utils/fca-utils.test.ts +0 -48
- package/tsconfig.json +0 -19
- /package/{src/e2ee/message/proto → proto}/ArmadilloApplication.proto +0 -0
- /package/{src/e2ee/message/proto → proto}/ArmadilloICDC.proto +0 -0
- /package/{src/e2ee/message/proto → proto}/ConsumerApplication.proto +0 -0
- /package/{src/e2ee/message/proto → proto}/MessageApplication.proto +0 -0
- /package/{src/e2ee/message/proto → proto}/MessageTransport.proto +0 -0
- /package/{src/e2ee/message/proto → proto}/WACommon.proto +0 -0
- /package/{src/e2ee/message/proto → proto}/WAMediaTransport.proto +0 -0
- /package/{src/e2ee/message/proto → proto}/proto-writer.ts +0 -0
|
@@ -1,227 +0,0 @@
|
|
|
1
|
-
import { basename, dirname, isAbsolute, join } from "node:path";
|
|
2
|
-
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
3
|
-
import { fileURLToPath } from "node:url";
|
|
4
|
-
import { FBClient } from "../../src/index.ts";
|
|
5
|
-
import type { MessengerEvent } from "../../src/models/domain.ts";
|
|
6
|
-
import { inferFileMediaKindFromMimeType, inferMimeTypeFromFileName, type FileMediaKind } from "../../src/utils/mime.ts";
|
|
7
|
-
|
|
8
|
-
const SCRIPT_DIR = dirname(fileURLToPath(import.meta.url));
|
|
9
|
-
const ROOT_DIR = join(SCRIPT_DIR, "..", "..");
|
|
10
|
-
|
|
11
|
-
const APPSTATE_PATH = join(ROOT_DIR, "tests/appstate.json");
|
|
12
|
-
const SESSION_PATH = join(ROOT_DIR, "tests/session.json");
|
|
13
|
-
const DEVICE_PATH = join(ROOT_DIR, "tests/device.json");
|
|
14
|
-
const ENV_PATH = join(ROOT_DIR, "tests/.env");
|
|
15
|
-
|
|
16
|
-
const DEFAULT_TARGET_JID = "100042415119261.0@msgr";
|
|
17
|
-
const DEFAULT_DATA_DIR = join(ROOT_DIR, "tests/data");
|
|
18
|
-
const DEFAULT_SEND_DELAY_MS = 2_000;
|
|
19
|
-
|
|
20
|
-
type SendKind = FileMediaKind;
|
|
21
|
-
|
|
22
|
-
interface TestMediaFile {
|
|
23
|
-
path: string;
|
|
24
|
-
name: string;
|
|
25
|
-
/** Explicit user-provided MIME override. If absent, send APIs infer from fileName. */
|
|
26
|
-
mimeType?: string;
|
|
27
|
-
/** MIME inferred for logging and choosing the send method in this test script. */
|
|
28
|
-
detectedMimeType: string;
|
|
29
|
-
kind: SendKind;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function loadEnvFile(filePath: string): void {
|
|
33
|
-
if (!existsSync(filePath)) return;
|
|
34
|
-
const raw = readFileSync(filePath, "utf8");
|
|
35
|
-
for (const line of raw.split(/\r?\n/)) {
|
|
36
|
-
const trimmed = line.trim();
|
|
37
|
-
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
38
|
-
const idx = trimmed.indexOf("=");
|
|
39
|
-
if (idx <= 0) continue;
|
|
40
|
-
const key = trimmed.slice(0, idx).trim();
|
|
41
|
-
let value = trimmed.slice(idx + 1).trim();
|
|
42
|
-
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
|
|
43
|
-
value = value.slice(1, -1);
|
|
44
|
-
}
|
|
45
|
-
process.env[key] = value;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function parseMessengerUserId(value: string): string {
|
|
50
|
-
const userPart = value.split("@")[0] ?? value;
|
|
51
|
-
const dotIdx = userPart.indexOf(".");
|
|
52
|
-
const colonIdx = userPart.indexOf(":");
|
|
53
|
-
const cuts = [dotIdx, colonIdx].filter((idx) => idx >= 0).sort((a, b) => a - b);
|
|
54
|
-
const end = cuts[0] ?? userPart.length;
|
|
55
|
-
return userPart.slice(0, end) || value;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function sleep(ms: number): Promise<void> {
|
|
59
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function parseDelayMs(value: string | undefined): number {
|
|
63
|
-
if (!value) return DEFAULT_SEND_DELAY_MS;
|
|
64
|
-
const parsed = Number.parseInt(value, 10);
|
|
65
|
-
return Number.isFinite(parsed) && parsed >= 0 ? parsed : DEFAULT_SEND_DELAY_MS;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function resolvePath(pathOrRelative: string): string {
|
|
69
|
-
return isAbsolute(pathOrRelative) ? pathOrRelative : join(ROOT_DIR, pathOrRelative);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
function toTestMediaFile(filePath: string, mimeOverride?: string): TestMediaFile {
|
|
73
|
-
const detectedMimeType = inferMimeTypeFromFileName(filePath);
|
|
74
|
-
const effectiveMimeType = mimeOverride || detectedMimeType;
|
|
75
|
-
return {
|
|
76
|
-
path: filePath,
|
|
77
|
-
name: basename(filePath),
|
|
78
|
-
mimeType: mimeOverride,
|
|
79
|
-
detectedMimeType,
|
|
80
|
-
kind: inferFileMediaKindFromMimeType(effectiveMimeType),
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
function listDataFiles(dataDir: string): TestMediaFile[] {
|
|
85
|
-
if (!existsSync(dataDir)) {
|
|
86
|
-
throw new Error(`Missing data directory at ${dataDir}`);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
return readdirSync(dataDir)
|
|
90
|
-
.map((name) => join(dataDir, name))
|
|
91
|
-
.filter((filePath) => statSync(filePath).isFile())
|
|
92
|
-
.sort((a, b) => basename(a).localeCompare(basename(b)))
|
|
93
|
-
.map((filePath) => toTestMediaFile(filePath));
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function getFilesToSend(): TestMediaFile[] {
|
|
97
|
-
const pathList = process.env.TEST_E2EE_MEDIA_PATHS
|
|
98
|
-
?.split(",")
|
|
99
|
-
.map((value) => value.trim())
|
|
100
|
-
.filter((value) => value.length > 0);
|
|
101
|
-
|
|
102
|
-
if (pathList && pathList.length > 0) {
|
|
103
|
-
return pathList.map((value) => toTestMediaFile(resolvePath(value)));
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// Backward-compatible single-file mode for the old image-only script.
|
|
107
|
-
if (process.env.TEST_E2EE_IMAGE_PATH) {
|
|
108
|
-
return [toTestMediaFile(resolvePath(process.env.TEST_E2EE_IMAGE_PATH), process.env.TEST_E2EE_IMAGE_MIME)];
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
const dataDir = resolvePath(process.env.TEST_E2EE_MEDIA_DIR ?? DEFAULT_DATA_DIR);
|
|
112
|
-
return listDataFiles(dataDir);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
async function sendFileByKind(
|
|
116
|
-
client: FBClient,
|
|
117
|
-
targetJid: string,
|
|
118
|
-
file: TestMediaFile,
|
|
119
|
-
caption?: string,
|
|
120
|
-
): Promise<Record<string, unknown>> {
|
|
121
|
-
const data = readFileSync(file.path);
|
|
122
|
-
const common = {
|
|
123
|
-
threadId: targetJid,
|
|
124
|
-
data,
|
|
125
|
-
fileName: file.name,
|
|
126
|
-
...(file.mimeType ? { mimeType: file.mimeType } : {}),
|
|
127
|
-
caption,
|
|
128
|
-
};
|
|
129
|
-
|
|
130
|
-
switch (file.kind) {
|
|
131
|
-
case "image":
|
|
132
|
-
return client.sendImage(common);
|
|
133
|
-
case "video":
|
|
134
|
-
return client.sendVideo(common);
|
|
135
|
-
case "audio":
|
|
136
|
-
return client.sendAudio({ ...common, ptt: false });
|
|
137
|
-
case "file":
|
|
138
|
-
return client.sendFile(common);
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
async function main() {
|
|
143
|
-
loadEnvFile(ENV_PATH);
|
|
144
|
-
|
|
145
|
-
const targetJid = process.env.TEST_E2EE_MEDIA_JID ?? process.env.TEST_E2EE_IMAGE_JID ?? DEFAULT_TARGET_JID;
|
|
146
|
-
const caption = (process.env.TEST_E2EE_MEDIA_CAPTION ?? process.env.TEST_E2EE_IMAGE_CAPTION) || undefined;
|
|
147
|
-
const delayMs = parseDelayMs(process.env.TEST_E2EE_MEDIA_DELAY_MS);
|
|
148
|
-
const files = getFilesToSend();
|
|
149
|
-
|
|
150
|
-
if (!existsSync(APPSTATE_PATH)) {
|
|
151
|
-
console.error("send-e2ee-media", `Missing appstate file at ${APPSTATE_PATH}`);
|
|
152
|
-
process.exit(1);
|
|
153
|
-
}
|
|
154
|
-
for (const file of files) {
|
|
155
|
-
if (!existsSync(file.path)) {
|
|
156
|
-
console.error("send-e2ee-media", `Missing test data file at ${file.path}`);
|
|
157
|
-
process.exit(1);
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
if (files.length === 0) {
|
|
161
|
-
console.error("send-e2ee-media", "No files found to send.");
|
|
162
|
-
process.exit(1);
|
|
163
|
-
}
|
|
164
|
-
if (!process.env.FB_E2EE_MEDIA_UPLOAD_AUTH) {
|
|
165
|
-
console.log(
|
|
166
|
-
"send-e2ee-media",
|
|
167
|
-
"FB_E2EE_MEDIA_UPLOAD_AUTH is not set; media upload auth will be requested from media_conn.",
|
|
168
|
-
);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
const client = new FBClient({
|
|
172
|
-
appStatePath: APPSTATE_PATH,
|
|
173
|
-
sessionStorePath: SESSION_PATH,
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
client.onEvent((event: MessengerEvent) => {
|
|
177
|
-
if (event.type === "error") console.error("send-e2ee-media", "Client error:", event.data.message);
|
|
178
|
-
if (event.type === "e2ee_connected") console.log("send-e2ee-media", "E2EE connected.");
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
try {
|
|
182
|
-
console.log("send-e2ee-media", "Connecting to Messenger...");
|
|
183
|
-
const { userId } = await client.connect();
|
|
184
|
-
const selfUserId = parseMessengerUserId(userId);
|
|
185
|
-
console.log("send-e2ee-media", `Connected as User ID: ${selfUserId}`);
|
|
186
|
-
|
|
187
|
-
const userDevicePath = join(ROOT_DIR, `device-${selfUserId}.json`);
|
|
188
|
-
const finalDevicePath = existsSync(userDevicePath) ? userDevicePath : DEVICE_PATH;
|
|
189
|
-
|
|
190
|
-
console.log("send-e2ee-media", `Connecting E2EE stream using: ${finalDevicePath}`);
|
|
191
|
-
await client.connectE2EE(finalDevicePath, selfUserId);
|
|
192
|
-
|
|
193
|
-
console.log(
|
|
194
|
-
"send-e2ee-media",
|
|
195
|
-
`Sending ${files.length} file(s) to ${targetJid}; delay=${delayMs}ms: ${files.map((file) => file.name).join(", ")}`,
|
|
196
|
-
);
|
|
197
|
-
|
|
198
|
-
for (const [index, file] of files.entries()) {
|
|
199
|
-
const size = statSync(file.path).size;
|
|
200
|
-
console.log(
|
|
201
|
-
"send-e2ee-media",
|
|
202
|
-
`[${index + 1}/${files.length}] Sending ${file.name} (${file.kind}, ${file.mimeType ?? `auto:${file.detectedMimeType}`}, ${size} bytes)...`,
|
|
203
|
-
);
|
|
204
|
-
|
|
205
|
-
const result = await sendFileByKind(client, targetJid, file, caption);
|
|
206
|
-
console.log("send-e2ee-media", `[${index + 1}/${files.length}] Send completed: ${JSON.stringify(result)}`);
|
|
207
|
-
|
|
208
|
-
if (index < files.length - 1 && delayMs > 0) {
|
|
209
|
-
console.log("send-e2ee-media", `Waiting ${delayMs}ms before next file...`);
|
|
210
|
-
await sleep(delayMs);
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
await sleep(1_000);
|
|
215
|
-
await client.disconnect();
|
|
216
|
-
process.exit(0);
|
|
217
|
-
} catch (err) {
|
|
218
|
-
console.error("send-e2ee-media", "Error:", err);
|
|
219
|
-
await client.disconnect().catch(() => undefined);
|
|
220
|
-
process.exit(1);
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
main().catch((err) => {
|
|
225
|
-
console.error("send-e2ee-media", "Fatal:", err);
|
|
226
|
-
process.exit(1);
|
|
227
|
-
});
|
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
import { dirname, join } from "node:path";
|
|
2
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
3
|
-
import { fileURLToPath } from "node:url";
|
|
4
|
-
import { FBClient } from "../../src/index.ts";
|
|
5
|
-
import type { MessengerEvent } from "../../src/models/domain.ts";
|
|
6
|
-
|
|
7
|
-
const SCRIPT_DIR = dirname(fileURLToPath(import.meta.url));
|
|
8
|
-
const ROOT_DIR = join(SCRIPT_DIR, "..", "..");
|
|
9
|
-
|
|
10
|
-
const APPSTATE_PATH = join(ROOT_DIR, "tests/appstate.json");
|
|
11
|
-
const SESSION_PATH = join(ROOT_DIR, "tests/session.json");
|
|
12
|
-
const DEVICE_PATH = join(ROOT_DIR, "tests/device.json");
|
|
13
|
-
const ENV_PATH = join(ROOT_DIR, "tests/.env");
|
|
14
|
-
|
|
15
|
-
// Defaults from the sample e2ee_message in the request.
|
|
16
|
-
const DEFAULT_THREAD_ID = "1805602490133470@g.us";
|
|
17
|
-
const DEFAULT_MESSAGE_ID = "7456658723671758234";
|
|
18
|
-
const DEFAULT_TARGET_SENDER_JID = "100042415119261.145@msgr";
|
|
19
|
-
const DEFAULT_REACTION = "👍";
|
|
20
|
-
|
|
21
|
-
function loadEnvFile(filePath: string): void {
|
|
22
|
-
if (!existsSync(filePath)) return;
|
|
23
|
-
const raw = readFileSync(filePath, "utf8");
|
|
24
|
-
for (const line of raw.split(/\r?\n/)) {
|
|
25
|
-
const trimmed = line.trim();
|
|
26
|
-
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
27
|
-
const idx = trimmed.indexOf("=");
|
|
28
|
-
if (idx <= 0) continue;
|
|
29
|
-
const key = trimmed.slice(0, idx).trim();
|
|
30
|
-
let value = trimmed.slice(idx + 1).trim();
|
|
31
|
-
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
|
|
32
|
-
value = value.slice(1, -1);
|
|
33
|
-
}
|
|
34
|
-
process.env[key] = value;
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function parseMessengerUserId(value: string): string {
|
|
39
|
-
const userPart = value.split("@")[0] ?? value;
|
|
40
|
-
const dotIdx = userPart.indexOf(".");
|
|
41
|
-
const colonIdx = userPart.indexOf(":");
|
|
42
|
-
const cuts = [dotIdx, colonIdx].filter((idx) => idx >= 0).sort((a, b) => a - b);
|
|
43
|
-
const end = cuts[0] ?? userPart.length;
|
|
44
|
-
return userPart.slice(0, end) || value;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
async function main() {
|
|
48
|
-
loadEnvFile(ENV_PATH);
|
|
49
|
-
|
|
50
|
-
const threadId = process.env.TEST_E2EE_REACTION_THREAD_ID ?? DEFAULT_THREAD_ID;
|
|
51
|
-
const messageId = process.env.TEST_E2EE_REACTION_MESSAGE_ID ?? DEFAULT_MESSAGE_ID;
|
|
52
|
-
const senderJid = process.env.TEST_E2EE_REACTION_SENDER_JID ?? DEFAULT_TARGET_SENDER_JID;
|
|
53
|
-
const reaction = process.env.TEST_E2EE_REACTION_EMOJI ?? DEFAULT_REACTION;
|
|
54
|
-
|
|
55
|
-
if (!existsSync(APPSTATE_PATH)) {
|
|
56
|
-
console.error("send-e2ee-reaction", `Missing appstate file at ${APPSTATE_PATH}`);
|
|
57
|
-
process.exit(1);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const client = new FBClient({
|
|
61
|
-
appStatePath: APPSTATE_PATH,
|
|
62
|
-
sessionStorePath: SESSION_PATH,
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
client.onEvent((event: MessengerEvent) => {
|
|
66
|
-
if (event.type === "error") console.error("send-e2ee-reaction", "Client error:", event.data.message);
|
|
67
|
-
if (event.type === "e2ee_connected") console.log("send-e2ee-reaction", "E2EE connected.");
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
try {
|
|
71
|
-
console.log("send-e2ee-reaction", "Connecting to Messenger...");
|
|
72
|
-
const { userId } = await client.connect();
|
|
73
|
-
const selfUserId = parseMessengerUserId(userId);
|
|
74
|
-
console.log("send-e2ee-reaction", `Connected as User ID: ${selfUserId}`);
|
|
75
|
-
|
|
76
|
-
const userDevicePath = join(ROOT_DIR, `device-${selfUserId}.json`);
|
|
77
|
-
const finalDevicePath = existsSync(userDevicePath) ? userDevicePath : DEVICE_PATH;
|
|
78
|
-
|
|
79
|
-
console.log("send-e2ee-reaction", `Connecting E2EE stream using: ${finalDevicePath}`);
|
|
80
|
-
await client.connectE2EE(finalDevicePath, selfUserId);
|
|
81
|
-
|
|
82
|
-
console.log(
|
|
83
|
-
"send-e2ee-reaction",
|
|
84
|
-
`Reacting to message=${messageId} in ${threadId}, targetSender=${senderJid}, emoji=${reaction}`,
|
|
85
|
-
);
|
|
86
|
-
|
|
87
|
-
await client.sendReaction({
|
|
88
|
-
threadId,
|
|
89
|
-
messageId,
|
|
90
|
-
senderJid,
|
|
91
|
-
reaction,
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
console.log("send-e2ee-reaction", "Reaction send completed.");
|
|
95
|
-
await new Promise((resolve) => setTimeout(resolve, 1_000));
|
|
96
|
-
await client.disconnect();
|
|
97
|
-
process.exit(0);
|
|
98
|
-
} catch (err) {
|
|
99
|
-
console.error("send-e2ee-reaction", "Error:", err);
|
|
100
|
-
await client.disconnect().catch(() => undefined);
|
|
101
|
-
process.exit(1);
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
main().catch((err) => {
|
|
106
|
-
console.error("send-e2ee-reaction", "Fatal:", err);
|
|
107
|
-
process.exit(1);
|
|
108
|
-
});
|
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
import { dirname, join } from "node:path";
|
|
2
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
3
|
-
import { fileURLToPath } from "node:url";
|
|
4
|
-
import { FBClient } from "../../src/index.ts";
|
|
5
|
-
import type { MessengerEvent } from "../../src/models/domain.ts";
|
|
6
|
-
|
|
7
|
-
const SCRIPT_DIR = dirname(fileURLToPath(import.meta.url));
|
|
8
|
-
const ROOT_DIR = join(SCRIPT_DIR, "..", "..");
|
|
9
|
-
|
|
10
|
-
const APPSTATE_PATH = join(ROOT_DIR, "tests/appstate.json");
|
|
11
|
-
const SESSION_PATH = join(ROOT_DIR, "tests/session.json");
|
|
12
|
-
const DEVICE_PATH = join(ROOT_DIR, "tests/device.json");
|
|
13
|
-
const ENV_PATH = join(ROOT_DIR, "tests/.env");
|
|
14
|
-
|
|
15
|
-
const DEFAULT_THREAD_ID = "100042415119261.0@msgr";
|
|
16
|
-
const DEFAULT_UNSEND_DELAY_MS = 2_000;
|
|
17
|
-
|
|
18
|
-
function loadEnvFile(filePath: string): void {
|
|
19
|
-
if (!existsSync(filePath)) return;
|
|
20
|
-
const raw = readFileSync(filePath, "utf8");
|
|
21
|
-
for (const line of raw.split(/\r?\n/)) {
|
|
22
|
-
const trimmed = line.trim();
|
|
23
|
-
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
24
|
-
const idx = trimmed.indexOf("=");
|
|
25
|
-
if (idx <= 0) continue;
|
|
26
|
-
const key = trimmed.slice(0, idx).trim();
|
|
27
|
-
let value = trimmed.slice(idx + 1).trim();
|
|
28
|
-
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
|
|
29
|
-
value = value.slice(1, -1);
|
|
30
|
-
}
|
|
31
|
-
process.env[key] = value;
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function parseMessengerUserId(value: string): string {
|
|
36
|
-
const userPart = value.split("@")[0] ?? value;
|
|
37
|
-
const dotIdx = userPart.indexOf(".");
|
|
38
|
-
const colonIdx = userPart.indexOf(":");
|
|
39
|
-
const cuts = [dotIdx, colonIdx].filter((idx) => idx >= 0).sort((a, b) => a - b);
|
|
40
|
-
const end = cuts[0] ?? userPart.length;
|
|
41
|
-
return userPart.slice(0, end) || value;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function sleep(ms: number): Promise<void> {
|
|
45
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function pickMessageId(result: Record<string, unknown>): string {
|
|
49
|
-
const raw = result.messageId ?? result.messageID ?? result.message_id;
|
|
50
|
-
if (typeof raw === "string" && raw.length > 0) return raw;
|
|
51
|
-
if (typeof raw === "number" || typeof raw === "bigint") return String(raw);
|
|
52
|
-
throw new Error(`sendMessage did not return a message id: ${JSON.stringify(result)}`);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
async function main() {
|
|
56
|
-
loadEnvFile(ENV_PATH);
|
|
57
|
-
|
|
58
|
-
const threadId = process.env.TEST_E2EE_UNSEND_THREAD_ID
|
|
59
|
-
?? process.env.TEST_E2EE_MEDIA_JID
|
|
60
|
-
?? DEFAULT_THREAD_ID;
|
|
61
|
-
const delayMs = Number(process.env.TEST_E2EE_UNSEND_DELAY_MS ?? String(DEFAULT_UNSEND_DELAY_MS));
|
|
62
|
-
const text = process.env.TEST_E2EE_UNSEND_TEXT
|
|
63
|
-
?? `unsend smoke ${new Date().toISOString()}`;
|
|
64
|
-
|
|
65
|
-
if (!existsSync(APPSTATE_PATH)) {
|
|
66
|
-
console.error("send-e2ee-unsend", `Missing appstate file at ${APPSTATE_PATH}`);
|
|
67
|
-
process.exit(1);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const client = new FBClient({
|
|
71
|
-
appStatePath: APPSTATE_PATH,
|
|
72
|
-
sessionStorePath: SESSION_PATH,
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
client.onEvent((event: MessengerEvent) => {
|
|
76
|
-
if (event.type === "error") console.error("send-e2ee-unsend", "Client error:", event.data.message);
|
|
77
|
-
if (event.type === "e2ee_connected") console.log("send-e2ee-unsend", "E2EE connected.");
|
|
78
|
-
if (event.type === "e2ee_message") console.log("send-e2ee-unsend", "e2ee_message:", event.data);
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
try {
|
|
82
|
-
console.log("send-e2ee-unsend", "Connecting to Messenger...");
|
|
83
|
-
const { userId } = await client.connect();
|
|
84
|
-
const selfUserId = parseMessengerUserId(userId);
|
|
85
|
-
console.log("send-e2ee-unsend", `Connected as User ID: ${selfUserId}`);
|
|
86
|
-
|
|
87
|
-
const userDevicePath = join(ROOT_DIR, `device-${selfUserId}.json`);
|
|
88
|
-
const finalDevicePath = existsSync(userDevicePath) ? userDevicePath : DEVICE_PATH;
|
|
89
|
-
|
|
90
|
-
console.log("send-e2ee-unsend", `Connecting E2EE stream using: ${finalDevicePath}`);
|
|
91
|
-
await client.connectE2EE(finalDevicePath, selfUserId);
|
|
92
|
-
|
|
93
|
-
console.log("send-e2ee-unsend", `Sending test message to ${threadId}: ${JSON.stringify(text)}`);
|
|
94
|
-
const sendResult = await client.sendMessage({ threadId, text });
|
|
95
|
-
const messageId = pickMessageId(sendResult);
|
|
96
|
-
console.log("send-e2ee-unsend", `Sent messageId=${messageId}. Waiting ${delayMs}ms before unsend...`);
|
|
97
|
-
|
|
98
|
-
await sleep(delayMs);
|
|
99
|
-
await client.unsendMessage(messageId, threadId);
|
|
100
|
-
console.log("send-e2ee-unsend", `Unsend completed for messageId=${messageId}`);
|
|
101
|
-
|
|
102
|
-
await sleep(1_000);
|
|
103
|
-
await client.disconnect();
|
|
104
|
-
process.exit(0);
|
|
105
|
-
} catch (err) {
|
|
106
|
-
console.error("send-e2ee-unsend", "Error:", err);
|
|
107
|
-
await client.disconnect().catch(() => undefined);
|
|
108
|
-
process.exit(1);
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
main().catch((err) => {
|
|
113
|
-
console.error("send-e2ee-unsend", "Fatal:", err);
|
|
114
|
-
process.exit(1);
|
|
115
|
-
});
|
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
import { dirname, join } from "node:path";
|
|
2
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
3
|
-
import { fileURLToPath } from "node:url";
|
|
4
|
-
import { FBClient } from "../../src/index.ts";
|
|
5
|
-
import type { MessengerEvent } from "../../src/models/domain.ts";
|
|
6
|
-
|
|
7
|
-
const SCRIPT_DIR = dirname(fileURLToPath(import.meta.url));
|
|
8
|
-
const ROOT_DIR = join(SCRIPT_DIR, "..", "..");
|
|
9
|
-
|
|
10
|
-
const APPSTATE_PATH = join(ROOT_DIR, "tests/appstate.json");
|
|
11
|
-
const SESSION_PATH = join(ROOT_DIR, "tests/session.json");
|
|
12
|
-
const DEVICE_PATH = join(ROOT_DIR, "tests/device.json");
|
|
13
|
-
const ENV_PATH = join(ROOT_DIR, "tests/.env");
|
|
14
|
-
|
|
15
|
-
const DEFAULT_THREAD_ID = "100042415119261.0@msgr";
|
|
16
|
-
const DEFAULT_TYPING_DURATION_MS = 5_000;
|
|
17
|
-
|
|
18
|
-
function loadEnvFile(filePath: string): void {
|
|
19
|
-
if (!existsSync(filePath)) return;
|
|
20
|
-
const raw = readFileSync(filePath, "utf8");
|
|
21
|
-
for (const line of raw.split(/\r?\n/)) {
|
|
22
|
-
const trimmed = line.trim();
|
|
23
|
-
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
24
|
-
const idx = trimmed.indexOf("=");
|
|
25
|
-
if (idx <= 0) continue;
|
|
26
|
-
const key = trimmed.slice(0, idx).trim();
|
|
27
|
-
let value = trimmed.slice(idx + 1).trim();
|
|
28
|
-
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
|
|
29
|
-
value = value.slice(1, -1);
|
|
30
|
-
}
|
|
31
|
-
process.env[key] = value;
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function parseMessengerUserId(value: string): string {
|
|
36
|
-
const userPart = value.split("@")[0] ?? value;
|
|
37
|
-
const dotIdx = userPart.indexOf(".");
|
|
38
|
-
const colonIdx = userPart.indexOf(":");
|
|
39
|
-
const cuts = [dotIdx, colonIdx].filter((idx) => idx >= 0).sort((a, b) => a - b);
|
|
40
|
-
const end = cuts[0] ?? userPart.length;
|
|
41
|
-
return userPart.slice(0, end) || value;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function sleep(ms: number): Promise<void> {
|
|
45
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
async function main() {
|
|
49
|
-
loadEnvFile(ENV_PATH);
|
|
50
|
-
|
|
51
|
-
const threadId = process.env.TEST_TYPING_THREAD_ID
|
|
52
|
-
?? process.env.TEST_E2EE_UNSEND_THREAD_ID
|
|
53
|
-
?? process.env.TEST_E2EE_MEDIA_JID
|
|
54
|
-
?? DEFAULT_THREAD_ID;
|
|
55
|
-
const durationMs = Number(process.env.TEST_TYPING_DURATION_MS ?? String(DEFAULT_TYPING_DURATION_MS));
|
|
56
|
-
|
|
57
|
-
if (!existsSync(APPSTATE_PATH)) {
|
|
58
|
-
console.error("send-typing", `Missing appstate file at ${APPSTATE_PATH}`);
|
|
59
|
-
process.exit(1);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const client = new FBClient({
|
|
63
|
-
appStatePath: APPSTATE_PATH,
|
|
64
|
-
sessionStorePath: SESSION_PATH,
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
client.onEvent((event: MessengerEvent) => {
|
|
68
|
-
if (event.type === "error") console.error("send-typing", "Client error:", event.data.message);
|
|
69
|
-
if (event.type === "ready") console.log("send-typing", "Client ready.");
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
try {
|
|
73
|
-
console.log("send-typing", "Connecting to Messenger...");
|
|
74
|
-
const { userId } = await client.connect();
|
|
75
|
-
const selfUserId = parseMessengerUserId(userId);
|
|
76
|
-
console.log("send-typing", `Connected as User ID: ${selfUserId}`);
|
|
77
|
-
|
|
78
|
-
const userDevicePath = join(ROOT_DIR, `device-${selfUserId}.json`);
|
|
79
|
-
const finalDevicePath = existsSync(userDevicePath) ? userDevicePath : DEVICE_PATH;
|
|
80
|
-
|
|
81
|
-
console.log("send-typing", `Connecting E2EE stream using: ${finalDevicePath}`);
|
|
82
|
-
await client.connectE2EE(finalDevicePath, selfUserId);
|
|
83
|
-
|
|
84
|
-
console.log("send-typing", `sendTyping true to ${threadId}`);
|
|
85
|
-
await client.sendTyping({ threadId, isTyping: true });
|
|
86
|
-
|
|
87
|
-
console.log("send-typing", `Waiting ${durationMs}ms...`);
|
|
88
|
-
await sleep(durationMs);
|
|
89
|
-
|
|
90
|
-
console.log("send-typing", `sendTyping false to ${threadId}`);
|
|
91
|
-
await client.sendTyping({ threadId, isTyping: false });
|
|
92
|
-
|
|
93
|
-
console.log("send-typing", "Typing test completed.");
|
|
94
|
-
await sleep(500);
|
|
95
|
-
await client.disconnect();
|
|
96
|
-
process.exit(0);
|
|
97
|
-
} catch (err) {
|
|
98
|
-
console.error("send-typing", "Error:", err);
|
|
99
|
-
await client.disconnect().catch(() => undefined);
|
|
100
|
-
process.exit(1);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
main().catch((err) => {
|
|
105
|
-
console.error("send-typing", "Fatal:", err);
|
|
106
|
-
process.exit(1);
|
|
107
|
-
});
|
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
import { dirname, join } from "path";
|
|
2
|
-
import { existsSync, readFileSync } from "fs";
|
|
3
|
-
import { fileURLToPath } from "url";
|
|
4
|
-
import { FBClient } from "../../src/index.ts";
|
|
5
|
-
|
|
6
|
-
const SCRIPT_DIR = dirname(fileURLToPath(import.meta.url));
|
|
7
|
-
const ROOT_DIR = join(SCRIPT_DIR, "..", "..");
|
|
8
|
-
|
|
9
|
-
const APPSTATE_PATH = join(ROOT_DIR, "tests/appstate.json");
|
|
10
|
-
const SESSION_PATH = join(ROOT_DIR, "tests/session.json");
|
|
11
|
-
const DEVICE_PATH = join(ROOT_DIR, "tests/device.json");
|
|
12
|
-
const ENV_PATH = join(ROOT_DIR, "tests/.env");
|
|
13
|
-
|
|
14
|
-
function loadEnvFile(filePath: string): void {
|
|
15
|
-
if (!existsSync(filePath)) return;
|
|
16
|
-
const raw = readFileSync(filePath, "utf8");
|
|
17
|
-
for (const line of raw.split(/\r?\n/)) {
|
|
18
|
-
const trimmed = line.trim();
|
|
19
|
-
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
20
|
-
const idx = trimmed.indexOf("=");
|
|
21
|
-
if (idx <= 0) continue;
|
|
22
|
-
const key = trimmed.slice(0, idx).trim();
|
|
23
|
-
let value = trimmed.slice(idx + 1).trim();
|
|
24
|
-
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
|
|
25
|
-
value = value.slice(1, -1);
|
|
26
|
-
}
|
|
27
|
-
process.env[key] = value;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
async function main() {
|
|
32
|
-
loadEnvFile(ENV_PATH);
|
|
33
|
-
|
|
34
|
-
if (!existsSync(APPSTATE_PATH)) {
|
|
35
|
-
console.error("test-group-send", `Missing appstate file at ${APPSTATE_PATH}`);
|
|
36
|
-
process.exit(1);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const client = new FBClient({
|
|
40
|
-
appStatePath: APPSTATE_PATH,
|
|
41
|
-
sessionStorePath: SESSION_PATH,
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
try {
|
|
45
|
-
console.log("test-group-send", `Connecting to Messenger...`);
|
|
46
|
-
const { userId } = await client.connect();
|
|
47
|
-
console.log("test-group-send", `Connected as User ID: ${userId}`);
|
|
48
|
-
|
|
49
|
-
const userDevicePath = join(ROOT_DIR, `device-${userId}.json`);
|
|
50
|
-
const finalDevicePath = existsSync(userDevicePath) ? userDevicePath : DEVICE_PATH;
|
|
51
|
-
|
|
52
|
-
await client.connectE2EE(finalDevicePath, userId);
|
|
53
|
-
console.log("test-group-send", `E2EE Stream active. Waiting for messages...`);
|
|
54
|
-
|
|
55
|
-
const targetGroupJid = process.env.TEST_GROUP_JID ?? "1805602490133470@g.us";
|
|
56
|
-
const text = process.env.TEST_MESSAGE_TEXT ?? "capture sample from e2ee";
|
|
57
|
-
const echoPromise = new Promise<void>((resolve) => {
|
|
58
|
-
client.onEvent("e2ee_message", (event) => {
|
|
59
|
-
const threadId = (event as any).threadId ?? (event as any).threadID;
|
|
60
|
-
const messageText = (event as any).text ?? (event as any).body;
|
|
61
|
-
const senderId = (event as any).senderId ?? (event as any).senderID;
|
|
62
|
-
|
|
63
|
-
if (threadId !== targetGroupJid) return;
|
|
64
|
-
|
|
65
|
-
console.log(
|
|
66
|
-
"test-group-send",
|
|
67
|
-
`Received group message in ${threadId}: "${messageText}" from ${senderId}`,
|
|
68
|
-
);
|
|
69
|
-
|
|
70
|
-
if (messageText === text) {
|
|
71
|
-
console.log("test-group-send", `✅ RECEIVED echo for our message: "${messageText}"`);
|
|
72
|
-
resolve();
|
|
73
|
-
}
|
|
74
|
-
});
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
console.log("test-group-send", `Sending message to ${targetGroupJid}: "${text}"`);
|
|
78
|
-
|
|
79
|
-
const sendResult = await client.sendMessage({
|
|
80
|
-
threadId: targetGroupJid,
|
|
81
|
-
text: text,
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
console.log("test-group-send", `Message send command completed: ${JSON.stringify(sendResult)}`);
|
|
85
|
-
console.log("test-group-send", `Waiting for the echoed group message to arrive...`);
|
|
86
|
-
|
|
87
|
-
const timeoutPromise = new Promise<void>((_, reject) => {
|
|
88
|
-
setTimeout(() => reject(new Error("Message echo not received within 15s")), 15000);
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
await Promise.race([echoPromise, timeoutPromise]);
|
|
92
|
-
|
|
93
|
-
console.log("test-group-send", `SUCCESS: Message verified in group.`);
|
|
94
|
-
await new Promise(r => setTimeout(r, 1000));
|
|
95
|
-
await client.disconnect();
|
|
96
|
-
process.exit(0);
|
|
97
|
-
|
|
98
|
-
} catch (err) {
|
|
99
|
-
console.error("test-group-send", `Error:`, err);
|
|
100
|
-
await client.disconnect().catch(() => { });
|
|
101
|
-
process.exit(1);
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
main().catch(console.error);
|
package/tests/setup.ts
DELETED