@xmtp/browser-sdk 2.0.13 → 2.1.1

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 (43) hide show
  1. package/dist/index.d.ts +849 -678
  2. package/dist/index.js +1 -1
  3. package/dist/index.js.map +1 -1
  4. package/dist/workers/client.js +1 -1
  5. package/dist/workers/client.js.map +1 -1
  6. package/dist/workers/utils.js +1 -1
  7. package/dist/workers/utils.js.map +1 -1
  8. package/package.json +9 -11
  9. package/src/Client.ts +71 -31
  10. package/src/ClientWorkerClass.ts +62 -19
  11. package/src/Conversation.ts +60 -33
  12. package/src/Conversations.ts +96 -48
  13. package/src/DecodedMessage.ts +8 -5
  14. package/src/Dm.ts +14 -4
  15. package/src/Group.ts +27 -20
  16. package/src/Preferences.ts +21 -10
  17. package/src/Utils.ts +2 -2
  18. package/src/UtilsWorkerClass.ts +56 -15
  19. package/src/WorkerClient.ts +25 -3
  20. package/src/WorkerConversation.ts +11 -2
  21. package/src/WorkerConversations.ts +19 -4
  22. package/src/WorkerPreferences.ts +4 -0
  23. package/src/index.ts +3 -1
  24. package/src/types/actions/client.ts +181 -0
  25. package/src/types/actions/conversation.ts +146 -0
  26. package/src/types/actions/conversations.ts +146 -0
  27. package/src/types/actions/dm.ts +19 -0
  28. package/src/types/actions/group.ts +161 -0
  29. package/src/types/actions/preferences.ts +68 -0
  30. package/src/types/actions/streams.ts +44 -0
  31. package/src/types/actions/utils.ts +29 -0
  32. package/src/types/actions.ts +75 -0
  33. package/src/types/options.ts +18 -0
  34. package/src/utils/conversions.ts +60 -0
  35. package/src/utils/createClient.ts +6 -1
  36. package/src/utils/errors.ts +3 -1
  37. package/src/workers/client.ts +243 -190
  38. package/src/workers/utils.ts +25 -29
  39. package/src/types/clientEvents.ts +0 -693
  40. package/src/types/clientStreamEvents.ts +0 -45
  41. package/src/types/index.ts +0 -4
  42. package/src/types/utils.ts +0 -72
  43. package/src/types/utilsEvents.ts +0 -60
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xmtp/browser-sdk",
3
- "version": "2.0.13",
3
+ "version": "2.1.1",
4
4
  "description": "XMTP client SDK for browsers written in TypeScript",
5
5
  "keywords": [
6
6
  "xmtp",
@@ -63,8 +63,7 @@
63
63
  "@xmtp/content-type-group-updated": "^2.0.2",
64
64
  "@xmtp/content-type-primitives": "^2.0.2",
65
65
  "@xmtp/content-type-text": "^2.0.2",
66
- "@xmtp/proto": "^3.78.0",
67
- "@xmtp/wasm-bindings": "1.1.7",
66
+ "@xmtp/wasm-bindings": "1.2.1",
68
67
  "uuid": "^11.1.0"
69
68
  },
70
69
  "devDependencies": {
@@ -73,19 +72,18 @@
73
72
  "@testing-library/dom": "^10.4.0",
74
73
  "@testing-library/jest-dom": "^6.6.3",
75
74
  "@types/uuid": "^10.0.0",
76
- "@vitest/browser": "^3.1.1",
77
- "@vitest/coverage-v8": "^3.1.1",
75
+ "@vitest/browser": "^3.1.4",
76
+ "@vitest/coverage-v8": "^3.1.4",
78
77
  "@web/rollup-plugin-copy": "^0.5.1",
79
- "playwright": "^1.51.1",
80
- "rollup": "^4.39.0",
78
+ "playwright": "^1.52.0",
79
+ "rollup": "^4.41.1",
81
80
  "rollup-plugin-dts": "^6.1.1",
82
- "rollup-plugin-filesize": "^10.0.0",
83
81
  "rollup-plugin-tsconfig-paths": "^1.5.2",
84
82
  "typescript": "^5.8.3",
85
- "viem": "^2.26.2",
86
- "vite": "^6.2.5",
83
+ "viem": "^2.30.5",
84
+ "vite": "^6.3.5",
87
85
  "vite-tsconfig-paths": "^5.1.4",
88
- "vitest": "^3.1.1"
86
+ "vitest": "^3.1.4"
89
87
  },
