@xmtp/browser-sdk 5.1.0 → 5.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/src/Client.ts CHANGED
@@ -221,6 +221,20 @@ export class Client<
221
221
  return this.#preferences;
222
222
  }
223
223
 
224
+ /**
225
+ * Gets the version of libxmtp used in the bindings
226
+ */
227
+ async libxmtpVersion() {
228
+ return this.sendMessage("client.libxmtpVersion", undefined);
229
+ }
230
+
231
+ /**
232
+ * Gets the app version used by the client
233
+ */
234
+ async appVersion() {
235
+ return this.sendMessage("client.appVersion", undefined);
236
+ }
237
+
224
238
  /**
225
239
  * Creates signature text for creating a new inbox
226
240
  *
@@ -488,6 +502,12 @@ export class Client<
488
502
 
489
503
  const { signatureText, signatureRequestId } =
490
504
  await this.unsafe_revokeAllOtherInstallationsSignatureText();
505
+
506
+ // no other installations to revoke, nothing to do
507
+ if (!signatureText) {
508
+ return;
509
+ }
510
+
491
511
  const signature = await this.#signer.signMessage(signatureText);
492
512
  const signer = await toSafeSigner(this.#signer, signature);
493
513
 
@@ -535,11 +555,18 @@ export class Client<
535
555
  inboxId: string,
536
556
  installationIds: Uint8Array[],
537
557
  env?: XmtpEnv,
558
+ gatewayHost?: string,
538
559
  enableLogging?: boolean,
539
560
  ) {
540
561
  const utils = new Utils(enableLogging);
541
562
  await utils.init();
542
- await utils.revokeInstallations(signer, inboxId, installationIds, env);
563
+ await utils.revokeInstallations(
564
+ signer,
565
+ inboxId,
566
+ installationIds,
567
+ env,
568
+ gatewayHost,
569
+ );
543
570
  utils.close();
544
571
  }
545
572
 
@@ -554,10 +581,15 @@ export class Client<
554
581
  inboxIds: string[],
555
582
  env?: XmtpEnv,
556
583
  enableLogging?: boolean,
584
+ gatewayHost?: string,
557
585
  ) {
558
586
  const utils = new Utils(enableLogging);
559
587
  await utils.init();
560
- const result = await utils.inboxStateFromInboxIds(inboxIds, env);
588
+ const result = await utils.inboxStateFromInboxIds(
589
+ inboxIds,
590
+ env,
591
+ gatewayHost,
592
+ );
561
593
  utils.close();
562
594
  return result;
563
595
  }
@@ -662,6 +694,38 @@ export class Client<
662
694
  if (!codec) {
663
695
  throw new CodecNotFoundError(contentType);
664
696
  }
697
+
698
+ return this.#encodeWithCodec(content, codec);
699
+ }
700
+
701
+ /**
702
+ * Prepares content for sending by encoding it and generating send options from the codec
703
+ *
704
+ * @param content - The message content to prepare for sending
705
+ * @param contentType - The content type identifier for the appropriate codec
706
+ * @returns An object containing the encoded content and send options
707
+ * @throws {CodecNotFoundError} When no codec is registered for the specified content type
708
+ */
709
+ prepareForSend(content: ContentTypes, contentType: ContentTypeId) {
710
+ const codec = this.codecFor(contentType);
711
+ if (!codec) {
712
+ throw new CodecNotFoundError(contentType);
713
+ }
714
+
715
+ return {
716
+ encodedContent: this.#encodeWithCodec(content, codec),
717
+ sendOptions: this.#sendMessageOpts(content, codec),
718
+ };
719
+ }
720
+
721
+ /**
722
+ * Encodes content using a specific codec and adds fallback information if available
723
+ *
724
+ * @param content - The content to encode
725
+ * @param codec - The codec to use for encoding
726
+ * @returns The encoded content with optional fallback
727
+ */
728
+ #encodeWithCodec(content: ContentTypes, codec: ContentCodec) {
665
729
  const encoded = codec.encode(content, this);
666
730
  const fallback = codec.fallback(content);
667
731
  if (fallback) {
@@ -670,6 +734,20 @@ export class Client<
670
734
  return toSafeEncodedContent(encoded);
671
735
  }
672
736
 
