@xdevplatform/chat-xdk 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.
package/README.md ADDED
@@ -0,0 +1,102 @@
1
+ # chat-xdk JavaScript/WASM bindings
2
+
3
+ JavaScript/WASM bindings for the X Chat SDK — end-to-end encryption for X Direct Messages.
4
+
5
+ ## Architecture
6
+
7
+ ```
8
+ JavaScript (chat-xdk) → wasm-bindgen → Rust WASM (chat_xdk_wasm) → chat-xdk-core
9
+ ```
10
+
11
+ The Rust WASM layer is a pure crypto engine; Juicebox key-storage lifecycle is orchestrated in the JS wrapper (`index.js`).
12
+
13
+ ## Prerequisites
14
+
15
+ - **Node.js 18+**
16
+ - [wasm-pack](https://rustwasm.github.io/wasm-pack/installer/)
17
+
18
+ ## Setup
19
+
20
+ ### 1. Build chat-xdk WASM
21
+
22
+ ```bash
23
+ cd crates/wasm
24
+ wasm-pack build --target nodejs --out-dir pkg # or --target web for browser bundlers
25
+ ```
26
+
27
+ ### 2. Build juicebox-sdk WASM
28
+
29
+ `juicebox-sdk` is not on npm — it must be built from source.
30
+ Clone [juicebox-sdk](https://github.com/juicebox-systems/juicebox-sdk) as a sibling of `chat-xdk`:
31
+
32
+ ```bash
33
+ git clone https://github.com/juicebox-systems/juicebox-sdk.git ../juicebox-sdk
34
+ cd ../juicebox-sdk/rust/sdk/bridge/wasm
35
+ wasm-pack build --target nodejs --out-dir pkg --out-name juicebox-sdk
36
+ ```
37
+
38
+ The `--out-name juicebox-sdk` flag is required so the output files match the names
39
+ that `index.js` imports (`juicebox-sdk.js`, `juicebox-sdk_bg.js`, `juicebox-sdk_bg.wasm`).
40
+ Use the same `--target` for both builds (`nodejs` for Node, `web` or `bundler` for browsers).
41
+
42
+ ### 3. Install dependencies
43
+
44
+ ```bash
45
+ cd crates/wasm/js
46
+ npm install
47
+ npm install ../../juicebox-sdk/rust/sdk/bridge/wasm/pkg # links juicebox-sdk
48
+ ```
49
+
50
+ ## Quick Start
51
+
52
+ ```typescript
53
+ import { createChat } from "chat-xdk";
54
+
55
+ const chat = await createChat({
56
+ juiceboxConfig: configJson,
57
+ getAuthToken: async (realmId) => await myBackend.getToken(realmId),
58
+ });
59
+ await chat.unlock("2580");
60
+
61
+ // Initial load — batch decrypt with automatic key extraction
62
+ const result = chat.decryptEvents(rawEvents, [
63
+ { userId: "111", publicKeyVersion: "v1", publicKey: "BASE64..." },
64
+ { userId: "222", publicKeyVersion: "v1", publicKey: "BASE64..." },
65
+ ]);
66
+
67
+ for (const dm of result.messages) {
68
+ if (dm.event.type === "message") {
69
+ console.log(dm.event.senderId, dm.event.content.text);
70
+ }
71
+ }
72
+
73
+ // Cache keys, then use decryptEvent for individual events
74
+ const cachedKeys = result.conversationKeys.keys;
75
+ const event = chat.decryptEvent(webhookEventB64, cachedKeys, senderSigningKeys);
76
+ ```
77
+
78
+ ## API
79
+
80
+ See the **JS** column of the unified tables in [`docs/API.md`](../../../docs/API.md) for the full method list, parameters, and return types.
81
+
82
+ ## Security limitations
83
+
84
+ The underlying protocol provides **no forward secrecy and no post-compromise
85
+ security**: compromise of an identity private key exposes all conversation
86
+ keys ever encrypted to that public key — and therefore all past and future
87
+ messages in those conversations. Key rotation does not retroactively protect
88
+ messages encrypted under a previous key. See
89
+ [docs/CRYPTO.md — Known Limitations](../../../docs/CRYPTO.md#known-limitations).
90
+
91
+ ### Key export is not exposed in the browser build
92
+
93
+ Raw private-key export and import are not part of this binding's public API.
94
+ `exportKeys()` would return unencrypted private key material to page
95
+ JavaScript, where any script that can reach the chat instance — including code
96
+ injected via XSS or a compromised dependency — could exfiltrate the identity
97
+ permanently. Keys are managed inside the Juicebox layer (`setup`/`unlock`)
98
+ instead, so raw key bytes never cross into application JavaScript.
99
+
100
+ ## License
101
+
102
+ MIT
package/index.d.ts ADDED
@@ -0,0 +1,558 @@
1
+ /**
2
+ * X Chat SDK - TypeScript Definitions
3
+ */
4
+
5
+ /**
6
+ * Public keys returned from getPublicKeys().
7
+ */
8
+ export interface PublicKeys {
9
+ /** Base64-encoded identity public key */
10
+ identity: string;
11
+ /** Base64-encoded signing public key */
12
+ signing: string;
13
+ }
14
+
15
+ /**
16
+ * Public key registration fields for the X API.
17
+ */
18
+ export interface PublicKeyRegistration {
19
+ identityPublicKeySignature: string;
20
+ publicKey: string;
21
+ publicKeyFingerprint?: string;
22
+ registrationMethod: string;
23
+ signingPublicKey: string;
24
+ signingPublicKeySignature?: string;
25
+ }
26
+
27
+ /**
28
+ * Public key registration payload for the X API.
29
+ */
30
+ export interface PublicKeyRegistrationPayload {
31
+ publicKey: PublicKeyRegistration;
32
+ version?: string;
33
+ generateVersion: boolean;
34
+ }
35
+
36
+ /**
37
+ * Result from encrypt*ForApi methods.
38
+ */
39
+ export interface SendPayload {
40
+ /** Base64-encoded Thrift MessageCreateEvent. */
41
+ encryptedContent: string;
42
+ /** Base64-encoded ECDSA signature. */
43
+ signature: string;
44
+ /** Base64-encoded Thrift MessageEventSignature. */
45
+ encodedEventSignature: string;
46
+ /** Signature metadata (signing key version + signature protocol version). */
47
+ signatureInfo: { publicKeyVersion: string; signatureVersion: string };
48
+ /** Version of the conversation key used to encrypt the content (a snowflake/timestamp string). */
49
+ conversationKeyVersion: string;
50
+ /** Whether the X API should send a push notification for this message. */
51
+ shouldNotify: boolean;
52
+ }
53
+
54
+ /**
55
+ * Result from signAddMembers / signKeyChange.
56
+ */
57
+ export interface ActionSignature {
58
+ messageId: string;
59
+ encodedMessageEventDetail: string;
60
+ signature: string;
61
+ signatureVersion: string;
62
+ publicKeyVersion: string;
63
+ signaturePayload: string;
64
+ }
65
+
66
+ /**
67
+ * A recipient for encryptConversationKeyForRecipients.
68
+ */
69
+ export interface RecipientInput {
70
+ /** The recipient's user ID (snowflake string). */
71
+ userId: string;
72
+ /** Base64-encoded identity public key (SEC1 or SPKI). */
73
+ publicKey: string;
74
+ /** Key version string from the X API. */
75
+ keyVersion: string;
76
+ }
77
+
78
+ /**
79
+ * A signing key entry for decryptEvent and decryptEvents.
80
+ * Pass all known signing keys for all participants; the SDK matches by userId + version.
81
+ */
82
+ export interface SigningKeyEntry {
83
+ /** The participant's user ID (snowflake string). */
84
+ userId: string;
85
+ /** Signing key version from the X API (a snowflake/timestamp string). */
86
+ publicKeyVersion: string;
87
+ /** Base64-encoded signing public key (SEC1 or SPKI). */
88
+ publicKey: string;
89
+ /** Base64-encoded identity public key for this user. */
90
+ identityPublicKey: string;
91
+ /** Base64-encoded raw r||s signature proving the signing key is bound to the identity key. Returned by the X API. */
92
+ identityPublicKeySignature: string;
93
+ }
94
+
95
+ /** Map of key version → raw conversation key bytes. */
96
+ export type ConversationKeyMap = { [version: string]: Uint8Array };
97
+
98
+ /**
99
+ * Result from extractConversationKeys.
100
+ * Contains the key map plus the latest version for convenience.
101
+ */
102
+ export interface ConversationKeyResult {
103
+ /** Map of key version → raw conversation key bytes. */
104
+ keys: ConversationKeyMap;
105
+ /** The latest (highest) key version, for encrypting new messages. */
106
+ latestVersion: string | null;
107
+ }
108
+
109
+ /**
110
+ * A single decrypted message from decryptEvents.
111
+ */
112
+ export interface DecryptedMessage {
113
+ /** The decrypted event. */
114
+ event: Event;
115
+ /** Original base64 event string (for reference). */
116
+ originalB64?: string;
117
+ }
118
+
119
+ /**
120
+ * Result from decryptEvents batch API.
121
+ */
122
+ export interface DecryptEventsResult {
123
+ /** Successfully decrypted messages. */
124
+ messages: DecryptedMessage[];
125
+ /** Extracted conversation keys (for caching). */
126
+ conversationKeys: ConversationKeyResult;
127
+ /** Errors encountered during decryption (index → error message). */
128
+ errors: { [index: number]: string };
129
+ }
130
+
131
+ /**
132
+ * Image dimensions returned by detectImageDimensions.
133
+ */
134
+ export interface ImageDimensions {
135
+ width: number;
136
+ height: number;
137
+ }
138
+
139
+ /**
140
+ * Encrypted key for a single recipient (from encryptConversationKeyForRecipients).
141
+ */
142
+ export interface EncryptedKeyForRecipient {
143
+ userId: string;
144
+ encryptedKey: string;
145
+ publicKeyVersion: string;
146
+ }
147
+
148
+ /**
149
+ * A public key input for prepareConversationKeys.
150
+ *
151
+ * Represents a single public key version for a user, as returned by the
152
+ * X API `GET /2/dm/encryption/public_keys` endpoint.
153
+ *
154
+ * Pass the full array — the SDK groups by userId and picks the latest
155
+ * version per user automatically.
156
+ */
157
+ export interface PublicKeyInput {
158
+ /** The user's user ID (snowflake string). */
159
+ userId: string;
160
+ /** Base64-encoded identity public key (SEC1 or SPKI). */
161
+ publicKey: string;
162
+ /** Key version string from the X API. */
163
+ keyVersion: string;
164
+ }
165
+
166
+ /**
167
+ * Result from prepareConversationKeys.
168
+ *
169
+ * Contains everything needed to POST to the X API for group creation
170
+ * or key initialization — no further transformation required.
171
+ */
172
+ export interface PreparedConversationKeys {
173
+ /** Raw conversation key bytes (32 bytes). Store locally for encrypting messages. */
174
+ conversationKey: Uint8Array;
175
+ /** Timestamp-based version string for this conversation key. */
176
+ conversationKeyVersion: string;
177
+ /** Encrypted keys for each participant, ready to POST to the API. */
178
+ participantKeys: EncryptedKeyForRecipient[];
179
+ }
180
+
181
+ /**
182
+ * Entity descriptor tuple: [startByteOffset, endByteOffset, entityType].
183
+ * entityType is one of: "url", "mention", "hashtag", "cashtag", "email", "address", "phoneNumber".
184
+ */
185
+ export type EntityTuple = [number, number, string];
186
+
187
+ // NOTE: AttachmentDescriptor uses snake_case as it's part of the Thrift protocol
188
+ // and matches the wire format used by the X API
189
+ /**
190
+ * Attachment descriptor object.
191
+ */
192
+ export type AttachmentDescriptor =
193
+ | { attachment_type: 'media'; media_hash_key: string; width: number; height: number; filesize_bytes: number; filename: string; media_type?: number; duration_millis?: number }
194
+ | { attachment_type: 'url'; url: string; display_title?: string }
195
+ | { attachment_type: 'post'; rest_id?: string; post_url?: string };
196
+
197
+ /**
198
+ * Decrypted event from the SDK.
199
+ * Meta fields (sequenceId, id, senderId, etc.) are flattened to top level.
200
+ */
201
+ export interface Event {
202
+ /** Discriminates which event variant this is. */
203
+ type: 'message' | 'keyChange' | 'typing' | 'readReceipt' | 'groupChange' |
204
+ 'messageDeleted' | 'memberDeleted' | 'conversationDeleted' |
205
+ 'settingsChange' | 'markedUnread' | 'failure' | 'unknown';
206
+ /** Flattened from meta: unique sequence ID for ordering. */
207
+ sequenceId?: string;
208
+ /** Flattened from meta: unique message/event ID (snowflake string). */
209
+ id?: string;
210
+ /** Flattened from meta: sender's user ID (snowflake string). */
211
+ senderId?: string;
212
+ /** Flattened from meta: conversation ID (snowflake string). */
213
+ conversationId?: string;
214
+ /** Flattened from meta: event creation timestamp in milliseconds. */
215
+ createdAtMsec?: number;
216
+ /** For message events: the decrypted message content. */
217
+ content?: MessageContent;
218
+ /** Whether the event signature was verified. */
219
+ verified?: boolean;
220
+ /** For message events: attachments included with the message (if any). */
221
+ attachments?: AttachmentInfo[];
222
+ /** For message events: media hash keys derived from attachments (if any). */
223
+ mediaHashes?: MediaHashReference[];
224
+ /** For keyChange events: the new conversation key version (a snowflake/timestamp string). */
225
+ keyVersion?: string;
226
+ /** For keyChange events: encrypted conversation keys, one per participant. */
227
+ participantKeys?: ParticipantKey[];
228
+ /** Index signature for additional event-specific fields. */
229
+ [key: string]: any;
230
+ }
231
+
232
+ /**
233
+ * Decrypted message content. Permissive convenience shape: `contentType`
234
+ * discriminates which fields are present. For the precisely-typed discriminated
235
+ * union, import `MessageContent` from the package's `./types` entry.
236
+ */
237
+ export interface MessageContent {
238
+ /**
239
+ * Discriminates the content kind: 'text', 'reaction', 'reactionRemoved',
240
+ * 'edit', 'markRead', 'markUnread', or 'unknown'.
241
+ */
242
+ contentType?: string;
243
+ /** Present on 'text' content. */
244
+ text?: string;
245
+ /** Present on 'edit' content. */
246
+ newText?: string;
247
+ /** Present on 'reaction' and 'reactionRemoved' content. */
248
+ emoji?: string;
249
+ /** Present on 'reaction', 'reactionRemoved', and 'edit' content. */
250
+ targetMessageId?: string;
251
+ /** Present on 'text' content: rich-text entities. */
252
+ entities?: unknown[];
253
+ /** Present on 'text' content: message attachments. */
254
+ attachments?: unknown[];
255
+ /** Present on 'text' content: reply-to preview. */
256
+ replyingToPreview?: unknown;
257
+ /** Present on 'text' content: forwarded message metadata. */
258
+ forwardedMessage?: unknown;
259
+ /** Present on 'text' content: the surface the message was sent from. */
260
+ sentFrom?: string | number;
261
+ /** Present on 'text' content: quick-reply payload. */
262
+ quickReply?: unknown;
263
+ /** Present on 'text' content: call-to-action buttons. */
264
+ ctas?: unknown[];
265
+ /** Present on 'unknown' content: the unrecognized numeric type id. */
266
+ typeId?: number;
267
+ /** Index signature for additional content-specific fields. */
268
+ [key: string]: any;
269
+ }
270
+
271
+ export interface ParticipantKey {
272
+ userId: string;
273
+ encryptedKey: string;
274
+ publicKeyVersion: string;
275
+ }
276
+
277
+ export interface MediaHashReference {
278
+ source: string;
279
+ mediaHashKey: string;
280
+ }
281
+
282
+ /**
283
+ * An attachment on a decrypted message. Permissive convenience shape: the
284
+ * fields are flattened and `attachmentType` discriminates which apply. For the
285
+ * precisely-typed per-variant union, import `AttachmentInfo` from the package's
286
+ * `./types` entry.
287
+ */
288
+ export interface AttachmentInfo {
289
+ /** One of: 'media', 'url', 'post', 'unifiedCard', 'money'. */
290
+ attachmentType?: string;
291
+ mediaHashKey?: string;
292
+ dimensions?: { width?: number; height?: number };
293
+ mediaType?: string;
294
+ durationMillis?: number;
295
+ filesizeBytes?: number;
296
+ filename?: string;
297
+ attachmentId?: string;
298
+ legacyMediaUrlHttps?: string;
299
+ legacyMediaPreviewUrl?: string;
300
+ url?: string;
301
+ bannerImageMediaHashKey?: string;
302
+ faviconImageMediaHashKey?: string;
303
+ displayTitle?: string;
304
+ restId?: string;
305
+ postUrl?: string;
306
+ fallbackText?: string;
307
+ /** Index signature for additional attachment-specific fields. */
308
+ [key: string]: any;
309
+ }
310
+
311
+ /**
312
+ * Options for creating a Chat instance with Juicebox integration.
313
+ */
314
+ export interface CreateChatOptions {
315
+ /**
316
+ * Juicebox configuration JSON from the X API.
317
+ * Obtain this from: GET /2/dm/encryption/juicebox/config
318
+ */
319
+ juiceboxConfig: string;
320
+
321
+ /**
322
+ * Async function to get a Juicebox auth token for a realm.
323
+ *
324
+ * @param realmId - Hex-encoded realm ID
325
+ * @returns Promise resolving to the auth token string
326
+ *
327
+ * @example
328
+ * async (realmId) => {
329
+ * const response = await fetch(`/api/juicebox/token?realm=${realmId}`, {
330
+ * headers: { Authorization: `Bearer ${userToken}` }
331
+ * });
332
+ * return response.text();
333
+ * }
334
+ */
335
+ getAuthToken: (realmId: string) => Promise<string>;
336
+
337
+ /**
338
+ * Optional number of PIN guesses allowed before lockout.
339
+ */
340
+ maxGuessCount?: number;
341
+ }
342
+
343
+ /**
344
+ * Common interface for all Chat methods (shared by Chat and ChatWithJuicebox).
345
+ *
346
+ * The raw WASM `Chat` class provides only crypto operations.
347
+ * `ChatWithJuicebox` (returned by `createChat()`) adds Juicebox lifecycle methods.
348
+ */
349
+ interface ChatCrypto {
350
+ /** When enabled, `decryptEvent` throws for unverified messages. */
351
+ setRejectUnverified(reject: boolean): void;
352
+
353
+ /** Generate new keypairs and return the registration payload. */
354
+ generateKeypairs(): PublicKeyRegistrationPayload;
355
+
356
+ /** Get current public keys. */
357
+ getPublicKeys(): PublicKeys;
358
+
359
+ /** Get the fingerprint of the loaded identity public key for out-of-band verification. */
360
+ getPublicKeyFingerprint(): string;
361
+
362
+ /** Returns true when both identity and signing keys are loaded. */
363
+ isUnlocked(): boolean;
364
+
365
+ /** Returns true when the identity key is loaded (sufficient for decryption). */
366
+ hasIdentityKey(): boolean;
367
+
368
+ /** Clear keys from memory. */
369
+ lock(): void;
370
+
371
+ /** Decrypt an encrypted conversation key (ECIES). */
372
+ decryptConversationKey(encryptedKeyB64: string): Uint8Array;
373
+
374
+ /** Generate a new conversation key. */
375
+ generateConversationKey(): Uint8Array;
376
+
377
+ /** Extract and decrypt conversation keys from raw KeyChange event strings. */
378
+ extractConversationKeys(events: string[]): ConversationKeyResult;
379
+
380
+ /**
381
+ * Decrypt multiple events in batch (recommended API).
382
+ *
383
+ * This method:
384
+ * 1. Extracts conversation keys from any KeyChange events
385
+ * 2. For each message, finds the correct signing key by matching userId + version
386
+ * 3. Decrypts the message using the appropriate conversation key
387
+ *
388
+ * @param events - Raw base64-encoded event strings from the webhook
389
+ * @param signingKeys - All known signing keys for all participants (with userId)
390
+ */
391
+ decryptEvents(events: string[], signingKeys?: SigningKeyEntry[]): DecryptEventsResult;
392
+
393
+ /** Decrypt a raw webhook event payload. */
394
+ decryptEvent(eventB64: string, conversationKeys: ConversationKeyMap, signingKeys?: SigningKeyEntry[]): Event;
395
+
396
+ /** Sign data. Returns raw signature bytes (64 bytes). */
397
+ sign(data: Uint8Array): Uint8Array;
398
+
399
+ /** Verify a signature. */
400
+ verify(publicKeyB64: string, signature: Uint8Array, data: Uint8Array): boolean;
401
+
402
+ /** Encrypt a text message. */
403
+ encryptMessage(messageId: string, senderId: string, conversationId: string, conversationKey: Uint8Array, text: string, conversationKeyVersion: string, signingKeyVersion: string, entities?: EntityTuple[] | null, attachments?: AttachmentDescriptor[] | null, shouldNotify?: boolean | null, ttlMsec?: number | null): SendPayload;
404
+
405
+ /** Encrypt a reply. */
406
+ encryptReply(messageId: string, senderId: string, conversationId: string, conversationKey: Uint8Array, text: string, conversationKeyVersion: string, signingKeyVersion: string, replyToSequenceId: string, replyToSenderId?: number | null, replyToText?: string | null, entities?: EntityTuple[] | null, attachments?: AttachmentDescriptor[] | null, replyToEntities?: EntityTuple[] | null, replyToAttachments?: AttachmentDescriptor[] | null, shouldNotify?: boolean | null, ttlMsec?: number | null): SendPayload;
407
+
408
+ /** Encrypt a reaction-add. */
409
+ encryptAddReaction(messageId: string, senderId: string, conversationId: string, conversationKey: Uint8Array, targetMessageSequenceId: string, emoji: string, conversationKeyVersion: string, signingKeyVersion: string): SendPayload;
410
+
411
+ /** Encrypt a reaction-remove. */
412
+ encryptRemoveReaction(messageId: string, senderId: string, conversationId: string, conversationKey: Uint8Array, targetMessageSequenceId: string, emoji: string, conversationKeyVersion: string, signingKeyVersion: string): SendPayload;
413
+
414
+ /** Encrypt a stream (e.g. media). */
415
+ encryptStream(plaintext: Uint8Array, conversationKey: Uint8Array): Uint8Array;
416
+
417
+ /** Decrypt a streaming-encrypted payload (e.g. media). */
418
+ decryptStream(encrypted: Uint8Array, conversationKey: Uint8Array): Uint8Array;
419
+
420
+ /** Encrypt a UTF-8 string and return base64 ciphertext. Use for metadata fields like group names. */
421
+ encrypt(plaintext: string, conversationKey: Uint8Array): string;
422
+
423
+ /** Decrypt a base64-encoded ciphertext and return the UTF-8 plaintext. Use for metadata fields like group names. */
424
+ decrypt(ciphertextB64: string, conversationKey: Uint8Array): string;
425
+
426
+ /** Encrypt a conversation key for one or more recipients. */
427
+ encryptConversationKeyForRecipients(conversationKey: Uint8Array, recipients: RecipientInput[]): EncryptedKeyForRecipient[];
428
+
429
+ /**
430
+ * Prepare conversation keys for all participants in one call.
431
+ *
432
+ * This is the recommended method for both group creation and key
433
+ * initialization. Pass the flat array of public keys from the X API;
434
+ * the SDK groups by userId, picks the latest version per user,
435
+ * generates a fresh conversation key, and encrypts it for everyone.
436
+ *
437
+ * @param publicKeys - Array of PublicKeyInput from the X API
438
+ * @returns PreparedConversationKeys ready to POST to the API
439
+ */
440
+ prepareConversationKeys(publicKeys: PublicKeyInput[]): PreparedConversationKeys;
441
+
442
+ /** Build and sign a GroupMemberAdd action signature. */
443
+ signAddMembers(publicKeyVersion: string, messageId: string, senderId: string, conversationId: string, newMemberIds: string[], currentMemberIds: string[], currentAdminIds: string[], currentPendingMemberIds: string[], conversationKeyVersion: string, currentTitle?: string | null, currentAvatarUrl?: string | null, currentTtlMsec?: number | null): ActionSignature;
444
+
445
+ /** Build and sign a ConversationKeyChange action signature. */
446
+ signKeyChange(publicKeyVersion: string, messageId: string, senderId: string, conversationId: string, conversationKeyVersion: string, conversationKey: Uint8Array): ActionSignature;
447
+ }
448
+
449
+ /** Internal crypto engine wrapped by ChatWithJuicebox. Not part of the public API. */
450
+ declare class Chat implements ChatCrypto {
451
+ constructor();
452
+
453
+ setRejectUnverified(reject: boolean): void;
454
+ generateKeypairs(): PublicKeyRegistrationPayload;
455
+ /** Set the registered public key version (call after unlock()). */
456
+ setKeyVersion(version: string): void;
457
+ getPublicKeys(): PublicKeys;
458
+ getPublicKeyFingerprint(): string;
459
+ isUnlocked(): boolean;
460
+ hasIdentityKey(): boolean;
461
+ lock(): void;
462
+ decryptConversationKey(encryptedKeyB64: string): Uint8Array;
463
+ generateConversationKey(): Uint8Array;
464
+ extractConversationKeys(events: string[]): ConversationKeyResult;
465
+ decryptEvents(events: string[], signingKeys?: SigningKeyEntry[]): DecryptEventsResult;
466
+ decryptEvent(eventB64: string, conversationKeys: ConversationKeyMap, signingKeys?: SigningKeyEntry[]): Event;
467
+ sign(data: Uint8Array): Uint8Array;
468
+ verify(publicKeyB64: string, signature: Uint8Array, data: Uint8Array): boolean;
469
+ verifyKeyBinding(identityPublicKeyB64: string, signingPublicKeyB64: string, identityPublicKeySignatureB64: string): boolean;
470
+ encryptMessage(messageId: string, senderId: string, conversationId: string, conversationKey: Uint8Array, text: string, conversationKeyVersion: string, signingKeyVersion: string, entities?: EntityTuple[] | null, attachments?: AttachmentDescriptor[] | null, shouldNotify?: boolean | null, ttlMsec?: number | null): SendPayload;
471
+ encryptReply(messageId: string, senderId: string, conversationId: string, conversationKey: Uint8Array, text: string, conversationKeyVersion: string, signingKeyVersion: string, replyToSequenceId: string, replyToSenderId?: number | null, replyToText?: string | null, entities?: EntityTuple[] | null, attachments?: AttachmentDescriptor[] | null, replyToEntities?: EntityTuple[] | null, replyToAttachments?: AttachmentDescriptor[] | null, shouldNotify?: boolean | null, ttlMsec?: number | null): SendPayload;
472
+ encryptAddReaction(messageId: string, senderId: string, conversationId: string, conversationKey: Uint8Array, targetMessageSequenceId: string, emoji: string, conversationKeyVersion: string, signingKeyVersion: string): SendPayload;
473
+ encryptRemoveReaction(messageId: string, senderId: string, conversationId: string, conversationKey: Uint8Array, targetMessageSequenceId: string, emoji: string, conversationKeyVersion: string, signingKeyVersion: string): SendPayload;
474
+ encryptStream(plaintext: Uint8Array, conversationKey: Uint8Array): Uint8Array;
475
+ decryptStream(encrypted: Uint8Array, conversationKey: Uint8Array): Uint8Array;
476
+ encrypt(plaintext: string, conversationKey: Uint8Array): string;
477
+ decrypt(ciphertextB64: string, conversationKey: Uint8Array): string;
478
+ encryptConversationKeyForRecipients(conversationKey: Uint8Array, recipients: RecipientInput[]): EncryptedKeyForRecipient[];
479
+ prepareConversationKeys(publicKeys: PublicKeyInput[]): PreparedConversationKeys;
480
+ signAddMembers(publicKeyVersion: string, messageId: string, senderId: string, conversationId: string, newMemberIds: string[], currentMemberIds: string[], currentAdminIds: string[], currentPendingMemberIds: string[], conversationKeyVersion: string, currentTitle?: string | null, currentAvatarUrl?: string | null, currentTtlMsec?: number | null): ActionSignature;
481
+ signKeyChange(publicKeyVersion: string, messageId: string, senderId: string, conversationId: string, conversationKeyVersion: string, conversationKey: Uint8Array): ActionSignature;
482
+ }
483
+
484
+ /**
485
+ * Chat with integrated Juicebox key storage (returned by `createChat()`).
486
+ *
487
+ * Key material is managed inside the Juicebox layer.
488
+ */
489
+ export declare class ChatWithJuicebox implements ChatCrypto {
490
+ /**
491
+ * Register keys with Juicebox. The PIN must meet strength requirements
492
+ * (4+ characters, not a single repeated character or sequential digit
493
+ * run). Pass a Uint8Array to be able to zero the buffer afterwards.
494
+ */
495
+ setup(pin: string | Uint8Array): Promise<PublicKeys>;
496
+ unlock(pin: string | Uint8Array): Promise<void>;
497
+ delete(): Promise<void>;
498
+ /** The new PIN must meet the same strength requirements as setup(). */
499
+ changePin(oldPin: string | Uint8Array, newPin: string | Uint8Array): Promise<void>;
500
+ updateConfig(juiceboxConfig: string): void;
501
+
502
+ setRejectUnverified(reject: boolean): void;
503
+ generateKeypairs(): PublicKeyRegistrationPayload;
504
+ /** Set the registered public key version (call after unlock()). */
505
+ setKeyVersion(version: string): void;
506
+ getPublicKeys(): PublicKeys;
507
+ getPublicKeyFingerprint(): string;
508
+ isUnlocked(): boolean;
509
+ hasIdentityKey(): boolean;
510
+ lock(): void;
511
+ decryptConversationKey(encryptedKeyB64: string): Uint8Array;
512
+ generateConversationKey(): Uint8Array;
513
+ extractConversationKeys(events: string[]): ConversationKeyResult;
514
+ decryptEvents(events: string[], signingKeys?: SigningKeyEntry[]): DecryptEventsResult;
515
+ decryptEvent(eventB64: string, conversationKeys: ConversationKeyMap, signingKeys?: SigningKeyEntry[]): Event;
516
+ sign(data: Uint8Array): Uint8Array;
517
+ verify(publicKeyB64: string, signature: Uint8Array, data: Uint8Array): boolean;
518
+ verifyKeyBinding(identityPublicKeyB64: string, signingPublicKeyB64: string, identityPublicKeySignatureB64: string): boolean;
519
+ encryptMessage(messageId: string, senderId: string, conversationId: string, conversationKey: Uint8Array, text: string, conversationKeyVersion: string, signingKeyVersion: string, entities?: EntityTuple[] | null, attachments?: AttachmentDescriptor[] | null, shouldNotify?: boolean | null, ttlMsec?: number | null): SendPayload;
520
+ encryptReply(messageId: string, senderId: string, conversationId: string, conversationKey: Uint8Array, text: string, conversationKeyVersion: string, signingKeyVersion: string, replyToSequenceId: string, replyToSenderId?: number | null, replyToText?: string | null, entities?: EntityTuple[] | null, attachments?: AttachmentDescriptor[] | null, replyToEntities?: EntityTuple[] | null, replyToAttachments?: AttachmentDescriptor[] | null, shouldNotify?: boolean | null, ttlMsec?: number | null): SendPayload;
521
+ encryptAddReaction(messageId: string, senderId: string, conversationId: string, conversationKey: Uint8Array, targetMessageSequenceId: string, emoji: string, conversationKeyVersion: string, signingKeyVersion: string): SendPayload;
522
+ encryptRemoveReaction(messageId: string, senderId: string, conversationId: string, conversationKey: Uint8Array, targetMessageSequenceId: string, emoji: string, conversationKeyVersion: string, signingKeyVersion: string): SendPayload;
523
+ encryptStream(plaintext: Uint8Array, conversationKey: Uint8Array): Uint8Array;
524
+ decryptStream(encrypted: Uint8Array, conversationKey: Uint8Array): Uint8Array;
525
+ encrypt(plaintext: string, conversationKey: Uint8Array): string;
526
+ decrypt(ciphertextB64: string, conversationKey: Uint8Array): string;
527
+ encryptConversationKeyForRecipients(conversationKey: Uint8Array, recipients: RecipientInput[]): EncryptedKeyForRecipient[];
528
+ prepareConversationKeys(publicKeys: PublicKeyInput[]): PreparedConversationKeys;
529
+ signAddMembers(publicKeyVersion: string, messageId: string, senderId: string, conversationId: string, newMemberIds: string[], currentMemberIds: string[], currentAdminIds: string[], currentPendingMemberIds: string[], conversationKeyVersion: string, currentTitle?: string | null, currentAvatarUrl?: string | null, currentTtlMsec?: number | null): ActionSignature;
530
+ signKeyChange(publicKeyVersion: string, messageId: string, senderId: string, conversationId: string, conversationKeyVersion: string, conversationKey: Uint8Array): ActionSignature;
531
+ }
532
+
533
+ /**
534
+ * Create a Chat instance with integrated Juicebox key storage.
535
+ *
536
+ * Each call creates an independent instance with its own Juicebox client.
537
+ */
538
+ export declare function createChat(options: CreateChatOptions): Promise<ChatWithJuicebox>;
539
+
540
+ // Utility Functions
541
+
542
+ /** Encode bytes to base64 string. */
543
+ export declare function bytesToBase64(bytes: Uint8Array): string;
544
+
545
+ /** Decode base64 string to bytes. Returns undefined if invalid. */
546
+ export declare function base64ToBytes(b64: string): Uint8Array | undefined;
547
+
548
+ /** Encode bytes to lowercase hex string. */
549
+ export declare function bytesToHex(bytes: Uint8Array): string;
550
+
551
+ /** Decode hex string to bytes. Returns undefined if invalid. */
552
+ export declare function hexToBytes(hex: string): Uint8Array | undefined;
553
+
554
+ /** Detect MIME type from file bytes. Returns the MIME type string or undefined. */
555
+ export declare function detectMimeType(bytes: Uint8Array): string | undefined;
556
+
557
+ /** Detect image dimensions from file bytes. Returns { width, height } or null. */
558
+ export declare function detectImageDimensions(bytes: Uint8Array): ImageDimensions | null;