fb-messenger-e2ee 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/DOCS.md +310 -0
  2. package/LICENSE +660 -0
  3. package/README.md +153 -0
  4. package/bun.lock +1014 -0
  5. package/examples/basic-usage.ts +52 -0
  6. package/jest.config.ts +18 -0
  7. package/package.json +56 -0
  8. package/src/config/env.ts +15 -0
  9. package/src/controllers/client.controller.ts +805 -0
  10. package/src/controllers/dgw-handler.ts +171 -0
  11. package/src/controllers/e2ee-handler.ts +832 -0
  12. package/src/controllers/event-mapper.ts +327 -0
  13. package/src/core/client.ts +151 -0
  14. package/src/e2ee/application/e2ee-client.ts +376 -0
  15. package/src/e2ee/application/fanout-planner.ts +79 -0
  16. package/src/e2ee/application/outbound-message-cache.ts +58 -0
  17. package/src/e2ee/application/prekey-maintenance.ts +55 -0
  18. package/src/e2ee/application/retry-manager.ts +156 -0
  19. package/src/e2ee/facebook/facebook-protocol-utils.ts +230 -0
  20. package/src/e2ee/facebook/icdc-payload.ts +31 -0
  21. package/src/e2ee/index.ts +76 -0
  22. package/src/e2ee/internal.ts +27 -0
  23. package/src/e2ee/media/media-crypto.ts +147 -0
  24. package/src/e2ee/media/media-upload.ts +90 -0
  25. package/src/e2ee/message/builders/client-payload.ts +54 -0
  26. package/src/e2ee/message/builders/consumer-application.ts +362 -0
  27. package/src/e2ee/message/builders/message-application.ts +64 -0
  28. package/src/e2ee/message/builders/message-transport.ts +84 -0
  29. package/src/e2ee/message/codecs/protobuf-codecs.ts +101 -0
  30. package/src/e2ee/message/constants.ts +4 -0
  31. package/src/e2ee/message/message-builder.ts +15 -0
  32. package/src/e2ee/message/proto/ArmadilloApplication.proto +281 -0
  33. package/src/e2ee/message/proto/ArmadilloICDC.proto +14 -0
  34. package/src/e2ee/message/proto/ConsumerApplication.proto +232 -0
  35. package/src/e2ee/message/proto/MessageApplication.proto +82 -0
  36. package/src/e2ee/message/proto/MessageTransport.proto +77 -0
  37. package/src/e2ee/message/proto/WACommon.proto +66 -0
  38. package/src/e2ee/message/proto/WAMediaTransport.proto +176 -0
  39. package/src/e2ee/message/proto/proto-writer.ts +76 -0
  40. package/src/e2ee/signal/prekey-manager.ts +140 -0
  41. package/src/e2ee/signal/signal-manager.ts +365 -0
  42. package/src/e2ee/store/device-json.ts +47 -0
  43. package/src/e2ee/store/device-repository.ts +12 -0
  44. package/src/e2ee/store/device-store.ts +538 -0
  45. package/src/e2ee/transport/binary/decoder.ts +180 -0
  46. package/src/e2ee/transport/binary/encoder.ts +143 -0
  47. package/src/e2ee/transport/binary/stanzas.ts +97 -0
  48. package/src/e2ee/transport/binary/tokens.ts +64 -0
  49. package/src/e2ee/transport/binary/wa-binary.ts +8 -0
  50. package/src/e2ee/transport/dgw/dgw-socket.ts +345 -0
  51. package/src/e2ee/transport/noise/noise-handshake.ts +417 -0
  52. package/src/e2ee/transport/noise/noise-socket.ts +230 -0
  53. package/src/index.ts +34 -0
  54. package/src/models/client.ts +26 -0
  55. package/src/models/config.ts +14 -0
  56. package/src/models/domain.ts +299 -0
  57. package/src/models/e2ee.ts +295 -0
  58. package/src/models/media.ts +41 -0
  59. package/src/models/messaging.ts +88 -0
  60. package/src/models/thread.ts +69 -0
  61. package/src/repositories/session.repository.ts +20 -0
  62. package/src/services/auth.service.ts +55 -0
  63. package/src/services/e2ee.service.ts +174 -0
  64. package/src/services/facebook-gateway.service.ts +245 -0
  65. package/src/services/icdc.service.ts +177 -0
  66. package/src/services/media.service.ts +304 -0
  67. package/src/services/messaging.service.ts +28 -0
  68. package/src/services/thread.service.ts +199 -0
  69. package/src/types/advanced-types.ts +61 -0
  70. package/src/types/fca-unofficial.d.ts +212 -0
  71. package/src/types/protocol-types.ts +43 -0
  72. package/src/utils/fca-utils.ts +30 -0
  73. package/src/utils/logger.ts +24 -0
  74. package/src/utils/mime.ts +51 -0
  75. package/tests/.env.example +87 -0
  76. package/tests/data/1x1.png +0 -0
  77. package/tests/data/example.txt +1 -0
  78. package/tests/data/file_example.mp3 +0 -0
  79. package/tests/data/file_example.mp4 +0 -0
  80. package/tests/integration.test.ts +498 -0
  81. package/tests/script/echo-e2ee.ts +174 -0
  82. package/tests/script/send-e2ee-media.ts +227 -0
  83. package/tests/script/send-e2ee-reaction.ts +108 -0
  84. package/tests/script/send-e2ee-unsend.ts +115 -0
  85. package/tests/script/send-typing.ts +107 -0
  86. package/tests/script/test-group-send.ts +105 -0
  87. package/tests/setup.ts +3 -0
  88. package/tests/types.test.ts +57 -0
  89. package/tests/unit/controllers/dgw-handler.test.ts +60 -0
  90. package/tests/unit/controllers/e2ee-handler.test.ts +293 -0
  91. package/tests/unit/controllers/event-mapper.test.ts +252 -0
  92. package/tests/unit/e2ee/application-helpers.test.ts +418 -0
  93. package/tests/unit/e2ee/device-store.test.ts +126 -0
  94. package/tests/unit/e2ee/facebook-protocol-utils.test.ts +134 -0
  95. package/tests/unit/e2ee/media-crypto.test.ts +55 -0
  96. package/tests/unit/e2ee/media-upload.test.ts +60 -0
  97. package/tests/unit/e2ee/message-builder.test.ts +42 -0
  98. package/tests/unit/e2ee/message-builders.test.ts +230 -0
  99. package/tests/unit/e2ee/noise-handshake.test.ts +260 -0
  100. package/tests/unit/e2ee/prekey-manager.test.ts +55 -0
  101. package/tests/unit/e2ee/wa-binary.test.ts +127 -0
  102. package/tests/unit/services/e2ee.service.test.ts +101 -0
  103. package/tests/unit/services/facebook-gateway.service.test.ts +138 -0
  104. package/tests/unit/services/media.service.test.ts +169 -0
  105. package/tests/unit/utils/fca-utils.test.ts +48 -0
  106. package/tsconfig.json +19 -0
