fb-messenger-e2ee 0.1.1 → 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 +27 -2
- 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,327 +0,0 @@
|
|
|
1
|
-
import { EventEmitter } from "node:events";
|
|
2
|
-
import type { MessengerEvent, MessengerMessage, Attachment, E2EEMessage, E2EEMessageKind } from "../models/domain.ts";
|
|
3
|
-
import { str, num, now } from "../utils/fca-utils.ts";
|
|
4
|
-
import type { MediaService } from "../services/media.service.ts";
|
|
5
|
-
import type { E2EEService } from "../services/e2ee.service.ts";
|
|
6
|
-
|
|
7
|
-
export class EventMapper {
|
|
8
|
-
constructor(
|
|
9
|
-
private readonly eventBus: EventEmitter,
|
|
10
|
-
private readonly mediaService: MediaService,
|
|
11
|
-
private readonly e2eeService: E2EEService
|
|
12
|
-
) {}
|
|
13
|
-
|
|
14
|
-
public emitMappedEvent(rawEvent: Record<string, unknown>): void {
|
|
15
|
-
const type = str(rawEvent.type);
|
|
16
|
-
|
|
17
|
-
// Standard LightSpeed messages
|
|
18
|
-
if (type === "message" || type === "message_reply") {
|
|
19
|
-
const msg: MessengerMessage = {
|
|
20
|
-
id: str(rawEvent.messageID),
|
|
21
|
-
threadId: str(rawEvent.threadID),
|
|
22
|
-
senderId: str(rawEvent.senderID),
|
|
23
|
-
text: str(rawEvent.body),
|
|
24
|
-
timestampMs: num(rawEvent.timestamp) || now(),
|
|
25
|
-
attachments: this.mapAttachments(rawEvent.attachments),
|
|
26
|
-
mentions: this.mapMentions(rawEvent),
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
const reply = rawEvent.messageReply as Record<string, unknown> | undefined;
|
|
30
|
-
if (reply?.messageID) {
|
|
31
|
-
msg.replyTo = {
|
|
32
|
-
messageId: str(reply.messageID),
|
|
33
|
-
senderId: str(reply.senderID),
|
|
34
|
-
text: str(reply.body),
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
this.emit({ type: "message", data: msg });
|
|
39
|
-
return;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// Message edit
|
|
43
|
-
if (type === "message_edit" || type === "messageEdit") {
|
|
44
|
-
this.emit({
|
|
45
|
-
type: "messageEdit",
|
|
46
|
-
data: {
|
|
47
|
-
messageId: str(rawEvent.messageID),
|
|
48
|
-
threadId: str(rawEvent.threadID),
|
|
49
|
-
newText: str(rawEvent.newText ?? rawEvent.text ?? rawEvent.body),
|
|
50
|
-
editCount: num(rawEvent.editCount),
|
|
51
|
-
timestampMs: num(rawEvent.timestamp) || now(),
|
|
52
|
-
},
|
|
53
|
-
});
|
|
54
|
-
return;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// Reactions
|
|
58
|
-
if (type === "message_reaction" || type === "reaction") {
|
|
59
|
-
this.emit({
|
|
60
|
-
type: "reaction",
|
|
61
|
-
data: {
|
|
62
|
-
messageId: str(rawEvent.messageID),
|
|
63
|
-
threadId: str(rawEvent.threadID),
|
|
64
|
-
actorId: str(rawEvent.userID ?? rawEvent.senderID),
|
|
65
|
-
reaction: str(rawEvent.reaction),
|
|
66
|
-
timestampMs: num(rawEvent.timestamp) || now(),
|
|
67
|
-
},
|
|
68
|
-
});
|
|
69
|
-
return;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// Typing
|
|
73
|
-
if (type === "typ") {
|
|
74
|
-
this.emit({
|
|
75
|
-
type: "typing",
|
|
76
|
-
data: {
|
|
77
|
-
threadId: str(rawEvent.threadID),
|
|
78
|
-
senderId: str(rawEvent.from ?? rawEvent.senderID),
|
|
79
|
-
isTyping: Boolean(rawEvent.isTyping),
|
|
80
|
-
},
|
|
81
|
-
});
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// Unsend
|
|
86
|
-
if (type === "message_unsend") {
|
|
87
|
-
this.emit({
|
|
88
|
-
type: "message_unsend",
|
|
89
|
-
data: {
|
|
90
|
-
messageId: str(rawEvent.messageID),
|
|
91
|
-
threadId: str(rawEvent.threadID),
|
|
92
|
-
actorId: str(rawEvent.senderID),
|
|
93
|
-
timestampMs: num(rawEvent.timestamp) || now(),
|
|
94
|
-
},
|
|
95
|
-
});
|
|
96
|
-
return;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// Read receipt
|
|
100
|
-
if (type === "read_receipt") {
|
|
101
|
-
this.emit({
|
|
102
|
-
type: "read_receipt",
|
|
103
|
-
data: {
|
|
104
|
-
threadId: str(rawEvent.threadID),
|
|
105
|
-
readerId: str(rawEvent.reader ?? rawEvent.readerID),
|
|
106
|
-
readWatermarkTimestampMs: num(rawEvent.readWatermarkTimestampMs),
|
|
107
|
-
timestampMs: num(rawEvent.time ?? rawEvent.timestamp) || now(),
|
|
108
|
-
},
|
|
109
|
-
});
|
|
110
|
-
return;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// Presence
|
|
114
|
-
if (type === "presence") {
|
|
115
|
-
this.emit({
|
|
116
|
-
type: "presence",
|
|
117
|
-
data: {
|
|
118
|
-
userId: str(rawEvent.userID),
|
|
119
|
-
isOnline: Boolean(rawEvent.userStatus ?? rawEvent.isOnline),
|
|
120
|
-
lastActiveTimestampMs: num(rawEvent.timestamp),
|
|
121
|
-
},
|
|
122
|
-
});
|
|
123
|
-
return;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// Handshake events
|
|
127
|
-
if (type === "disconnected") {
|
|
128
|
-
this.emit({ type: "disconnected", data: { isE2EE: Boolean(rawEvent.isE2EE) } });
|
|
129
|
-
return;
|
|
130
|
-
}
|
|
131
|
-
if (type === "reconnected") {
|
|
132
|
-
this.emit({ type: "reconnected", data: {} });
|
|
133
|
-
return;
|
|
134
|
-
}
|
|
135
|
-
if (type === "ready") {
|
|
136
|
-
this.emit({ type: "ready", data: { isNewSession: Boolean(rawEvent.isNewSession) } });
|
|
137
|
-
return;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// E2EE specific
|
|
141
|
-
if (type === "e2ee_connected" || type === "e2eeConnected") {
|
|
142
|
-
this.e2eeService.markConnected();
|
|
143
|
-
this.emit({ type: "e2ee_connected", data: {} });
|
|
144
|
-
return;
|
|
145
|
-
}
|
|
146
|
-
if (type === "e2ee_message" || type === "e2eeMessage") {
|
|
147
|
-
const e2eeData = rawEvent.data as any;
|
|
148
|
-
if (!e2eeData) return;
|
|
149
|
-
|
|
150
|
-
const rawChatJid = str(e2eeData.chatJid || e2eeData.threadId);
|
|
151
|
-
const senderInfo = this.parseMessengerJid(str(e2eeData.senderJid));
|
|
152
|
-
const senderId = str(e2eeData.senderId) || senderInfo.user || senderInfo.rawUser || senderInfo.jid;
|
|
153
|
-
const senderJid = this.canonicalMessengerDeviceJid(str(e2eeData.senderJid), senderId, senderInfo.device);
|
|
154
|
-
const chat = this.normalizeE2EEChat(rawChatJid || senderJid);
|
|
155
|
-
|
|
156
|
-
if (e2eeData.type === "decryption_failed") {
|
|
157
|
-
this.emit({
|
|
158
|
-
type: "error",
|
|
159
|
-
data: {
|
|
160
|
-
message: `E2EE decrypt failed${chat.chatJid ? ` in ${chat.chatJid}` : ""}${senderJid ? ` from ${senderJid}` : ""}: ${str(e2eeData.error)}`,
|
|
161
|
-
},
|
|
162
|
-
});
|
|
163
|
-
return;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
const kind = this.normalizeE2EEKind(e2eeData.kind || e2eeData.type, e2eeData);
|
|
167
|
-
const data: Record<string, unknown> = {
|
|
168
|
-
id: str(e2eeData.messageId || e2eeData.messageID),
|
|
169
|
-
threadId: chat.threadId,
|
|
170
|
-
chatJid: chat.chatJid,
|
|
171
|
-
senderJid,
|
|
172
|
-
senderId,
|
|
173
|
-
isGroup: chat.isGroup,
|
|
174
|
-
kind,
|
|
175
|
-
text: str(e2eeData.text || e2eeData.body || ""),
|
|
176
|
-
timestampMs: num(e2eeData.timestampMs || e2eeData.timestamp) || now(),
|
|
177
|
-
};
|
|
178
|
-
|
|
179
|
-
if (senderInfo.device > 0) data.senderDeviceId = senderInfo.device;
|
|
180
|
-
if (Array.isArray(e2eeData.attachments) && e2eeData.attachments.length > 0) data.attachments = e2eeData.attachments;
|
|
181
|
-
if (Array.isArray(e2eeData.mentions) && e2eeData.mentions.length > 0) data.mentions = e2eeData.mentions;
|
|
182
|
-
if (e2eeData.media) data.media = e2eeData.media;
|
|
183
|
-
if (e2eeData.raw) data.raw = e2eeData.raw;
|
|
184
|
-
if (e2eeData.emoji || e2eeData.reaction) data.reaction = str(e2eeData.emoji || e2eeData.reaction);
|
|
185
|
-
if (e2eeData.targetId) data.targetId = str(e2eeData.targetId);
|
|
186
|
-
if (typeof e2eeData.fromMe === "boolean") data.fromMe = e2eeData.fromMe;
|
|
187
|
-
|
|
188
|
-
if (e2eeData.replyToId) {
|
|
189
|
-
const replySender = this.parseMessengerJid(str(e2eeData.replyToSenderJid));
|
|
190
|
-
data.replyTo = {
|
|
191
|
-
messageId: str(e2eeData.replyToId),
|
|
192
|
-
senderId: replySender.user || str(e2eeData.replyToSenderJid),
|
|
193
|
-
};
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
this.emit({
|
|
197
|
-
type: "e2ee_message",
|
|
198
|
-
data: data as unknown as E2EEMessage,
|
|
199
|
-
});
|
|
200
|
-
return;
|
|
201
|
-
}
|
|
202
|
-
if (type === "e2ee_reaction" || type === "e2eeReaction") {
|
|
203
|
-
const d = rawEvent.data as Record<string, unknown> | undefined ?? rawEvent;
|
|
204
|
-
this.emit({
|
|
205
|
-
type: "e2ee_reaction",
|
|
206
|
-
data: {
|
|
207
|
-
messageId: str(d.messageId),
|
|
208
|
-
chatJid: str(d.chatJid),
|
|
209
|
-
senderJid: str(d.senderJid),
|
|
210
|
-
senderId: str(d.senderId),
|
|
211
|
-
reaction: str(d.reaction),
|
|
212
|
-
},
|
|
213
|
-
});
|
|
214
|
-
return;
|
|
215
|
-
}
|
|
216
|
-
if (type === "e2ee_receipt" || type === "e2eeReceipt") {
|
|
217
|
-
const d = rawEvent.data as Record<string, unknown> | undefined ?? rawEvent;
|
|
218
|
-
this.emit({
|
|
219
|
-
type: "e2ee_receipt",
|
|
220
|
-
data: {
|
|
221
|
-
type: str(d.type),
|
|
222
|
-
chat: str(d.chat),
|
|
223
|
-
sender: str(d.sender),
|
|
224
|
-
messageIds: Array.isArray(d.messageIds) ? (d.messageIds as unknown[]).map(str) : [],
|
|
225
|
-
},
|
|
226
|
-
});
|
|
227
|
-
return;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
// Raw fallback
|
|
231
|
-
this.emit({ type: "raw", data: rawEvent });
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
private normalizeE2EEKind(value: unknown, data: Record<string, unknown>): E2EEMessageKind {
|
|
235
|
-
const kind = str(value);
|
|
236
|
-
if (this.isE2EEMessageKind(kind)) return kind;
|
|
237
|
-
if (data.media && typeof data.media === "object" && data.media !== null) {
|
|
238
|
-
const mediaType = str((data.media as Record<string, unknown>).type);
|
|
239
|
-
if (this.isE2EEMessageKind(mediaType)) return mediaType;
|
|
240
|
-
}
|
|
241
|
-
if (str(data.text || data.body)) return "text";
|
|
242
|
-
return "unknown";
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
private isE2EEMessageKind(value: string): value is E2EEMessageKind {
|
|
246
|
-
return ["text", "image", "video", "audio", "document", "sticker", "reaction", "edit", "revoke", "unknown"].includes(value);
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
private normalizeE2EEChat(jid: string): { threadId: string; chatJid: string; isGroup: boolean } {
|
|
250
|
-
if (!jid) return { threadId: "", chatJid: "", isGroup: false };
|
|
251
|
-
if (this.isGroupJid(jid)) return { threadId: jid, chatJid: jid, isGroup: true };
|
|
252
|
-
|
|
253
|
-
const parsed = this.parseMessengerJid(jid);
|
|
254
|
-
if (parsed.server === "msgr" && parsed.user) {
|
|
255
|
-
return {
|
|
256
|
-
threadId: parsed.user,
|
|
257
|
-
chatJid: `${parsed.user}.0@msgr`,
|
|
258
|
-
isGroup: false,
|
|
259
|
-
};
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
if (/^\d+$/.test(jid)) {
|
|
263
|
-
return { threadId: jid, chatJid: `${jid}.0@msgr`, isGroup: false };
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
return { threadId: jid, chatJid: jid, isGroup: false };
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
private isGroupJid(jid: string): boolean {
|
|
270
|
-
return jid.endsWith("@g.us") || jid.includes(".g.");
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
private canonicalMessengerDeviceJid(jid: string, fallbackUser: string, fallbackDevice = 0): string {
|
|
274
|
-
const parsed = this.parseMessengerJid(jid);
|
|
275
|
-
if (parsed.server === "msgr" && parsed.user) return `${parsed.user}.${parsed.device}@msgr`;
|
|
276
|
-
if (!jid && fallbackUser) return `${fallbackUser}.${fallbackDevice}@msgr`;
|
|
277
|
-
return jid;
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
private parseMessengerJid(jid: string): { jid: string; rawUser: string; user: string; device: number; server: string } {
|
|
281
|
-
const [userPart = jid, server = ""] = jid.split("@");
|
|
282
|
-
const colonIdx = userPart.indexOf(":");
|
|
283
|
-
const dotIdx = userPart.indexOf(".");
|
|
284
|
-
const userEnd = dotIdx !== -1 ? dotIdx : (colonIdx !== -1 ? colonIdx : userPart.length);
|
|
285
|
-
const user = userPart.slice(0, userEnd) || userPart;
|
|
286
|
-
const rawDevice = colonIdx !== -1
|
|
287
|
-
? userPart.slice(colonIdx + 1)
|
|
288
|
-
: (dotIdx !== -1 ? userPart.slice(dotIdx + 1) : "0");
|
|
289
|
-
return { jid, rawUser: userPart, user, device: Number(rawDevice) || 0, server };
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
public emit(event: MessengerEvent): void {
|
|
293
|
-
// Node's EventEmitter treats "error" specially: emitting it without a
|
|
294
|
-
// typed error listener throws ERR_UNHANDLED_ERROR and can kill long-running
|
|
295
|
-
// listeners such as tests/script/echo-e2ee.ts. We still always emit the
|
|
296
|
-
// catch-all "event" below, so client.onEvent(listener) receives the error.
|
|
297
|
-
if (event.type !== "error" || this.eventBus.listenerCount("error") > 0) {
|
|
298
|
-
this.eventBus.emit(event.type as any, event.data);
|
|
299
|
-
}
|
|
300
|
-
this.eventBus.emit("event", event);
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
private mapAttachments(raw: unknown): Attachment[] | undefined {
|
|
304
|
-
if (!Array.isArray(raw) || raw.length === 0) return undefined;
|
|
305
|
-
const mapped = (raw as unknown[])
|
|
306
|
-
.map(item => this.mediaService.normalizeAttachment(item))
|
|
307
|
-
.filter((item): item is Attachment => item !== null);
|
|
308
|
-
return mapped.length > 0 ? mapped : undefined;
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
private mapMentions(rawEvent: Record<string, unknown>) {
|
|
312
|
-
const mentions = rawEvent.mentions;
|
|
313
|
-
if (!Array.isArray(mentions) || mentions.length === 0) return undefined;
|
|
314
|
-
return (mentions as unknown[]).flatMap(m => {
|
|
315
|
-
if (typeof m !== "object" || m === null) return [];
|
|
316
|
-
const item = m as Record<string, unknown>;
|
|
317
|
-
return [
|
|
318
|
-
{
|
|
319
|
-
userId: str(item.id ?? item.userId),
|
|
320
|
-
offset: num(item.fromIndex ?? item.offset),
|
|
321
|
-
length: num(item.length),
|
|
322
|
-
type: str(item.type) || "user",
|
|
323
|
-
},
|
|
324
|
-
];
|
|
325
|
-
});
|
|
326
|
-
}
|
|
327
|
-
}
|
package/src/core/client.ts
DELETED
|
@@ -1,151 +0,0 @@
|
|
|
1
|
-
import type { MessengerEvent } from "../models/domain.ts";
|
|
2
|
-
import type {
|
|
3
|
-
SendMediaInput,
|
|
4
|
-
SendMessageInput,
|
|
5
|
-
SendReactionInput,
|
|
6
|
-
TypingInput,
|
|
7
|
-
} from "../models/messaging.ts";
|
|
8
|
-
import type { ClientOptions, MessengerEventMap } from "../models/client.ts";
|
|
9
|
-
import { TypedEventEmitter } from "../types/advanced-types.ts";
|
|
10
|
-
import { ClientController } from "../controllers/client.controller.ts";
|
|
11
|
-
import { FileSessionRepository } from "../repositories/session.repository.ts";
|
|
12
|
-
import { AuthService } from "../services/auth.service.ts";
|
|
13
|
-
import { E2EEService } from "../services/e2ee.service.ts";
|
|
14
|
-
import { FacebookGatewayService } from "../services/facebook-gateway.service.ts";
|
|
15
|
-
import { MediaService } from "../services/media.service.ts";
|
|
16
|
-
import { ICDCService } from "../services/icdc.service.ts";
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* E2EE-only Messenger client facade.
|
|
20
|
-
*
|
|
21
|
-
* `fca-unofficial` is still used internally for app-state login and CAT/bootstrap
|
|
22
|
-
* material that the E2EE transport currently requires, but plaintext/non-E2EE
|
|
23
|
-
* messaging, thread management, polls, stickers, and history APIs are intentionally
|
|
24
|
-
* not exposed here. Use `fca-unofficial` directly for those non-E2EE surfaces.
|
|
25
|
-
*/
|
|
26
|
-
export class FBClient {
|
|
27
|
-
private readonly eventBus = new TypedEventEmitter<MessengerEventMap>();
|
|
28
|
-
private readonly controller: ClientController;
|
|
29
|
-
|
|
30
|
-
public constructor(private readonly options: ClientOptions) {
|
|
31
|
-
const sessionRepository = new FileSessionRepository();
|
|
32
|
-
const authService = new AuthService(sessionRepository);
|
|
33
|
-
const gateway = new FacebookGatewayService();
|
|
34
|
-
const mediaService = new MediaService(gateway);
|
|
35
|
-
const e2eeService = new E2EEService();
|
|
36
|
-
|
|
37
|
-
const icdcService = new ICDCService(
|
|
38
|
-
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
|
|
39
|
-
);
|
|
40
|
-
this.controller = new ClientController(
|
|
41
|
-
authService,
|
|
42
|
-
gateway,
|
|
43
|
-
mediaService,
|
|
44
|
-
e2eeService,
|
|
45
|
-
icdcService,
|
|
46
|
-
this.eventBus as any,
|
|
47
|
-
);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// Events
|
|
51
|
-
|
|
52
|
-
/** Listen for events. Supports catch-all or specific event types. */
|
|
53
|
-
public onEvent(listener: (event: MessengerEvent) => void): void;
|
|
54
|
-
public onEvent<K extends keyof MessengerEventMap>(
|
|
55
|
-
event: K,
|
|
56
|
-
listener: (data: MessengerEventMap[K]) => void,
|
|
57
|
-
): void;
|
|
58
|
-
public onEvent(arg1: any, arg2?: any): void {
|
|
59
|
-
if (typeof arg1 === "function") {
|
|
60
|
-
this.eventBus.on("event", arg1);
|
|
61
|
-
} else {
|
|
62
|
-
this.eventBus.on(arg1, arg2);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/** Stop listening for events. */
|
|
67
|
-
public offEvent(listener: (event: MessengerEvent) => void): void;
|
|
68
|
-
public offEvent<K extends keyof MessengerEventMap>(
|
|
69
|
-
event: K,
|
|
70
|
-
listener: (data: MessengerEventMap[K]) => void,
|
|
71
|
-
): void;
|
|
72
|
-
public offEvent(arg1: any, arg2?: any): void {
|
|
73
|
-
if (typeof arg1 === "function") {
|
|
74
|
-
this.eventBus.off("event", arg1);
|
|
75
|
-
} else {
|
|
76
|
-
this.eventBus.off(arg1, arg2);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/** Legacy helper for the catch-all wrapper event. */
|
|
81
|
-
public onAnyEvent(listener: (event: MessengerEvent) => void): void {
|
|
82
|
-
(this.eventBus as any).on("event", listener);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// Lifecycle
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Login with appState and prepare auth/CAT bootstrap state.
|
|
89
|
-
* This does not start plaintext/non-E2EE MQTT listening.
|
|
90
|
-
*/
|
|
91
|
-
public async connect(): Promise<{ userId: string }> {
|
|
92
|
-
return this.controller.connect(
|
|
93
|
-
{
|
|
94
|
-
appStatePath: this.options.appStatePath,
|
|
95
|
-
appState: this.options.appState,
|
|
96
|
-
platform: this.options.platform ?? "facebook",
|
|
97
|
-
},
|
|
98
|
-
this.options.sessionStorePath,
|
|
99
|
-
);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
public async disconnect(): Promise<void> {
|
|
103
|
-
await this.controller.disconnect();
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// E2EE lifecycle
|
|
107
|
-
|
|
108
|
-
public async connectE2EE(deviceStorePath: string, userId: string): Promise<void> {
|
|
109
|
-
await this.controller.connectE2EE(deviceStorePath, userId);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
public async sendNoiseKeepAlive(): Promise<void> {
|
|
113
|
-
await this.controller.sendNoiseKeepAlive();
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// E2EE messaging
|
|
117
|
-
|
|
118
|
-
public async sendMessage(input: SendMessageInput): Promise<Record<string, unknown>> {
|
|
119
|
-
return this.controller.sendMessage(input);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
public async sendReaction(input: SendReactionInput): Promise<void> {
|
|
123
|
-
await this.controller.sendReaction(input);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
public async unsendMessage(messageId: string, threadId?: string): Promise<void> {
|
|
127
|
-
await this.controller.unsendMessage(messageId, threadId);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
public async sendTyping(input: TypingInput): Promise<void> {
|
|
131
|
-
await this.controller.sendTyping(input);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// E2EE media send
|
|
135
|
-
|
|
136
|
-
public async sendImage(input: SendMediaInput): Promise<Record<string, unknown>> {
|
|
137
|
-
return this.controller.sendImage(input);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
public async sendVideo(input: SendMediaInput): Promise<Record<string, unknown>> {
|
|
141
|
-
return this.controller.sendVideo(input);
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
public async sendAudio(input: SendMediaInput): Promise<Record<string, unknown>> {
|
|
145
|
-
return this.controller.sendAudio(input);
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
public async sendFile(input: SendMediaInput): Promise<Record<string, unknown>> {
|
|
149
|
-
return this.controller.sendFile(input);
|
|
150
|
-
}
|
|
151
|
-
}
|