90
88
  "engines": {
91
89
  "node": ">=20"
package/src/Client.ts CHANGED
@@ -15,7 +15,7 @@ import {
15
15
  import { ClientWorkerClass } from "@/ClientWorkerClass";
16
16
  import { Conversations } from "@/Conversations";
17
17
  import { Preferences } from "@/Preferences";
18
- import type { ClientOptions, XmtpEnv } from "@/types";
18
+ import type { ClientOptions, XmtpEnv } from "@/types/options";
19
19
  import { Utils } from "@/Utils";
20
20
  import {
21
21
  fromSafeEncodedContent,
@@ -32,18 +32,25 @@ import {
32
32
  } from "@/utils/errors";
33
33
  import { type Signer } from "@/utils/signer";
34
34
 
35
+ export type ExtractCodecContentTypes<C extends ContentCodec[] = []> =
36
+ [...C, GroupUpdatedCodec, TextCodec][number] extends ContentCodec<infer T>
37
+ ? T
38
+ : never;
39
+
35
40
  /**
36
41
  * Client for interacting with the XMTP network
37
42
  */
38
- export class Client extends ClientWorkerClass {
43
+ export class Client<
44
+ ContentTypes = ExtractCodecContentTypes,
45
+ > extends ClientWorkerClass {
39
46
  #codecs: Map<string, ContentCodec>;
40
- #conversations: Conversations;
47
+ #conversations: Conversations<ContentTypes>;
41
48
  #identifier?: Identifier;
42
49
  #inboxId: string | undefined;
43
50
  #installationId: string | undefined;
44
51
  #installationIdBytes: Uint8Array | undefined;
45
52
  #isReady = false;
46
- #preferences: Preferences;
53
+ #preferences: Preferences<ContentTypes>;
47
54
  #signer?: Signer;
48
55
  #options?: ClientOptions;
49
56
 
@@ -85,7 +92,7 @@ export class Client extends ClientWorkerClass {
85
92
  * @param identifier - The identifier to initialize the client with
86
93
  */
87
94
  async init(identifier: Identifier) {
88
- const result = await this.sendMessage("init", {
95
+ const result = await this.sendMessage("client.init", {
89
96
  identifier,
90
97
  options: this.#options,
91
98
  });
@@ -103,8 +110,13 @@ export class Client extends ClientWorkerClass {
103
110
  * @param options - Optional configuration for the client
104
111
  * @returns A new client instance
105
112
  */
106
- static async create(signer: Signer, options?: ClientOptions) {
107
- const client = new Client(options);
113
+ static async create<ContentCodecs extends ContentCodec[] = []>(
114
+ signer: Signer,
115
+ options?: Omit<ClientOptions, "codecs"> & {
116
+ codecs?: ContentCodecs;
117
+ },
118
+ ) {
119
+ const client = new Client<ExtractCodecContentTypes<ContentCodecs>>(options);
108
120
  client.#signer = signer;
109
121
 
110
122
  await client.init(await signer.getIdentifier());
@@ -126,8 +138,13 @@ export class Client extends ClientWorkerClass {
126
138
  * @param options - Optional configuration for the client
127
139
  * @returns A new client instance
128
140
  */
129
- static async build(identifier: Identifier, options?: ClientOptions) {
130
- const client = new Client({
141
+ static async build<ContentCodecs extends ContentCodec[] = []>(
142
+ identifier: Identifier,
143
+ options?: Omit<ClientOptions, "codecs"> & {
144
+ codecs?: ContentCodecs;
145
+ },
146
+ ) {
147
+ const client = new Client<ExtractCodecContentTypes<ContentCodecs>>({
131
148
  ...options,
132
149
  disableAutoRegister: true,
133
150
  });
@@ -210,7 +227,7 @@ export class Client extends ClientWorkerClass {
210
227
  * @returns The signature text
211
228
  */
212
229
  async unsafe_createInboxSignatureText() {
213
- return this.sendMessage("createInboxSignatureText", undefined);
230
+ return this.sendMessage("client.createInboxSignatureText", undefined);
214
231
  }
215
232
 
216
233
  /**
@@ -234,7 +251,7 @@ export class Client extends ClientWorkerClass {
234
251
  throw new InboxReassignError();
235
252
  }
236
253
 
237
- return this.sendMessage("addAccountSignatureText", {
254
+ return this.sendMessage("client.addAccountSignatureText", {
238
255
  newIdentifier,
239
256
  });
240
257
  }
@@ -252,7 +269,7 @@ export class Client extends ClientWorkerClass {
252
269
  * @returns The signature text
253
270
  */
254
271
  async unsafe_removeAccountSignatureText(identifier: Identifier) {
255
- return this.sendMessage("removeAccountSignatureText", {
272
+ return this.sendMessage("client.removeAccountSignatureText", {
256
273
  identifier,
257
274
  });
258
275
  }
@@ -271,7 +288,7 @@ export class Client extends ClientWorkerClass {
271
288
  */
272
289
  async unsafe_revokeAllOtherInstallationsSignatureText() {
273
290
  return this.sendMessage(
274
- "revokeAllOtherInstallationsSignatureText",
291
+ "client.revokeAllOtherInstallationsSignatureText",
275
292
  undefined,
276
293
  );
277
294
  }
@@ -290,7 +307,7 @@ export class Client extends ClientWorkerClass {
290
307
  * @returns The signature text
291
308
  */
292
309
  async unsafe_revokeInstallationsSignatureText(installationIds: Uint8Array[]) {
293
- return this.sendMessage("revokeInstallationsSignatureText", {
310
+ return this.sendMessage("client.revokeInstallationsSignatureText", {
294
311
  installationIds,
295
312
  });
296
313
  }
@@ -309,7 +326,7 @@ export class Client extends ClientWorkerClass {
309
326
  * @returns The signature text
310
327
  */
311
328
  async unsafe_changeRecoveryIdentifierSignatureText(identifier: Identifier) {
312
- return this.sendMessage("changeRecoveryIdentifierSignatureText", {
329
+ return this.sendMessage("client.changeRecoveryIdentifierSignatureText", {
313
330
  identifier,
314
331
  });
315
332
  }
@@ -339,7 +356,7 @@ export class Client extends ClientWorkerClass {
339
356
 
340
357
  switch (signer.type) {
341
358
  case "SCW":
342
- await this.sendMessage("addScwSignature", {
359
+ await this.sendMessage("client.addScwSignature", {
343
360
  type: signatureType,
344
361
  bytes: signature,
345
362
  chainId: signer.getChainId(),
@@ -347,7 +364,7 @@ export class Client extends ClientWorkerClass {
347
364
  });
348
365
  break;
349
366
  case "EOA":
350
- await this.sendMessage("addEcdsaSignature", {
367
+ await this.sendMessage("client.addEcdsaSignature", {
351
368
  type: signatureType,
352
369
  bytes: signature,
353
370
  });
@@ -367,7 +384,7 @@ export class Client extends ClientWorkerClass {
367
384
  * methods instead.
368
385
  */
369
386
  async unsafe_applySignatures() {
370
- return this.sendMessage("applySignatures", undefined);
387
+ return this.sendMessage("client.applySignatures", undefined);
371
388
  }
372
389
 
373
390
  /**
@@ -395,7 +412,7 @@ export class Client extends ClientWorkerClass {
395
412
  this.#signer,
396
413
  );
397
414
 
398
- return this.sendMessage("registerIdentity", undefined);
415
+ return this.sendMessage("client.registerIdentity", undefined);
399
416
  }
400
417
 
401
418
  /**
@@ -578,7 +595,7 @@ export class Client extends ClientWorkerClass {
578
595
  * @returns Whether the client is registered
579
596
  */
580
597
  async isRegistered() {
581
- return this.sendMessage("isRegistered", undefined);
598
+ return this.sendMessage("client.isRegistered", undefined);
582
599
  }
583
600
 
584
601
  /**
@@ -588,7 +605,7 @@ export class Client extends ClientWorkerClass {
588
605
  * @returns Whether the client can message the identifiers
589
606
  */
590
607
  async canMessage(identifiers: Identifier[]) {
591
- return this.sendMessage("canMessage", { identifiers });
608
+ return this.sendMessage("client.canMessage", { identifiers });
592
609
  }
593
610
 
594
611
  /**
@@ -619,7 +636,7 @@ export class Client extends ClientWorkerClass {
619
636
  * @returns The inbox ID, if found
620
637
  */
621
638
  async findInboxIdByIdentifier(identifier: Identifier) {
622
- return this.sendMessage("findInboxIdByIdentifier", { identifier });
639
+ return this.sendMessage("client.findInboxIdByIdentifier", { identifier });
623
640
  }
624
641
 
625
642
  /**
@@ -628,9 +645,9 @@ export class Client extends ClientWorkerClass {
628
645
  * @param contentType - The content type to get the codec for
629
646
  * @returns The codec, if found
630
647
  */
631
- codecFor<T = unknown>(contentType: ContentTypeId) {
648
+ codecFor<ContentType = unknown>(contentType: ContentTypeId) {
632
649
  return this.#codecs.get(contentType.toString()) as
633
- | ContentCodec<T>
650
+ | ContentCodec<ContentType>
634
651
  | undefined;
635
652
  }
636
653
 
@@ -642,7 +659,7 @@ export class Client extends ClientWorkerClass {
642
659
  * @returns The encoded content
643
660
  * @throws {CodecNotFoundError} if no codec is found for the content type
644
661
  */
645
- encodeContent(content: unknown, contentType: ContentTypeId) {
662
+ encodeContent(content: ContentTypes, contentType: ContentTypeId) {
646
663
  const codec = this.codecFor(contentType);
647
664
  if (!codec) {
648
665
  throw new CodecNotFoundError(contentType);
@@ -664,8 +681,11 @@ export class Client extends ClientWorkerClass {
664
681
  * @throws {CodecNotFoundError} if no codec is found for the content type
665
682
  * @throws {InvalidGroupMembershipChangeError} if the message is an invalid group membership change
666
683
  */
667
- decodeContent<T = unknown>(message: SafeMessage, contentType: ContentTypeId) {
668
- const codec = this.codecFor<T>(contentType);
684
+ decodeContent<ContentType = unknown>(
685
+ message: SafeMessage,
686
+ contentType: ContentTypeId,
687
+ ) {
688
+ const codec = this.codecFor<ContentType>(contentType);
669
689
  if (!codec) {
670
690
  throw new CodecNotFoundError(contentType);
671
691
  }
@@ -690,7 +710,9 @@ export class Client extends ClientWorkerClass {
690
710
  * @returns The signature
691
711
  */
692
712
  signWithInstallationKey(signatureText: string) {
693
- return this.sendMessage("signWithInstallationKey", { signatureText });
713
+ return this.sendMessage("client.signWithInstallationKey", {
714
+ signatureText,
715
+ });
694
716
  }
695
717
 
696
718
  /**
@@ -704,7 +726,7 @@ export class Client extends ClientWorkerClass {
704
726
  signatureText: string,
705
727
  signatureBytes: Uint8Array,
706
728
  ) {
707
- return this.sendMessage("verifySignedWithInstallationKey", {
729
+ return this.sendMessage("client.verifySignedWithInstallationKey", {
708
730
  signatureText,
709
731
  signatureBytes,
710
732
  });
@@ -723,7 +745,7 @@ export class Client extends ClientWorkerClass {
723
745
  signatureBytes: Uint8Array,
724
746
  publicKey: Uint8Array,
725
747
  ) {
726
- return this.sendMessage("verifySignedWithPublicKey", {
748
+ return this.sendMessage("client.verifySignedWithPublicKey", {
727
749
  signatureText,
728
750
  signatureBytes,
729
751
  publicKey,
@@ -737,8 +759,26 @@ export class Client extends ClientWorkerClass {
737
759
  * @returns The key package statuses
738
760
  */
739
761
  async getKeyPackageStatusesForInstallationIds(installationIds: string[]) {
740
- return this.sendMessage("getKeyPackageStatusesForInstallationIds", {
762
+ return this.sendMessage("client.getKeyPackageStatusesForInstallationIds", {
741
763
  installationIds,
742
764
  });
743
765
  }
766
+
767
+ apiStatistics() {
768
+ return this.sendMessage("client.apiStatistics", undefined);
769
+ }
770
+
771
+ apiIdentityStatistics() {
772
+ return this.sendMessage("client.apiIdentityStatistics", undefined);
773
+ }
774
+
775
+ apiAggregateStatistics() {
776
+ return this.sendMessage("client.apiAggregateStatistics", undefined);
777
+ }
778
+
779
+ async uploadDebugArchive(serverUrl?: string) {
780
+ return this.sendMessage("client.uploadDebugArchive", {
781
+ serverUrl,
782
+ });
783
+ }
744
784
  }
@@ -1,21 +1,31 @@
1
1
  import { v4 } from "uuid";
2
2
  import type {
3
- ClientEventsActions,
4
- ClientEventsErrorData,
5
- ClientEventsResult,
6
- ClientEventsWorkerMessageData,
7
- ClientSendMessageData,
8
- } from "@/types";
3
+ ActionErrorData,
4
+ ActionName,
5
+ ActionWithoutData,
6
+ ClientWorkerAction,
7
+ ExtractActionData,
8
+ ExtractActionResult,
9
+ } from "@/types/actions";
9
10
  import type {
10
- ClientStreamEvents,
11
- ClientStreamEventsErrorData,
12
- } from "@/types/clientStreamEvents";
11
+ StreamAction,
12
+ StreamActionErrorData,
13
+ } from "@/types/actions/streams";
13
14
 
14
15
  const handleError = (event: ErrorEvent) => {
15
- console.error(`Worker error on line ${event.lineno} in "${event.filename}"`);
16
16
  console.error(event.message);
17
17
  };
18
18
 
19
+ /**
20
+ * Class that sets up a worker and provides communications for client functions
21
+ *
22
+ * This class is not meant to be used directly, it is extended by the Client class
23
+ * to provide an interface to the worker.
24
+ *
25
+ * @param worker - The worker to use for the client class
26
+ * @param enableLogging - Whether to enable logging in the worker
27
+ * @returns A new ClientWorkerClass instance
28
+ */
19
29
  export class ClientWorkerClass {
20
30
  #worker: Worker;
21
31
 
@@ -32,13 +42,22 @@ export class ClientWorkerClass {
32
42
  constructor(worker: Worker, enableLogging: boolean) {
33
43
  this.#worker = worker;
34
44
  this.#worker.addEventListener("message", this.handleMessage);
35
- this.#worker.addEventListener("error", handleError);
45
+ if (enableLogging) {
46
+ this.#worker.addEventListener("error", handleError);
47
+ }
36
48
  this.#enableLogging = enableLogging;
37
49
  }
38
50
 
39
- sendMessage<A extends ClientEventsActions>(
51
+ /**
52
+ * Sends an action message to the client worker
53
+ *
54
+ * @param action - The action to send to the worker
55
+ * @param data - The data to send to the worker
56
+ * @returns A promise that resolves when the action is completed
57
+ */
58
+ sendMessage<A extends ActionName<ClientWorkerAction>>(
40
59
  action: A,
41
- data: ClientSendMessageData<A>,
60
+ data: ExtractActionData<ClientWorkerAction, A>,
42
61
  ) {
43
62
  const promiseId = v4();
44
63
  this.#worker.postMessage({
@@ -46,17 +65,29 @@ export class ClientWorkerClass {
46
65
  id: promiseId,
47
66
  data,
48
67
  });
49
- const promise = new Promise<ClientEventsResult<A>>((resolve, reject) => {
68
+ const promise = new Promise((resolve, reject) => {
50
69
  this.#promises.set(promiseId, {
51
70
  resolve: resolve as (value: unknown) => void,
52
71
  reject,
53
72
  });
54
73
  });
55
- return promise;
74
+ return promise as [ExtractActionResult<ClientWorkerAction, A>] extends [
75
+ undefined,
76
+ ]
77
+ ? Promise<void>
78
+ : Promise<ExtractActionResult<ClientWorkerAction, A>>;
56
79
  }
57
80
 
81
+ /**
82
+ * Handles a message from the client worker
83
+ *
84
+ * @param event - The event to handle
85
+ */
58
86
  handleMessage = (
59
- event: MessageEvent<ClientEventsWorkerMessageData | ClientEventsErrorData>,
87
+ event: MessageEvent<
88
+ | ActionWithoutData<ClientWorkerAction>
89
+ | ActionErrorData<ClientWorkerAction>
90
+ >,
60
91
  ) => {
61
92
  const eventData = event.data;
62
93
  if (this.#enableLogging) {
@@ -73,12 +104,19 @@ export class ClientWorkerClass {
73
104
  }
74
105
  };
75
106
 
76
- handleStreamMessage = <T extends ClientStreamEvents["result"]>(
107
+ /**
108
+ * Handles a stream message from the client worker
109
+ *
110
+ * @param streamId - The ID of the stream to handle
111
+ * @param callback - The callback to handle the stream message
112
+ * @returns A function to remove the stream handler
113
+ */
114
+ handleStreamMessage = <T extends StreamAction["result"]>(
77
115
  streamId: string,
78
116
  callback: (error: Error | null, value: T | null) => void,
79
117
  ) => {
80
118
  const streamHandler = (
81
- event: MessageEvent<ClientStreamEvents | ClientStreamEventsErrorData>,
119
+ event: MessageEvent<StreamAction | StreamActionErrorData>,
82
120
  ) => {
83
121
  const eventData = event.data;
84
122
  if (eventData.streamId === streamId) {
@@ -96,9 +134,14 @@ export class ClientWorkerClass {
96
134
  };
97
135
  };
98
136
 
137
+ /**
138
+ * Removes all event listeners and terminates the worker
139
+ */
99
140
  close() {
100
141
  this.#worker.removeEventListener("message", this.handleMessage);
101
- this.#worker.removeEventListener("error", handleError);
142
+ if (this.#enableLogging) {
143
+ this.#worker.removeEventListener("error", handleError);
144
+ }
102
145
  this.#worker.terminate();
103
146
  }
104
147
  }