@xdevplatform/chat-xdk 0.1.0 → 0.2.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/index.d.ts CHANGED
@@ -10,6 +10,11 @@ export interface PublicKeys {
10
10
  identity: string;
11
11
  /** Base64-encoded signing public key */
12
12
  signing: string;
13
+ /**
14
+ * Always the empty string; reserved. The registered key version set via
15
+ * setKeyVersion is tracked internally and not surfaced here.
16
+ */
17
+ version: string;
13
18
  }
14
19
 
15
20
  /**
@@ -34,7 +39,8 @@ export interface PublicKeyRegistrationPayload {
34
39
  }
35
40
 
36
41
  /**
37
- * Result from encrypt*ForApi methods.
42
+ * Result from encryptMessage, encryptReply, encryptAddReaction, and
43
+ * encryptRemoveReaction.
38
44
  */
39
45
  export interface SendPayload {
40
46
  /** Base64-encoded Thrift MessageCreateEvent. */
@@ -52,7 +58,7 @@ export interface SendPayload {
52
58
  }
53
59
 
54
60
  /**
55
- * Result from signAddMembers / signKeyChange.
61
+ * An action signature authenticating a conversation key change or member add.
56
62
  */
57
63
  export interface ActionSignature {
58
64
  messageId: string;
@@ -60,19 +66,8 @@ export interface ActionSignature {
60
66
  signature: string;
61
67
  signatureVersion: string;
62
68
  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;
69
+ /** Omitted for conversation-key changes; the signed payload embeds the plaintext conversation key. */
70
+ signaturePayload?: string;
76
71
  }
77
72
 
78
73
  /**
@@ -124,8 +119,8 @@ export interface DecryptEventsResult {
124
119
  messages: DecryptedMessage[];
125
120
  /** Extracted conversation keys (for caching). */
126
121
  conversationKeys: ConversationKeyResult;
127
- /** Errors encountered during decryption (index → error message). */
128
- errors: { [index: number]: string };
122
+ /** Errors encountered during decryption (event index, as a string key → error message). */
123
+ errors: { [index: string]: string };
129
124
  }
130
125
 
131
126
  /**
@@ -137,7 +132,7 @@ export interface ImageDimensions {
137
132
  }
138
133
 
139
134
  /**
140
- * Encrypted key for a single recipient (from encryptConversationKeyForRecipients).
135
+ * Encrypted conversation key for a single participant.
141
136
  */
142
137
  export interface EncryptedKeyForRecipient {
143
138
  userId: string;
@@ -146,7 +141,7 @@ export interface EncryptedKeyForRecipient {
146
141
  }
147
142
 
148
143
  /**
149
- * A public key input for prepareConversationKeys.
144
+ * A public key input for prepareConversationKeyChange / prepareGroupMembersChange.
150
145
  *
151
146
  * Represents a single public key version for a user, as returned by the
152
147
  * X API `GET /2/dm/encryption/public_keys` endpoint.
@@ -164,26 +159,209 @@ export interface PublicKeyInput {
164
159
  }
165
160
 
166
161
  /**
167
- * Result from prepareConversationKeys.
162
+ * Result from prepareConversationKeyChange / prepareGroupMembersChange.
168
163
  *
169
- * Contains everything needed to POST to the X API for group creation
170
- * or key initialization no further transformation required.
164
+ * Contains everything needed to POST the change to the X API: the new key
165
+ * (to keep locally), the per-participant encrypted copies, and the action
166
+ * signature that lets recipients verify the change.
171
167
  */
172
- export interface PreparedConversationKeys {
168
+ export interface PreparedConversationChange {
169
+ /** Conversation id the change applies to (derived for a one-to-one, or the id you passed). */
170
+ conversationId: string;
173
171
  /** Raw conversation key bytes (32 bytes). Store locally for encrypting messages. */
174
172
  conversationKey: Uint8Array;
175
173
  /** Timestamp-based version string for this conversation key. */
176
174
  conversationKeyVersion: string;
177
175
  /** Encrypted keys for each participant, ready to POST to the API. */
178
176
  participantKeys: EncryptedKeyForRecipient[];
177
+ /** Action signatures authenticating the change, ready to POST to the API. */
178
+ actionSignatures: ActionSignature[];
179
179
  }
180
180
 
181
181
  /**
182
182
  * Entity descriptor tuple: [startByteOffset, endByteOffset, entityType].
183
- * entityType is one of: "url", "mention", "hashtag", "cashtag", "email", "address", "phoneNumber".
183
+ * entityType is one of: "url", "mention", "hashtag", "cashtag", "email",
184
+ * "address", "phoneNumber" (the snake_case spelling "phone_number" is
185
+ * accepted too). Unrecognized entity types are silently dropped — the
186
+ * message encrypts without that entity and no error is raised.
184
187
  */
185
188
  export type EntityTuple = [number, number, string];
186
189
 
190
+ /**
191
+ * Parameters for encryptMessage.
192
+ */
193
+ export interface EncryptMessageParams {
194
+ /** ID of the message being sent. */
195
+ messageId: string;
196
+ /** User ID of the sender. */
197
+ senderId: string;
198
+ /** ID of the conversation the message belongs to. */
199
+ conversationId: string;
200
+ /** Raw 32-byte conversation key used to encrypt the message content. */
201
+ conversationKey: Uint8Array;
202
+ /** The plaintext message text. */
203
+ text: string;
204
+ /** Version of the conversation key used for encryption. */
205
+ conversationKeyVersion: string;
206
+ /** Version of the signing key used to sign the message. */
207
+ signingKeyVersion: string;
208
+ /** Optional rich-text entities (URLs, mentions, etc.) to embed. */
209
+ entities?: EntityTuple[] | null;
210
+ /** Optional attachments (posts, URLs, media, etc.) to include in the message. */
211
+ attachments?: AttachmentDescriptor[] | null;
212
+ /** Whether to send a push notification. Omitted defaults to true. */
213
+ shouldNotify?: boolean | null;
214
+ /** Optional TTL in milliseconds for disappearing messages. */
215
+ ttlMsec?: number | null;
216
+ }
217
+
218
+ /**
219
+ * Parameters for encryptReply.
220
+ */
221
+ export interface EncryptReplyParams {
222
+ /** ID of the reply message being sent. */
223
+ messageId: string;
224
+ /** User ID of the sender. */
225
+ senderId: string;
226
+ /** ID of the conversation the reply belongs to. */
227
+ conversationId: string;
228
+ /** Raw 32-byte conversation key used to encrypt the message content. */
229
+ conversationKey: Uint8Array;
230
+ /** The plaintext reply text. */
231
+ text: string;
232
+ /** Version of the conversation key used for encryption. */
233
+ conversationKeyVersion: string;
234
+ /** Version of the signing key used to sign the message. */
235
+ signingKeyVersion: string;
236
+ /** The sequenceId of the message being replied to. */
237
+ replyToSequenceId: string;
238
+ /**
239
+ * The sender ID of the message being replied to (for preview).
240
+ * Prefer a string: snowflake user ids exceed the range where JavaScript
241
+ * numbers stay exact. Numbers are accepted only while integral and within
242
+ * `Number.MAX_SAFE_INTEGER`.
243
+ */
244
+ replyToSenderId?: string | number | null;
245
+ /** The text of the message being replied to (for preview). */
246
+ replyToText?: string | null;
247
+ /** Optional rich-text entities (URLs, mentions, etc.) to embed in the outgoing message. */
248
+ entities?: EntityTuple[] | null;
249
+ /** Optional attachments (posts, URLs, media, etc.) to include in the outgoing message. */
250
+ attachments?: AttachmentDescriptor[] | null;
251
+ /** Optional rich-text entities from the original message (for the reply preview). */
252
+ replyToEntities?: EntityTuple[] | null;
253
+ /** Optional attachments from the original message (for the reply preview). */
254
+ replyToAttachments?: AttachmentDescriptor[] | null;
255
+ /** Whether to send a push notification. Omitted defaults to true. */
256
+ shouldNotify?: boolean | null;
257
+ /** Optional TTL in milliseconds for disappearing messages. */
258
+ ttlMsec?: number | null;
259
+ }
260
+
261
+ /**
262
+ * Parameters for encryptAddReaction and encryptRemoveReaction.
263
+ *
264
+ * All fields are required; the same params object can be passed to both
265
+ * methods to add and later remove the same reaction.
266
+ */
267
+ export interface EncryptReactionParams {
268
+ /** ID of the reaction message being sent. */
269
+ messageId: string;
270
+ /** User ID of the sender. */
271
+ senderId: string;
272
+ /** ID of the conversation the reaction belongs to. */
273
+ conversationId: string;
274
+ /** Raw 32-byte conversation key used to encrypt the reaction content. */
275
+ conversationKey: Uint8Array;
276
+ /** The sequenceId of the message being reacted to. */
277
+ targetMessageSequenceId: string;
278
+ /** The reaction emoji. */
279
+ emoji: string;
280
+ /** Version of the conversation key used for encryption. */
281
+ conversationKeyVersion: string;
282
+ /** Version of the signing key used to sign the message. */
283
+ signingKeyVersion: string;
284
+ }
285
+
286
+ /**
287
+ * Parameters for prepareConversationKeyChange.
288
+ */
289
+ export interface ConversationKeyChangeParams {
290
+ /** User ID of the sender signing the change. */
291
+ senderId: string;
292
+ /** Version of the signing key used to sign the change. */
293
+ signingKeyVersion: string;
294
+ /** Identity public keys for every participant the new key is encrypted for. */
295
+ publicKeys: PublicKeyInput[];
296
+ /**
297
+ * Conversation the change applies to. Omit for a one-to-one (the canonical
298
+ * id is derived from the two participants); pass the existing id for a
299
+ * group key rotation.
300
+ */
301
+ conversationId?: string | null;
302
+ }
303
+
304
+ /**
305
+ * Parameters for prepareGroupMembersChange.
306
+ *
307
+ * The current* fields snapshot the group state the change is made against.
308
+ * An unset optional signs the null sentinel, so every binding produces
309
+ * identical signed bytes.
310
+ */
311
+ export interface GroupMembersChangeParams {
312
+ /** User ID of the sender signing the change. */
313
+ senderId: string;
314
+ /** Version of the signing key used to sign the change. */
315
+ signingKeyVersion: string;
316
+ /** Identity public keys for every participant of the updated roster. */
317
+ publicKeys: PublicKeyInput[];
318
+ /** ID of the group conversation being changed. */
319
+ conversationId: string;
320
+ /** User IDs being added to the group. */
321
+ newMemberIds: string[];
322
+ /** User IDs of the current members. */
323
+ currentMemberIds: string[];
324
+ /** User IDs of the current admins. */
325
+ currentAdminIds: string[];
326
+ /** User IDs of members whose join is still pending. */
327
+ currentPendingMemberIds: string[];
328
+ /** The group's current title, if set. */
329
+ currentTitle?: string | null;
330
+ /** The group's current avatar URL, if set. */
331
+ currentAvatarUrl?: string | null;
332
+ /** The group's current message TTL in milliseconds, if set. */
333
+ currentTtlMsec?: number | null;
334
+ /** The group's current screen-capture-blocking state; omit when unset. */
335
+ currentScreenCaptureBlockingEnabled?: boolean | null;
336
+ }
337
+
338
+ /**
339
+ * Parameters for prepareGroupCreate.
340
+ *
341
+ * An unset optional signs the null sentinel, so every binding produces
342
+ * identical signed bytes.
343
+ */
344
+ export interface GroupCreateParams {
345
+ /** User ID of the sender signing the create. */
346
+ senderId: string;
347
+ /** Version of the signing key used to sign the create. */
348
+ signingKeyVersion: string;
349
+ /** Identity public keys for every participant of the new group. */
350
+ publicKeys: PublicKeyInput[];
351
+ /** ID of the new group conversation (the g-prefixed id minted by the initialize endpoint). */
352
+ conversationId: string;
353
+ /** User IDs of the group's members. */
354
+ memberIds: string[];
355
+ /** User IDs of the group's admins. */
356
+ adminIds: string[];
357
+ /** The group's title, if set. */
358
+ title?: string | null;
359
+ /** The group's avatar URL, if set. */
360
+ avatarUrl?: string | null;
361
+ /** The group's message TTL in milliseconds, if set. */
362
+ ttlMsec?: number | null;
363
+ }
364
+
187
365
  // NOTE: AttachmentDescriptor uses snake_case as it's part of the Thrift protocol
188
366
  // and matches the wire format used by the X API
189
367
  /**
@@ -221,7 +399,12 @@ export interface Event {
221
399
  attachments?: AttachmentInfo[];
222
400
  /** For message events: media hash keys derived from attachments (if any). */
223
401
  mediaHashes?: MediaHashReference[];
224
- /** For keyChange events: the new conversation key version (a snowflake/timestamp string). */
402
+ /**
403
+ * The conversation key version (a snowflake/timestamp string). On message
404
+ * events: the version the message and any attached media were encrypted
405
+ * under — decrypt them with the matching key from the conversationKeys
406
+ * map. On keyChange events: the new key version introduced by the change.
407
+ */
225
408
  keyVersion?: string;
226
409
  /** For keyChange events: encrypted conversation keys, one per participant. */
227
410
  participantKeys?: ParticipantKey[];
@@ -231,8 +414,8 @@ export interface Event {
231
414
 
232
415
  /**
233
416
  * 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.
417
+ * discriminates which fields are present (e.g. `text` on 'text' content,
418
+ * `emoji`/`targetMessageId` on reactions, `newText` on edits).
236
419
  */
237
420
  export interface MessageContent {
238
421
  /**
@@ -281,9 +464,8 @@ export interface MediaHashReference {
281
464
 
282
465
  /**
283
466
  * 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.
467
+ * per-variant fields are flattened and `attachmentType` discriminates which
468
+ * apply ('media', 'url', 'post', 'unifiedCard', or 'money').
287
469
  */
288
470
  export interface AttachmentInfo {
289
471
  /** One of: 'media', 'url', 'post', 'unifiedCard', 'money'. */
@@ -315,6 +497,12 @@ export interface CreateChatOptions {
315
497
  /**
316
498
  * Juicebox configuration JSON from the X API.
317
499
  * Obtain this from: GET /2/dm/encryption/juicebox/config
500
+ *
501
+ * Accepted shapes match the native bindings: the `sdk_config` wrapper
502
+ * (its embedded SDK config string is unwrapped for the Juicebox client),
503
+ * the `token_map` array (converted to a realms config with majority
504
+ * recover threshold), or a raw realms config. Realm auth tokens always
505
+ * come from `getAuthToken`, not the config.
318
506
  */
319
507
  juiceboxConfig: string;
320
508
 
@@ -335,11 +523,51 @@ export interface CreateChatOptions {
335
523
  getAuthToken: (realmId: string) => Promise<string>;
336
524
 
337
525
  /**
338
- * Optional number of PIN guesses allowed before lockout.
526
+ * Optional override for the number of PIN guesses allowed before lockout.
527
+ * A non-negative integer wins over the config, including across later
528
+ * `updateConfig()` calls. When omitted, the config's `max_guess_count`
529
+ * applies when it is a non-negative integer (0 included), defaulting to
530
+ * 20 for the `sdk_config` shape and 5 otherwise.
339
531
  */
340
532
  maxGuessCount?: number;
341
533
  }
342
534
 
535
+ /**
536
+ * Incremental stream encryptor for large payloads.
537
+ *
538
+ * Feed plaintext with `push`; call `finish` once to emit the final frame.
539
+ * `finish()` consumes and frees the WASM object — do not call `free()` after
540
+ * it (doing so throws). Call `free()` only when abandoning a stream without
541
+ * finishing it.
542
+ */
543
+ export declare class StreamEncryptor {
544
+ /** Encrypt a plaintext chunk, returning ciphertext available so far. */
545
+ push(plaintext: Uint8Array): Uint8Array;
546
+ /** Emit the final frame; consumes and frees the encryptor. */
547
+ finish(): Uint8Array;
548
+ /** Release the underlying WASM object when abandoning an unfinished stream. */
549
+ free(): void;
550
+ }
551
+
552
+ /**
553
+ * Incremental stream decryptor for large payloads.
554
+ *
555
+ * Feed ciphertext with `push`; call `finish` once at end of input. `finish`
556
+ * throws if the stream ended before its final frame (truncation), so do not
557
+ * treat plaintext from `push` as complete until `finish` succeeds.
558
+ * `finish()` consumes and frees the WASM object — do not call `free()` after
559
+ * it (doing so throws). Call `free()` only when abandoning a stream without
560
+ * finishing it.
561
+ */
562
+ export declare class StreamDecryptor {
563
+ /** Decrypt a ciphertext chunk, returning plaintext available so far. */
564
+ push(ciphertext: Uint8Array): Uint8Array;
565
+ /** Decrypt the final frame; consumes and frees the decryptor. */
566
+ finish(): Uint8Array;
567
+ /** Release the underlying WASM object when abandoning an unfinished stream. */
568
+ free(): void;
569
+ }
570
+
343
571
  /**
344
572
  * Common interface for all Chat methods (shared by Chat and ChatWithJuicebox).
345
573
  *
@@ -347,12 +575,19 @@ export interface CreateChatOptions {
347
575
  * `ChatWithJuicebox` (returned by `createChat()`) adds Juicebox lifecycle methods.
348
576
  */
349
577
  interface ChatCrypto {
350
- /** When enabled, `decryptEvent` throws for unverified messages. */
578
+ /**
579
+ * When enabled — the default — `decryptEvent` throws for any signed event
580
+ * whose signature cannot be verified (invalid, missing, or no matching
581
+ * signing key) instead of returning it with `verified: false`.
582
+ */
351
583
  setRejectUnverified(reject: boolean): void;
352
584
 
353
585
  /** Generate new keypairs and return the registration payload. */
354
586
  generateKeypairs(): PublicKeyRegistrationPayload;
355
587
 
588
+ /** Set the registered public key version (call after unlock()). */
589
+ setKeyVersion(version: string): void;
590
+
356
591
  /** Get current public keys. */
357
592
  getPublicKeys(): PublicKeys;
358
593
 
@@ -371,9 +606,6 @@ interface ChatCrypto {
371
606
  /** Decrypt an encrypted conversation key (ECIES). */
372
607
  decryptConversationKey(encryptedKeyB64: string): Uint8Array;
373
608
 
374
- /** Generate a new conversation key. */
375
- generateConversationKey(): Uint8Array;
376
-
377
609
  /** Extract and decrypt conversation keys from raw KeyChange event strings. */
378
610
  extractConversationKeys(events: string[]): ConversationKeyResult;
379
611
 
@@ -386,12 +618,22 @@ interface ChatCrypto {
386
618
  * 3. Decrypts the message using the appropriate conversation key
387
619
  *
388
620
  * @param events - Raw base64-encoded event strings from the webhook
389
- * @param signingKeys - All known signing keys for all participants (with userId)
621
+ * @param signingKeys - All known signing keys for all participants (with
622
+ * userId). Under the default reject-unverified policy, omitting this makes
623
+ * every signed event land in `errors`; only after
624
+ * `setRejectUnverified(false)` are such events returned with
625
+ * `verified: false`.
390
626
  */
391
627
  decryptEvents(events: string[], signingKeys?: SigningKeyEntry[]): DecryptEventsResult;
392
628
 
393
- /** Decrypt a raw webhook event payload. */
394
- decryptEvent(eventB64: string, conversationKeys: ConversationKeyMap, signingKeys?: SigningKeyEntry[]): Event;
629
+ /**
630
+ * Decrypt a raw webhook event payload.
631
+ *
632
+ * Under the default reject-unverified policy, omitting `signingKeys` makes
633
+ * every signed event throw; only after `setRejectUnverified(false)` are
634
+ * such events returned with `verified: false`.
635
+ */
636
+ decryptEvent(eventB64: string, conversationKeys?: ConversationKeyMap | null, signingKeys?: SigningKeyEntry[]): Event;
395
637
 
396
638
  /** Sign data. Returns raw signature bytes (64 bytes). */
397
639
  sign(data: Uint8Array): Uint8Array;
@@ -399,17 +641,20 @@ interface ChatCrypto {
399
641
  /** Verify a signature. */
400
642
  verify(publicKeyB64: string, signature: Uint8Array, data: Uint8Array): boolean;
401
643
 
644
+ /** Verify that an identity key signed a signing key (key binding). */
645
+ verifyKeyBinding(identityPublicKeyB64: string, signingPublicKeyB64: string, identityPublicKeySignatureB64: string): boolean;
646
+
402
647
  /** 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;
648
+ encryptMessage(params: EncryptMessageParams): SendPayload;
404
649
 
405
650
  /** 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;
651
+ encryptReply(params: EncryptReplyParams): SendPayload;
407
652
 
408
653
  /** Encrypt a reaction-add. */
409
- encryptAddReaction(messageId: string, senderId: string, conversationId: string, conversationKey: Uint8Array, targetMessageSequenceId: string, emoji: string, conversationKeyVersion: string, signingKeyVersion: string): SendPayload;
654
+ encryptAddReaction(params: EncryptReactionParams): SendPayload;
410
655
 
411
656
  /** Encrypt a reaction-remove. */
412
- encryptRemoveReaction(messageId: string, senderId: string, conversationId: string, conversationKey: Uint8Array, targetMessageSequenceId: string, emoji: string, conversationKeyVersion: string, signingKeyVersion: string): SendPayload;
657
+ encryptRemoveReaction(params: EncryptReactionParams): SendPayload;
413
658
 
414
659
  /** Encrypt a stream (e.g. media). */
415
660
  encryptStream(plaintext: Uint8Array, conversationKey: Uint8Array): Uint8Array;
@@ -417,33 +662,59 @@ interface ChatCrypto {
417
662
  /** Decrypt a streaming-encrypted payload (e.g. media). */
418
663
  decryptStream(encrypted: Uint8Array, conversationKey: Uint8Array): Uint8Array;
419
664
 
665
+ /** Create an incremental stream encryptor for large payloads. */
666
+ streamEncryptor(conversationKey: Uint8Array): StreamEncryptor;
667
+
668
+ /** Create an incremental stream decryptor for large payloads. */
669
+ streamDecryptor(conversationKey: Uint8Array): StreamDecryptor;
670
+
420
671
  /** Encrypt a UTF-8 string and return base64 ciphertext. Use for metadata fields like group names. */
421
672
  encrypt(plaintext: string, conversationKey: Uint8Array): string;
422
673
 
423
674
  /** Decrypt a base64-encoded ciphertext and return the UTF-8 plaintext. Use for metadata fields like group names. */
424
675
  decrypt(ciphertextB64: string, conversationKey: Uint8Array): string;
425
676
 
426
- /** Encrypt a conversation key for one or more recipients. */
427
- encryptConversationKeyForRecipients(conversationKey: Uint8Array, recipients: RecipientInput[]): EncryptedKeyForRecipient[];
428
-
429
677
  /**
430
- * Prepare conversation keys for all participants in one call.
678
+ * Prepare a signed conversation-key change, ready to send to the X API.
431
679
  *
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.
680
+ * Use this to start a one-to-one or rotate an existing conversation's key
681
+ * (one-to-one or group). Creating a group or adding members requires a
682
+ * paired group signature as well use prepareGroupCreate or
683
+ * prepareGroupMembersChange for those.
436
684
  *
437
- * @param publicKeys - Array of PublicKeyInput from the X API
438
- * @returns PreparedConversationKeys ready to POST to the API
685
+ * Pass the flat array of public keys (self plus recipients) from the X API
686
+ * in params.publicKeys. Omit params.conversationId for a one-to-one (it is
687
+ * derived from the two participants); pass the existing id for a group key
688
+ * rotation.
689
+ *
690
+ * @returns PreparedConversationChange ready to POST to the API
439
691
  */
440
- prepareConversationKeys(publicKeys: PublicKeyInput[]): PreparedConversationKeys;
692
+ prepareConversationKeyChange(params: ConversationKeyChangeParams): PreparedConversationChange;
441
693
 
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;
694
+ /**
695
+ * Prepare a signed group member-add change, ready to send to the X API.
696
+ *
697
+ * Use this when adding members to an existing group. Creating the group is
698
+ * prepareGroupCreate; a key rotation without a roster change is
699
+ * prepareConversationKeyChange.
700
+ *
701
+ * @returns PreparedConversationChange with two action signatures: the
702
+ * conversation-key change and the member add
703
+ */
704
+ prepareGroupMembersChange(params: GroupMembersChangeParams): PreparedConversationChange;
444
705
 
445
- /** Build and sign a ConversationKeyChange action signature. */
446
- signKeyChange(publicKeyVersion: string, messageId: string, senderId: string, conversationId: string, conversationKeyVersion: string, conversationKey: Uint8Array): ActionSignature;
706
+ /**
707
+ * Prepare a signed group create, ready to send to the X API.
708
+ *
709
+ * Use this once, when creating a group (conversationId is the g-prefixed id
710
+ * minted by the initialize endpoint). Later key rotations use
711
+ * prepareConversationKeyChange; roster additions use
712
+ * prepareGroupMembersChange.
713
+ *
714
+ * @returns PreparedConversationChange with two action signatures: the
715
+ * conversation-key change and the group create
716
+ */
717
+ prepareGroupCreate(params: GroupCreateParams): PreparedConversationChange;
447
718
  }
448
719
 
449
720
  /** Internal crypto engine wrapped by ChatWithJuicebox. Not part of the public API. */
@@ -460,25 +731,25 @@ declare class Chat implements ChatCrypto {
460
731
  hasIdentityKey(): boolean;
461
732
  lock(): void;
462
733
  decryptConversationKey(encryptedKeyB64: string): Uint8Array;
463
- generateConversationKey(): Uint8Array;
464
734
  extractConversationKeys(events: string[]): ConversationKeyResult;
465
735
  decryptEvents(events: string[], signingKeys?: SigningKeyEntry[]): DecryptEventsResult;
466
- decryptEvent(eventB64: string, conversationKeys: ConversationKeyMap, signingKeys?: SigningKeyEntry[]): Event;
736
+ decryptEvent(eventB64: string, conversationKeys?: ConversationKeyMap | null, signingKeys?: SigningKeyEntry[]): Event;
467
737
  sign(data: Uint8Array): Uint8Array;
468
738
  verify(publicKeyB64: string, signature: Uint8Array, data: Uint8Array): boolean;
469
739
  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;
740
+ encryptMessage(params: EncryptMessageParams): SendPayload;
741
+ encryptReply(params: EncryptReplyParams): SendPayload;
742
+ encryptAddReaction(params: EncryptReactionParams): SendPayload;
743
+ encryptRemoveReaction(params: EncryptReactionParams): SendPayload;
474
744
  encryptStream(plaintext: Uint8Array, conversationKey: Uint8Array): Uint8Array;
475
745
  decryptStream(encrypted: Uint8Array, conversationKey: Uint8Array): Uint8Array;
746
+ streamEncryptor(conversationKey: Uint8Array): StreamEncryptor;
747
+ streamDecryptor(conversationKey: Uint8Array): StreamDecryptor;
476
748
  encrypt(plaintext: string, conversationKey: Uint8Array): string;
477
749
  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;
750
+ prepareConversationKeyChange(params: ConversationKeyChangeParams): PreparedConversationChange;
751
+ prepareGroupMembersChange(params: GroupMembersChangeParams): PreparedConversationChange;
752
+ prepareGroupCreate(params: GroupCreateParams): PreparedConversationChange;
482
753
  }
483
754
 
484
755
  /**
@@ -497,6 +768,11 @@ export declare class ChatWithJuicebox implements ChatCrypto {
497
768
  delete(): Promise<void>;
498
769
  /** The new PIN must meet the same strength requirements as setup(). */
499
770
  changePin(oldPin: string | Uint8Array, newPin: string | Uint8Array): Promise<void>;
771
+ /**
772
+ * Re-create the Juicebox client from a new config (e.g. refreshed auth
773
+ * tokens) and re-resolve the PIN guess budget from it; an explicit
774
+ * createChat `maxGuessCount` override keeps winning.
775
+ */
500
776
  updateConfig(juiceboxConfig: string): void;
501
777
 
502
778
  setRejectUnverified(reject: boolean): void;
@@ -509,31 +785,43 @@ export declare class ChatWithJuicebox implements ChatCrypto {
509
785
  hasIdentityKey(): boolean;
510
786
  lock(): void;
511
787
  decryptConversationKey(encryptedKeyB64: string): Uint8Array;
512
- generateConversationKey(): Uint8Array;
513
788
  extractConversationKeys(events: string[]): ConversationKeyResult;
514
789
  decryptEvents(events: string[], signingKeys?: SigningKeyEntry[]): DecryptEventsResult;
515
- decryptEvent(eventB64: string, conversationKeys: ConversationKeyMap, signingKeys?: SigningKeyEntry[]): Event;
790
+ decryptEvent(eventB64: string, conversationKeys?: ConversationKeyMap | null, signingKeys?: SigningKeyEntry[]): Event;
516
791
  sign(data: Uint8Array): Uint8Array;
517
792
  verify(publicKeyB64: string, signature: Uint8Array, data: Uint8Array): boolean;
518
793
  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;
794
+ encryptMessage(params: EncryptMessageParams): SendPayload;
795
+ encryptReply(params: EncryptReplyParams): SendPayload;
796
+ encryptAddReaction(params: EncryptReactionParams): SendPayload;
797
+ encryptRemoveReaction(params: EncryptReactionParams): SendPayload;
523
798
  encryptStream(plaintext: Uint8Array, conversationKey: Uint8Array): Uint8Array;
524
799
  decryptStream(encrypted: Uint8Array, conversationKey: Uint8Array): Uint8Array;
800
+ streamEncryptor(conversationKey: Uint8Array): StreamEncryptor;
801
+ streamDecryptor(conversationKey: Uint8Array): StreamDecryptor;
525
802
  encrypt(plaintext: string, conversationKey: Uint8Array): string;
526
803
  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;
804
+ prepareConversationKeyChange(params: ConversationKeyChangeParams): PreparedConversationChange;
805
+ prepareGroupMembersChange(params: GroupMembersChangeParams): PreparedConversationChange;
806
+ prepareGroupCreate(params: GroupCreateParams): PreparedConversationChange;
807
+
808
+ /**
809
+ * Release the WASM-side crypto engine: clears key material (`lock()`) and
810
+ * frees the underlying WASM object. The instance must not be used
811
+ * afterwards. When reusing the instance, `lock()` alone suffices for key
812
+ * hygiene.
813
+ */
814
+ free(): void;
531
815
  }
532
816
 
533
817
  /**
534
818
  * Create a Chat instance with integrated Juicebox key storage.
535
819
  *
536
820
  * Each call creates an independent instance with its own Juicebox client.
821
+ * The Juicebox auth-token callback is a process-wide global that each
822
+ * instance re-arms before its own operations, so sequential use of multiple
823
+ * instances is fine but concurrent Juicebox operations across instances in
824
+ * one process are not supported.
537
825
  */
538
826
  export declare function createChat(options: CreateChatOptions): Promise<ChatWithJuicebox>;
539
827