737
+ /**
738
+ * Generates send options based on the content and codec
739
+ *
740
+ * @param content - The content being sent
741
+ * @param codec - The codec used for the content
742
+ * @returns Send options including whether to push notify recipients
743
+ */
744
+ #sendMessageOpts(
745
+ content: ContentTypes,
746
+ codec: ContentCodec,
747
+ ): { shouldPush: boolean } {
748
+ return { shouldPush: codec.shouldPush(content) };
749
+ }
750
+
673
751
  /**
674
752
  * Decodes a message for a given content type
675
753
  *
@@ -144,15 +144,16 @@ export class Conversation<ContentTypes = unknown> {
144
144
  throw new MissingContentTypeError();
145
145
  }
146
146
 
147
- const safeEncodedContent =
147
+ const { encodedContent: safeEncodedContent, sendOptions } =
148
148
  typeof content === "string"
149
- ? this.#client.encodeContent(content, contentType ?? ContentTypeText)
149
+ ? this.#client.prepareForSend(content, contentType ?? ContentTypeText)
150
150
  : // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
151
- this.#client.encodeContent(content, contentType!);
151
+ this.#client.prepareForSend(content, contentType!);
152
152
 
153
153
  return this.#client.sendMessage("conversation.sendOptimistic", {
154
154
  id: this.#id,
155
155
  content: safeEncodedContent,
156
+ sendOptions,
156
157
  });
157
158
  }
158
159
 
@@ -169,15 +170,16 @@ export class Conversation<ContentTypes = unknown> {
169
170
  throw new MissingContentTypeError();
170
171
  }
171
172
 
172
- const safeEncodedContent =
173
+ const { encodedContent: safeEncodedContent, sendOptions } =
173
174
  typeof content === "string"
174
- ? this.#client.encodeContent(content, contentType ?? ContentTypeText)
175
+ ? this.#client.prepareForSend(content, contentType ?? ContentTypeText)
175
176
  : // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
176
- this.#client.encodeContent(content, contentType!);
177
+ this.#client.prepareForSend(content, contentType!);
177
178
 
178
179
  return this.#client.sendMessage("conversation.send", {
179
180
  id: this.#id,
180
181
  content: safeEncodedContent,
182
+ sendOptions,
181
183
  });
182
184
  }
183
185
 
@@ -196,6 +198,22 @@ export class Conversation<ContentTypes = unknown> {
196
198
  return messages.map((message) => new DecodedMessage(this.#client, message));
197
199
  }
198
200
 
201
+ /**
202
+ * Counts messages in this conversation
203
+ *
204
+ * @param options - Optional filtering options
205
+ * @returns Promise that resolves with the count of messages
206
+ */
207
+ async countMessages(
208
+ options?: Omit<SafeListMessagesOptions, "limit" | "direction">,
209
+ ) {
210
+ const count = await this.#client.sendMessage("conversation.countMessages", {
211
+ id: this.#id,
212
+ options,
213
+ });
214
+ return count;
215
+ }
216
+
199
217
  /**
200
218
  * Gets the consent state for this conversation
201
219
  *
@@ -294,8 +312,10 @@ export class Conversation<ContentTypes = unknown> {
294
312
  onFail: () => void,
295
313
  ) => {
296
314
  const streamId = v4();
297
- // sync the conversation
298
- await this.sync();
315
+ if (!options?.disableSync) {
316
+ // sync the conversation
317
+ await this.sync();
318
+ }
299
319
  // start the stream
300
320
  await this.#client.sendMessage("conversation.stream", {
301
321
  groupId: this.#id,
@@ -321,8 +321,10 @@ export class Conversations<ContentTypes = unknown> {
321
321
  onFail: () => void,
322
322
  ) => {
323
323
  const streamId = v4();
324
- // sync the conversation
325
- await this.sync();
324
+ if (!options?.disableSync) {
325
+ // sync the conversation
326
+ await this.sync();
327
+ }
326
328
  // start the stream
327
329
  await this.#client.sendMessage("conversations.stream", {
328
330
  streamId,
@@ -394,8 +396,10 @@ export class Conversations<ContentTypes = unknown> {
394
396
  onFail: () => void,
395
397
  ) => {
396
398
  const streamId = v4();
397
- // sync the conversation
398
- await this.sync();
399
+ if (!options?.disableSync) {
400
+ // sync the conversation
401
+ await this.sync();
402
+ }
399
403
  // start the stream
400
404
  await this.#client.sendMessage("conversations.streamAllMessages", {
401
405
  streamId,
@@ -453,4 +457,38 @@ export class Conversations<ContentTypes = unknown> {
453
457
  conversationType: ConversationType.Dm,
454
458
  });
455
459
  }
460
+
461
+ /**
462
+ * Creates a stream for message deletions
463
+ *
464
+ * @param options - Optional stream options
465
+ * @returns Stream instance for message deletions
466
+ */
467
+ async streamMessageDeletions(
468
+ options?: Omit<
469
+ StreamOptions<string>,
470
+ | "disableSync"
471
+ | "onFail"
472
+ | "onRetry"
473
+ | "onRestart"
474
+ | "retryAttempts"
475
+ | "retryDelay"
476
+ | "retryOnFail"
477
+ >,
478
+ ) {
479
+ const stream = async (callback: StreamCallback<string>) => {
480
+ const streamId = v4();
481
+ // start the stream
482
+ await this.#client.sendMessage("conversations.streamMessageDeletions", {
483
+ streamId,
484
+ });
485
+ // handle stream messages
486
+ return this.#client.handleStreamMessage<string>(
487
+ streamId,
488
+ callback,
489
+ options,
490
+ );
491
+ };
492
+ return createStream(stream, undefined, options);
493
+ }
456
494
  }