package/DOCS.md ADDED
@@ -0,0 +1,310 @@
1
+ # FME - FB Messenger E2EE Documentation
2
+
3
+ This document provides API reference and operational notes for the `FME - FB Messenger E2EE` library.
4
+
5
+ ---
6
+
7
+ ## Core: `FBClient`
8
+
9
+ `FBClient` is the main public entry point.
10
+
11
+ ### Constructor
12
+
13
+ ```typescript
14
+ new FBClient(options: ClientOptions)
15
+ ```
16
+
17
+ **ClientOptions**
18
+
19
+ | Option | Type | Description |
20
+ |---|---|---|
21
+ | `appStatePath` | `string` | Path to Facebook appState/cookies JSON. |
22
+ | `appState` | `any[] \| string` | Optional in-memory appState alternative. |
23
+ | `sessionStorePath` | `string` | Optional path for login/session metadata used by E2EE bootstrap. |
24
+ | `platform` | `"facebook" \| "messenger"` | Login platform hint. Defaults to `"facebook"`. |
25
+
26
+ ---
27
+
28
+ ## Lifecycle
29
+
30
+ ### `connect()`
31
+
32
+ Initializes the minimal `fca-unofficial` login bridge required for appState auth/CAT bootstrap and stores session metadata when configured. It does **not** expose or start plaintext/non-E2EE messaging/listening.
33
+
34
+ ```typescript
35
+ const { userId } = await client.connect();
36
+ ```
37
+
38
+ Returns `Promise<{ userId: string }>`.
39
+
40
+ ### `connectE2EE(deviceStorePath: string, userId: string)`
41
+
42
+ Enables the E2EE Noise/Signal stream. Must be called after `connect()`.
43
+
44
+ ```typescript
45
+ await client.connectE2EE("./device-store.json", userId);
46
+ ```
47
+
48
+ Behavior:
49
+
50
+ 1. Loads the existing `DeviceStore` if `deviceStorePath` exists, otherwise creates one.
51
+ 2. Registers through ICDC only when the store has no `jid_device` yet.
52
+ 3. Performs the Noise handshake with the E2EE websocket.
53
+ 4. Sends presence/priming/passive-state nodes.
54
+ 5. Runs startup prekey sync and starts periodic prekey maintenance.
55
+ 6. Optionally connects DGW if DGW env settings are enabled.
56
+
57
+ ### `disconnect()`
58
+
59
+ Stops heartbeats, periodic prekey maintenance, DGW/E2EE sockets, and the internal auth bridge.
60
+
61
+ ---
62
+
63
+ ## Device Store & Key Maintenance
64
+
65
+ `device-store.json` is the long-lived E2EE device identity and cryptographic state. Keep it persistent between restarts.
66
+
67
+ ### Important fields
68
+
69
+ | Field | Purpose | Rotate automatically? |
70
+ |---|---|---|
71
+ | `noise_key_priv` | Noise handshake private key | No |
72
+ | `identity_key_priv` | Signal identity private key | No |
73
+ | `registration_id` | Signal registration ID | No |
74
+ | `adv_secret_key` | Messenger/WA companion secret | No |
75
+ | `facebook_uuid` | ICDC device UUID | No |
76
+ | `jid_user`, `jid_device` | Registered Messenger E2EE device JID | No |
77
+ | `pre_keys` | Local one-time prekey records | Yes, by upload/refill |
78
+ | `signed_pre_keys` / `signed_pre_key_id` | Signed prekey records | Yes, when uploading fresh prekeys |
79
+ | `sessions` | Signal sessions with devices | Updated by libsignal |
80
+ | `sender_keys` | Group sender-key state from SKDM | Updated when SKDM is received |
81
+
82
+ Do **not** delete the entire device store just because listening stops. Deleting it forces new device registration and loses sessions/sender keys. Prefer reconnecting and letting the prekey maintenance refill server-side prekeys.
83
+
84
+ ### Automatic prekey maintenance
85
+
86
+ The controller checks server-side one-time prekey count after E2EE connect and then periodically.
87
+
88
+ | Env | Default | Description |
89
+ |---|---:|---|
90
+ | `FB_E2EE_PREKEY_SYNC_INTERVAL_MS` | `1800000` | Periodic prekey sync interval in milliseconds. Set `0` to disable. |
91
+ | `FB_E2EE_PREKEY_MIN_COUNT` | `5` | Minimum server prekey count before refill. |
92
+ | `FB_E2EE_PREKEY_UPLOAD_COUNT` | `50` | Number of fresh prekeys uploaded per refill. |
93
+
94
+ This refresh does not change the registered device identity. It only generates/uploads fresh one-time prekeys and a current signed prekey under the existing identity.
95
+
96
+ ### Group sender-key caveat
97
+
98
+ A group `skmsg` needs a matching local `sender_keys` record. If the local sender key for a group/sender is truly missing, the client cannot derive it locally. On retryable decrypt failures, the receive path sends a `receipt type="retry"` with registration/key material to ask the sender/server to resend enough data to rebuild the session/SKDM. When a fresh SKDM arrives, it is processed and stored automatically. For messages sent by this client, a short in-memory retry cache stores the encrypted app payload/franking tag so incoming retry receipts can be answered with a targeted retry message and fresh SKDM without re-registering the device.
99
+
100
+ ---
101
+
102
+ ## E2EE-only public API
103
+
104
+ This package no longer delegates plaintext/non-E2EE actions to `fca-unofficial`.
105
+ Use this package for Messenger E2EE only; use `fca-unofficial` directly in your app for classic/non-E2EE messaging, thread management, history, polls, etc.
106
+
107
+ All send APIs require:
108
+
109
+ 1. `await client.connect()`
110
+ 2. `await client.connectE2EE(deviceStorePath, userId)`
111
+ 3. an E2EE-capable thread identifier (`1234567890`, `1234567890.0@msgr`, or `180...@g.us`)
112
+
113
+ ### `sendMessage(input: SendMessageInput)`
114
+
115
+ Sends an E2EE text message. There is no plaintext FCA fallback.
116
+
117
+ ```typescript
118
+ const sent = await client.sendMessage({
119
+ threadId: "1234567890.0@msgr",
120
+ text: "hello",
121
+ replyToMessageId: "optional-message-id",
122
+ });
123
+ console.log(sent.messageId);
124
+ ```
125
+
126
+ **SendMessageInput**
127
+
128
+ | Field | Type | Description |
129
+ |---|---|---|
130
+ | `threadId` | `string` | Numeric user ID, `@msgr` JID, or group JID. |
131
+ | `text` | `string` | Message body. |
132
+ | `replyToMessageId` | `string` | Optional replied message ID. |
133
+
134
+ ### `sendReaction(input: SendReactionInput)`
135
+
136
+ Sends an E2EE reaction. For group messages from someone else, pass `senderJid` so the target `MessageKey` is encoded correctly.
137
+
138
+ ```typescript
139
+ await client.sendReaction({
140
+ threadId: "1805602490133470@g.us",
141
+ messageId: "7456658723671758234",
142
+ senderJid: "100042415119261.145@msgr",
143
+ reaction: "👍",
144
+ });
145
+ ```
146
+
147
+ ### `unsendMessage(messageId: string, threadId?: string)`
148
+
149
+ Un-sends/revokes an E2EE message you previously sent. Pass `threadId` unless the original send result is still in the short outbound cache.
150
+
151
+ ```typescript
152
+ await client.unsendMessage(sent.messageId, "1234567890.0@msgr");
153
+ ```
154
+
155
+ ### `sendTyping(input: TypingInput)`
156
+
157
+ Sends E2EE chatstate (`composing` / `paused`) over the Noise socket.
158
+
159
+ ```typescript
160
+ await client.sendTyping({ threadId: "1234567890.0@msgr", isTyping: true });
161
+ await new Promise(resolve => setTimeout(resolve, 5000));
162
+ await client.sendTyping({ threadId: "1234567890.0@msgr", isTyping: false });
163
+ ```
164
+
165
+ ---
166
+
167
+ ## E2EE Media Handling
168
+
169
+ ### `sendImage` / `sendVideo` / `sendAudio` / `sendFile`
170
+
171
+ These helpers encrypt, upload, and send E2EE media for one-to-one Messenger E2EE chats. MIME type is inferred from `fileName` when omitted. Group E2EE media send is not implemented yet.
172
+
173
+ ```typescript
174
+ await client.sendImage({
175
+ threadId: "1234567890.0@msgr",
176
+ data: imageBuffer,
177
+ fileName: "image.jpg",
178
+ caption: "optional caption",
179
+ });
180
+ ```
181
+
182
+ ---
183
+
184
+ ## Event Handling
185
+
186
+ ### Catch-all listener
187
+
188
+ ```typescript
189
+ client.onEvent((event) => {
190
+ console.log(event.type, event.data);
191
+ });
192
+ ```
193
+
194
+ ### Typed listener
195
+
196
+ ```typescript
197
+ client.onEvent("e2ee_message", (msg) => {
198
+ console.log(msg.threadId, msg.chatJid, msg.senderJid, msg.kind, msg.text);
199
+ });
200
+ ```
201
+
202
+ ### E2EE message event shape
203
+
204
+ `e2ee_message` uses `type + data` like other events, but `data` separates conversation identity from sender device identity:
205
+
206
+ ```typescript
207
+ {
208
+ type: "e2ee_message",
209
+ data: {
210
+ id: "7456191609143713633",
211
+ threadId: "100042415119261",
212
+ chatJid: "100042415119261.0@msgr",
213
+ senderJid: "100042415119261.160@msgr",
214
+ senderId: "100042415119261",
215
+ senderDeviceId: 160,
216
+ isGroup: false,
217
+ kind: "text",
218
+ text: "Hehe",
219
+ timestampMs: 1777694609888,
220
+ },
221
+ }
222
+ ```
223
+
224
+ For group messages, `threadId` and `chatJid` stay as the group JID, while `senderJid` remains the actual sender device JID. Optional fields such as `attachments`, `mentions`, and `replyTo` are omitted when empty.
225
+
226
+ Common event types:
227
+
228
+ - `message`
229
+ - `messageEdit`
230
+ - `reaction`
231
+ - `typing`
232
+ - `message_unsend`
233
+ - `read_receipt`
234
+ - `presence`
235
+ - `e2ee_connected`
236
+ - `e2ee_message`
237
+ - `e2ee_reaction`
238
+ - `e2ee_receipt`
239
+ - `disconnected`
240
+ - `reconnected`
241
+ - `ready`
242
+ - `raw`
243
+ - `error`
244
+
245
+ `error` is also routed through the catch-all `event` channel. The internal emitter avoids Node's unhandled `error` event crash when no typed error listener is registered.
246
+
247
+ ---
248
+
249
+ ## E2EE Technical Details
250
+
251
+ ### Internal module layout
252
+
253
+ - `src/e2ee/application`: `E2EEClient`, retry manager, prekey maintenance, outbound retry cache, fanout/JID helpers.
254
+ - `src/e2ee/store`: `DeviceStore`, JSON schema/migration helpers, file repository.
255
+ - `src/e2ee/transport`: Noise socket/handshake, WA-binary encoder/decoder/stanzas, optional DGW socket.
256
+ - `src/e2ee/signal`: Signal sessions, prekeys, sender-key group cipher helpers.
257
+ - `src/e2ee/message`: `ProtoWriter`, client/consumer/application/transport builders, protobuf codecs/schemas.
258
+ - `src/e2ee/media` and `src/e2ee/facebook`: media crypto/upload and Facebook-specific protocol helpers.
259
+
260
+ Legacy `src/e2ee/*.ts` shim files still re-export the new modules for compatibility.
261
+
262
+ ### Receive path
263
+
264
+ 1. `FacebookE2EESocket` decrypts Noise frames.
265
+ 2. `ClientController` unmarshals WA-binary nodes.
266
+ 3. `E2EEHandler` ACKs, processes participant SKDM/direct participant payloads, decrypts `msg` / `pkmsg` / `skmsg`, decodes protobuf payloads, and emits normalized events.
267
+ 4. Retryable decrypt failures emit an `error` event and send an E2EE retry receipt rather than terminating the listener loop.
268
+
269
+ ### Send path
270
+
271
+ - DM: build MessageTransport, establish/fetch sessions when needed, fan out encrypted device payloads.
272
+ - Group: fetch participants/devices, build group `skmsg`, distribute `skdm` to devices through `<participants>`, include `phash`, `franking`, and `trace` nodes.
273
+ - Outgoing E2EE payloads are cached briefly by `OutboundMessageCache`; `E2EERetryManager` uses that cache to respond to `receipt type="retry"` without re-registering the device.
274
+
275
+ ### Requirements
276
+
277
+ - A valid `appState` / cookies file.
278
+ - A persistent device store JSON.
279
+ - No plaintext fallback for E2EE send failures.
280
+
281
+ ---
282
+
283
+ ## Environment Variables
284
+
285
+ | Env | Default | Description |
286
+ |---|---|---|
287
+ | `FB_APPSTATE_PATH` | `./data/appstate.json` | AppState/cookies path used by env helpers/examples. |
288
+ | `FB_SESSION_STORE_PATH` | `./data/session.json` | Non-E2EE session metadata path. |
289
+ | `FB_PLATFORM` | `facebook` | Platform hint. |
290
+ | `DEBUG` / `NODE_ENV=development` | off | Enables debug logger output. |
291
+ | `FB_E2EE_PREKEY_SYNC_INTERVAL_MS` | `1800000` | Periodic E2EE prekey sync interval. |
292
+ | `FB_E2EE_PREKEY_MIN_COUNT` | `5` | Minimum server prekey count before refill. |
293
+ | `FB_E2EE_PREKEY_UPLOAD_COUNT` | `50` | Fresh prekeys uploaded per refill. |
294
+ | `FB_DGW_ENABLE` | unset | Enables optional DGW connection when set to `1`. |
295
+
296
+ ---
297
+
298
+ ## Manual Scripts
299
+
300
+ ```bash
301
+ bun run tests/script/echo-e2ee.ts
302
+ ```
303
+
304
+ The echo script keeps the process alive by default. Set `ECHO_EXIT_AFTER_MS` to auto-exit for short manual tests.
305
+
306
+ ---
307
+
308
+ ## Support
309
+
310
+ For bugs and feature requests, please open an issue in the repository.