package/src/Utils.ts CHANGED
@@ -37,12 +37,18 @@ export class Utils extends UtilsWorkerClass {
37
37
  *
38
38
  * @param identifier - The identifier to get the inbox ID for
39
39
  * @param env - Optional XMTP environment configuration (default: "dev")
40
+ * @param gatewayHost - Optional gateway host override
40
41
  * @returns Promise that resolves with the inbox ID for the identifier
41
42
  */
42
- async getInboxIdForIdentifier(identifier: Identifier, env?: XmtpEnv) {
43
+ async getInboxIdForIdentifier(
44
+ identifier: Identifier,
45
+ env?: XmtpEnv,
46
+ gatewayHost?: string,
47
+ ) {
43
48
  return this.sendMessage("utils.getInboxIdForIdentifier", {
44
49
  identifier,
45
50
  env,
51
+ gatewayHost,
46
52
  });
47
53
  }
48
54
 
@@ -59,6 +65,7 @@ export class Utils extends UtilsWorkerClass {
59
65
  * @param identifier - The identifier to revoke installations for
60
66
  * @param inboxId - The inbox ID to revoke installations for
61
67
  * @param installationIds - The installation IDs to revoke
68
+ * @param gatewayHost - Optional gateway host override
62
69
  * @returns The signature text and signature request ID
63
70
  */
64
71
  async revokeInstallationsSignatureText(
@@ -66,6 +73,7 @@ export class Utils extends UtilsWorkerClass {
66
73
  inboxId: string,
67
74
  installationIds: Uint8Array[],
68
75
  env?: XmtpEnv,
76
+ gatewayHost?: string,
69
77
  ) {
70
78
  return this.sendMessage("utils.revokeInstallationsSignatureText", {
71
79
  env,
@@ -73,6 +81,7 @@ export class Utils extends UtilsWorkerClass {
73
81
  inboxId,
74
82
  installationIds,
75
83
  signatureRequestId: v4(),
84
+ gatewayHost,
76
85
  });
77
86
  }
78
87
 
@@ -83,6 +92,7 @@ export class Utils extends UtilsWorkerClass {
83
92
  * @param signer - The signer to use
84
93
  * @param inboxId - The inbox ID to revoke installations for
85
94
  * @param installationIds - The installation IDs to revoke
95
+ * @param gatewayHost - Optional gateway host override
86
96
  * @returns Promise that resolves with the result of the revoke installations operation
87
97
  */
88
98
  async revokeInstallations(
@@ -90,6 +100,7 @@ export class Utils extends UtilsWorkerClass {
90
100
  inboxId: string,
91
101
  installationIds: Uint8Array[],
92
102
  env?: XmtpEnv,
103
+ gatewayHost?: string,
93
104
  ) {
94
105
  const identifier = await signer.getIdentifier();
95
106
  const { signatureText, signatureRequestId } =
@@ -98,6 +109,7 @@ export class Utils extends UtilsWorkerClass {
98
109
  inboxId,
99
110
  installationIds,
100
111
  env,
112
+ gatewayHost,
101
113
  );
102
114
  const signature = await signer.signMessage(signatureText);
103
115
  const safeSigner = await toSafeSigner(signer, signature);
@@ -106,6 +118,7 @@ export class Utils extends UtilsWorkerClass {
106
118
  signer: safeSigner,
107
119
  signatureRequestId,
108
120
  env,
121
+ gatewayHost,
109
122
  });
110
123
  }
111
124
 
@@ -116,10 +129,15 @@ export class Utils extends UtilsWorkerClass {
116
129
  * @param env - The environment to use
117
130
  * @returns The inbox state for the specified inbox IDs
118
131
  */
119
- async inboxStateFromInboxIds(inboxIds: string[], env?: XmtpEnv) {
132
+ async inboxStateFromInboxIds(
133
+ inboxIds: string[],
134
+ env?: XmtpEnv,
135
+ gatewayHost?: string,
136
+ ) {
120
137
  return this.sendMessage("utils.inboxStateFromInboxIds", {
121
138
  inboxIds,
122
139
  env,
140
+ gatewayHost,
123
141
  });
124
142
  }
125
143
  }
@@ -34,6 +34,14 @@ export class WorkerClient {
34
34
  return new WorkerClient(client, options);
35
35
  }
36
36
 
37
+ get libxmtpVersion() {
38
+ return this.#client.libxmtpVersion;
39
+ }
40
+
41
+ get appVersion() {
42
+ return this.#client.appVersion;
43
+ }
44
+
37
45
  get accountIdentifier() {
38
46
  return this.#client.accountIdentifier;
39
47
  }
@@ -12,6 +12,7 @@ import {
12
12
  type MetadataField,
13
13
  type PermissionPolicy,
14
14
  type PermissionUpdateType,
15
+ type SendMessageOpts,
15
16
  } from "@xmtp/wasm-bindings";
16
17
  import {
17
18
  fromSafeListMessagesOptions,
@@ -180,12 +181,14 @@ export class WorkerConversation {
180
181
  return this.#group.publishMessages();
181
182
  }
182
183
 
183
- sendOptimistic(encodedContent: EncodedContent) {
184
- return this.#group.sendOptimistic(encodedContent);
184
+ sendOptimistic(encodedContent: EncodedContent, opts: SendMessageOpts) {
185
+ // Pass through to underlying implementation - it will handle undefined opts
186
+ return this.#group.sendOptimistic(encodedContent, opts);
185
187
  }
186
188
 
187
- async send(encodedContent: EncodedContent) {
188
- return this.#group.send(encodedContent);
189
+ async send(encodedContent: EncodedContent, opts: SendMessageOpts) {
190
+ // Pass through to underlying implementation - it will handle undefined opts
191
+ return this.#group.send(encodedContent, opts);
189
192
  }
190
193
 
191
194
  async messages(options?: SafeListMessagesOptions) {
@@ -194,6 +197,12 @@ export class WorkerConversation {
194
197
  );
195
198
  }
196
199
 
200
+ async countMessages(options?: SafeListMessagesOptions) {
201
+ return this.#group.countMessages(
202
+ options ? fromSafeListMessagesOptions(options) : undefined,
203
+ );
204
+ }
205
+
197
206
  get consentState() {
198
207
  return this.#group.consentState();
199
208
  }
@@ -214,4 +214,17 @@ export class WorkerConversations {
214
214
  consentStates,
215
215
  );
216
216
  }
217
+
218
+ streamMessageDeletions(callback: StreamCallback<string>) {
219
+ const on_message_deleted = (messageId: string) => {
220
+ callback(null, messageId);
221
+ };
222
+ const on_error = (error: Error | null) => {
223
+ callback(error, undefined);
224
+ };
225
+ return this.#conversations.streamMessageDeletions({
226
+ on_message_deleted,
227
+ on_error,
228
+ });
229
+ }
217
230
  }
package/src/constants.ts CHANGED
@@ -7,9 +7,9 @@
7
7
  * @property {string} production - The production URL for the XMTP network
8
8
  */
9
9
  export const ApiUrls = {
10
- local: "http://localhost:5555",
11
- dev: "https://dev.xmtp.network",
12
- production: "https://production.xmtp.network",
10
+ local: "http://localhost:5557",
11
+ dev: "https://api.dev.xmtp.network:5558",
12
+ production: "https://api.production.xmtp.network:5558",
13
13
  } as const;
14
14
 
15
15
  /**
package/src/index.ts CHANGED
@@ -12,6 +12,7 @@ export type * from "./types/options";
12
12
  export * from "./utils/conversions";
13
13
  export type { AsyncStreamProxy } from "./AsyncStream";
14
14
  export type {
15
+ GroupSyncSummary,
15
16
  Identifier,
16
17
  IdentifierKind,
17
18
  UserPreference,
@@ -65,7 +65,7 @@ export type ClientAction =
65
65
  action: "client.revokeAllOtherInstallationsSignatureText";
66
66
  id: string;
67
67
  result: {
68
- signatureText: string;
68
+ signatureText: string | undefined;
69
69
  signatureRequestId: string;
70
70
  };
71
71
  data: {
@@ -210,4 +210,16 @@ export type ClientAction =
210
210
  data: {
211
211
  installationIds: string[];
212
212
  };
213
+ }
214
+ | {
215
+ action: "client.libxmtpVersion";
216
+ id: string;
217
+ result: string;
218
+ data: undefined;
219
+ }
220
+ | {
221
+ action: "client.appVersion";
222
+ id: string;
223
+ result: string;
224
+ data: undefined;
213
225
  };
@@ -8,6 +8,7 @@ import type {
8
8
  SafeListMessagesOptions,
9
9
  SafeMessage,
10
10
  SafeMessageDisappearingSettings,
11
+ SafeSendMessageOpts,
11
12
  } from "@/utils/conversions";
12
13
 
13
14
  export type ConversationAction =
@@ -26,6 +27,7 @@ export type ConversationAction =
26
27
  data: {
27
28
  id: string;
28
29
  content: SafeEncodedContent;
30
+ sendOptions: SafeSendMessageOpts;
29
31
  };
30
32
  }
31
33
  | {
@@ -35,6 +37,7 @@ export type ConversationAction =
35
37
  data: {
36
38
  id: string;
37
39
  content: SafeEncodedContent;
40
+ sendOptions: SafeSendMessageOpts;
38
41
  };
39
42
  }
40
43
  | {
@@ -54,6 +57,15 @@ export type ConversationAction =
54
57
  options?: SafeListMessagesOptions;
55
58
  };
56
59
  }
60
+ | {
61
+ action: "conversation.countMessages";
62
+ id: string;
63
+ result: bigint;
64
+ data: {
65
+ id: string;
66
+ options?: Omit<SafeListMessagesOptions, "limit" | "direction">;
67
+ };
68
+ }
57
69
  | {
58
70
  action: "conversation.members";
59
71
  id: string;
@@ -143,4 +143,12 @@ export type ConversationsAction =
143
143
  conversationType?: ConversationType;
144
144
  consentStates?: ConsentState[];
145
145
  };
146
+ }
147
+ | {
148
+ action: "conversations.streamMessageDeletions";
149
+ id: string;
150
+ result: undefined;
151
+ data: {
152
+ streamId: string;
153
+ };
146
154
  };
@@ -1,4 +1,8 @@
1
- import type { ConsentEntityType, ConsentState } from "@xmtp/wasm-bindings";
1
+ import type {
2
+ ConsentEntityType,
3
+ ConsentState,
4
+ GroupSyncSummary,
5
+ } from "@xmtp/wasm-bindings";
2
6
  import type { SafeConsent, SafeInboxState } from "@/utils/conversions";
3
7
 
4
8
  export type PreferencesAction =
@@ -47,7 +51,7 @@ export type PreferencesAction =
47
51
  | {
48
52
  action: "preferences.sync";
49
53
  id: string;
50
- result: number;
54
+ result: GroupSyncSummary;
51
55
  data: undefined;
52
56
  }
53
57
  | {
@@ -26,6 +26,11 @@ export type StreamAction =
26
26
  streamId: string;
27
27
  result: UserPreference[] | undefined;
28
28
  }
29
+ | {
30
+ action: "stream.messageDeleted";
31
+ streamId: string;
32
+ result: string | undefined;
33
+ }
29
34
  | {
30
35
  action: "stream.fail";
31
36
  streamId: string;
@@ -27,6 +27,7 @@ export type UtilsWorkerAction =
27
27
  data: {
28
28
  identifier: Identifier;
29
29
  env?: XmtpEnv;
30
+ gatewayHost?: string;
30
31
  };
31
32
  }
32
33
  | {
@@ -40,6 +41,7 @@ export type UtilsWorkerAction =
40
41
  env?: XmtpEnv;
41
42
  identifier: Identifier;
42
43
  inboxId: string;
44
+ gatewayHost?: string;
43
45
  installationIds: Uint8Array[];
44
46
  signatureRequestId: string;
45
47
  };
@@ -52,6 +54,7 @@ export type UtilsWorkerAction =
52
54
  env?: XmtpEnv;
53
55
  signer: SafeSigner;
54
56
  signatureRequestId: string;
57
+ gatewayHost?: string;
55
58
  };
56
59
  }
57
60
  | {
@@ -61,5 +64,6 @@ export type UtilsWorkerAction =
61
64
  data: {
62
65
  inboxIds: string[];
63
66
  env?: XmtpEnv;
67
+ gatewayHost?: string;
64
68
  };
65
69
  };
@@ -21,6 +21,10 @@ export type NetworkOptions = {
21
21
  * specific endpoint for syncing history
22
22
  */
23
23
  historySyncUrl?: string | null;
24
+ /**
25
+ * gatewayHost can be used to override the gateway endpoint
26
+ */
27
+ gatewayHost?: string | null;
24
28
  };
25
29
 
26
30
  export type ContentOptions = {