@xmtp/browser-sdk 6.4.0 → 6.4.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.
package/src/Client.ts CHANGED
@@ -1,5 +1,11 @@
1
1
  import { type ContentCodec } from "@xmtp/content-type-primitives";
2
- import { LogLevel, type Identifier } from "@xmtp/wasm-bindings";
2
+ import {
3
+ Backend,
4
+ LogLevel,
5
+ type ArchiveOptions,
6
+ type Identifier,
7
+ type InboxState,
8
+ } from "@xmtp/wasm-bindings";
3
9
  import { CodecRegistry } from "@/CodecRegistry";
4
10
  import { Conversations } from "@/Conversations";
5
11
  import { DebugInformation } from "@/DebugInformation";
@@ -10,6 +16,7 @@ import type {
10
16
  ExtractCodecContentTypes,
11
17
  XmtpEnv,
12
18
  } from "@/types/options";
19
+ import { createBackend } from "@/utils/createBackend";
13
20
  import {
14
21
  AccountAlreadyAssociatedError,
15
22
  InboxReassignError,
@@ -22,6 +29,23 @@ import { toSafeSigner, type SafeSigner, type Signer } from "@/utils/signer";
22
29
  import { uuid } from "@/utils/uuid";
23
30
  import { WorkerBridge } from "@/utils/WorkerBridge";
24
31
 
32
+ /**
33
+ * Resolves a `Backend` instance from either a `Backend` or an `XmtpEnv` string.
34
+ *
35
+ * @param envOrBackend - A `Backend` instance, or an `XmtpEnv` string
36
+ * @param gatewayHost - Optional gateway host (only used when `envOrBackend` is an `XmtpEnv`)
37
+ * @returns A `Backend` instance
38
+ */
39
+ const resolveBackend = async (
40
+ envOrBackend?: XmtpEnv | Backend,
41
+ gatewayHost?: string,
42
+ ): Promise<Backend> => {
43
+ if (envOrBackend instanceof Backend) {
44
+ return envOrBackend;
45
+ }
46
+ return createBackend({ env: envOrBackend, gatewayHost });
47
+ };
48
+
25
49
  /**
26
50
  * Client for interacting with the XMTP network
27
51
  */
@@ -30,6 +54,7 @@ export class Client<ContentTypes = ExtractCodecContentTypes> {
30
54
  #codecRegistry: CodecRegistry;
31
55
  #conversations: Conversations<ContentTypes>;
32
56
  #debugInformation: DebugInformation;
57
+ #env?: XmtpEnv;
33
58
  #identifier?: Identifier;
34
59
  #inboxId?: string;
35
60
  #installationId?: string;
@@ -50,6 +75,21 @@ export class Client<ContentTypes = ExtractCodecContentTypes> {
50
75
  * @param options - Optional configuration for the client
51
76
  */
52
77
  constructor(options?: ClientOptions) {
78
+ /*
79
+ * The Browser SDK runs XMTP's WASM bindings inside a Web Worker.
80
+ * The SDK sends options to the worker via postMessage(), which uses the
81
+ * structured clone algorithm. Codecs contain functions that can't be
82
+ * cloned, so we mark the codecs property as non-enumerable to exclude
83
+ * it from serialization.
84
+ *
85
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm#things_that_dont_work_with_structured_clone
86
+ */
87
+ if (options) {
88
+ Object.defineProperty(options, "codecs", {
89
+ value: options.codecs,
90
+ enumerable: false,
91
+ });
92
+ }
53
93
  const worker = new Worker(new URL("./workers/client", import.meta.url), {
54
94
  type: "module",
55
95
  });
@@ -57,8 +97,8 @@ export class Client<ContentTypes = ExtractCodecContentTypes> {
57
97
  options?.loggingLevel !== undefined &&
58
98
  options.loggingLevel !== LogLevel.Off;
59
99
  this.#worker = new WorkerBridge<ClientWorkerAction>(worker, enableLogging);
60
- this.#options = options;
61
100
  this.#codecRegistry = new CodecRegistry([...(options?.codecs ?? [])]);
101
+ this.#options = options;
62
102
  this.#conversations = new Conversations(
63
103
  this,
64
104
  this.#worker,
@@ -82,6 +122,7 @@ export class Client<ContentTypes = ExtractCodecContentTypes> {
82
122
  options: this.#options,
83
123
  });
84
124
  this.#appVersion = result.appVersion;
125
+ this.#env = result.env as XmtpEnv;
85
126
  this.#identifier = identifier;
86
127
  this.#inboxId = result.inboxId;
87
128
  this.#installationId = result.installationId;
@@ -231,6 +272,13 @@ export class Client<ContentTypes = ExtractCodecContentTypes> {
231
272
  return this.#appVersion;
232
273
  }
233
274
 
275
+ /**
276
+ * Gets the XMTP environment used by this client
277
+ */
278
+ get env() {
279
+ return this.#env;
280
+ }
281
+
234
282
  /**
235
283
  * Creates signature text for creating a new inbox
236
284
  *
@@ -544,10 +592,27 @@ export class Client<ContentTypes = ExtractCodecContentTypes> {
544
592
  /**
545
593
  * Revokes specific installations of the client's inbox without a client
546
594
  *
547
- * @param env - The environment to use
548
595
  * @param signer - The signer to use
549
596
  * @param inboxId - The inbox ID to revoke installations for
550
597
  * @param installationIds - The installation IDs to revoke
598
+ * @param backend - Optional `Backend` instance created with `createBackend()`
599
+ */
600
+ static async revokeInstallations(
601
+ signer: Signer,
602
+ inboxId: string,
603
+ installationIds: Uint8Array[],
604
+ backend?: Backend,
605
+ ): Promise<void>;
606
+ /**
607
+ * Revokes specific installations of the client's inbox without a client
608
+ *
609
+ * @param signer - The signer to use
610
+ * @param inboxId - The inbox ID to revoke installations for
611
+ * @param installationIds - The installation IDs to revoke
612
+ * @param env - The environment to use
613
+ * @param gatewayHost - Optional gateway host
614
+ * @deprecated Pass a `Backend` instance created with `createBackend()` instead
615
+ * of `XmtpEnv` and `gatewayHost`.
551
616
  */
552
617
  static async revokeInstallations(
553
618
  signer: Signer,
@@ -555,30 +620,53 @@ export class Client<ContentTypes = ExtractCodecContentTypes> {
555
620
  installationIds: Uint8Array[],
556
621
  env?: XmtpEnv,
557
622
  gatewayHost?: string,
623
+ ): Promise<void>;
624
+ static async revokeInstallations(
625
+ signer: Signer,
626
+ inboxId: string,
627
+ installationIds: Uint8Array[],
628
+ envOrBackend?: XmtpEnv | Backend,
629
+ gatewayHost?: string,
558
630
  ) {
559
- await utilsRevokeInstallations(
560
- signer,
561
- inboxId,
562
- installationIds,
563
- env,
564
- gatewayHost,
565
- );
631
+ const backend = await resolveBackend(envOrBackend, gatewayHost);
632
+ await utilsRevokeInstallations(backend, signer, inboxId, installationIds);
566
633
  }
567
634
 
635
+ /**
636
+ * Fetches the inbox states for the specified inbox IDs from the network
637
+ * without a client
638
+ *
639
+ * @param inboxIds - The inbox IDs to get the state for
640
+ * @param backend - Optional `Backend` instance created with `createBackend()`
641
+ * @returns The inbox states for the specified inbox IDs
642
+ */
643
+ static async fetchInboxStates(
644
+ inboxIds: string[],
645
+ backend?: Backend,
646
+ ): Promise<InboxState[]>;
568
647
  /**
569
648
  * Fetches the inbox states for the specified inbox IDs from the network
570
649
  * without a client
571
650
  *
572
651
  * @param inboxIds - The inbox IDs to get the state for
573
652
  * @param env - The environment to use
653
+ * @param gatewayHost - Optional gateway host
574
654
  * @returns The inbox states for the specified inbox IDs
655
+ * @deprecated Pass a `Backend` instance created with `createBackend()` instead
656
+ * of `XmtpEnv` and `gatewayHost`.
575
657
  */
576
658
  static async fetchInboxStates(
577
659
  inboxIds: string[],
578
660
  env?: XmtpEnv,
579
661
  gatewayHost?: string,
662
+ ): Promise<InboxState[]>;
663
+ static async fetchInboxStates(
664
+ inboxIds: string[],
665
+ envOrBackend?: XmtpEnv | Backend,
666
+ gatewayHost?: string,
580
667
  ) {
581
- return utilsInboxStateFromInboxIds(inboxIds, env, gatewayHost);
668
+ const backend = await resolveBackend(envOrBackend, gatewayHost);
669
+ return utilsInboxStateFromInboxIds(backend, inboxIds);
582
670
  }
583
671
 
584
672
  /**
@@ -625,17 +713,40 @@ export class Client<ContentTypes = ExtractCodecContentTypes> {
625
713
  return this.#worker.action("client.canMessage", { identifiers });
626
714
  }
627
715
 
716
+ /**
717
+ * Checks if the specified identifiers can be messaged
718
+ *
719
+ * @param identifiers - The identifiers to check
720
+ * @param backend - Optional `Backend` instance created with `createBackend()`
721
+ * @returns Map of identifiers to whether they can be messaged
722
+ */
723
+ static async canMessage(
724
+ identifiers: Identifier[],
725
+ backend?: Backend,
726
+ ): Promise<Map<string, boolean>>;
628
727
  /**
629
728
  * Checks if the specified identifiers can be messaged
630
729
  *
631
730
  * @param identifiers - The identifiers to check
632
731
  * @param env - Optional XMTP environment
633
732
  * @returns Map of identifiers to whether they can be messaged
733
+ * @deprecated Pass a `Backend` instance created with `createBackend()` instead
734
+ * of `XmtpEnv`.
634
735
  */
635
- static async canMessage(identifiers: Identifier[], env?: XmtpEnv) {
736
+ /* eslint-disable @typescript-eslint/unified-signatures */
737
+ static async canMessage(
738
+ identifiers: Identifier[],
739
+ env?: XmtpEnv,
740
+ ): Promise<Map<string, boolean>>;
741
+ /* eslint-enable @typescript-eslint/unified-signatures */
742
+ static async canMessage(
743
+ identifiers: Identifier[],
744
+ envOrBackend?: XmtpEnv | Backend,
745
+ ) {
746
+ const backend = await resolveBackend(envOrBackend);
636
747
  const canMessageMap = new Map<string, boolean>();
637
748
  for (const identifier of identifiers) {
638
- const inboxId = await getInboxIdForIdentifier(identifier, env);
749
+ const inboxId = await getInboxIdForIdentifier(backend, identifier);
639
750
  canMessageMap.set(
640
751
  identifier.identifier.toLowerCase(),
641
752
  inboxId !== undefined,
@@ -720,9 +831,14 @@ export class Client<ContentTypes = ExtractCodecContentTypes> {
720
831
  /**
721
832
  * Send a sync request to other devices on the network
722
833
  *
834
+ * @param options - Archive options specifying what to sync
835
+ * @param serverUrl - The server URL for the sync request
723
836
  * @returns Promise that resolves when the sync request is sent
724
837
  */
725
- async sendSyncRequest() {
726
- return this.#worker.action("client.sendSyncRequest");
838
+ async sendSyncRequest(options: ArchiveOptions, serverUrl: string) {
839
+ return this.#worker.action("client.sendSyncRequest", {
840
+ options,
841
+ serverUrl,
842
+ });
727
843
  }
728
844
  }
@@ -295,7 +295,7 @@ export class Conversations<ContentTypes = unknown> {
295
295
  /**
296
296
  * Creates a new group conversation with the specified inbox IDs
297
297
  *
298
- * @param inboxIds - Array of inbox IDs for group members
298
+ * @param inboxIds - Array of inbox IDs for other group members (the creator is included automatically)
299
299
  * @param options - Optional group creation options
300
300
  * @returns Promise that resolves with the new group
301
301
  */
@@ -1,11 +1,12 @@
1
1
  import {
2
2
  verifySignedWithPublicKey,
3
+ type ArchiveOptions,
3
4
  type Client,
4
5
  type Identifier,
5
6
  type KeyPackageStatus,
6
7
  type SignatureRequestHandle,
7
8
  } from "@xmtp/wasm-bindings";
8
- import type { ClientOptions } from "@/types/options";
9
+ import type { ClientOptions, XmtpEnv } from "@/types/options";
9
10
  import { createClient } from "@/utils/createClient";
10
11
  import type { SafeSigner } from "@/utils/signer";
11
12
  import { WorkerConversations } from "@/WorkerConversations";
@@ -16,10 +17,12 @@ export class WorkerClient {
16
17
  #client: Client;
17
18
  #conversations: WorkerConversations;
18
19
  #debugInformation: WorkerDebugInformation;
20
+ #env: XmtpEnv;
19
21
  #preferences: WorkerPreferences;
20
22
 
21
- constructor(client: Client) {
23
+ constructor(client: Client, env: XmtpEnv) {
22
24
  this.#client = client;
25
+ this.#env = env;
23
26
  const conversations = client.conversations();
24
27
  this.#conversations = new WorkerConversations(this, conversations);
25
28
  this.#debugInformation = new WorkerDebugInformation(client);
@@ -30,8 +33,8 @@ export class WorkerClient {
30
33
  identifier: Identifier,
31
34
  options?: Omit<ClientOptions, "codecs">,
32
35
  ) {
33
- const client = await createClient(identifier, options);
34
- return new WorkerClient(client);
36
+ const { client, env } = await createClient(identifier, options);
37
+ return new WorkerClient(client, env);
35
38
  }
36
39
 
37
40
  get libxmtpVersion() {
@@ -42,6 +45,10 @@ export class WorkerClient {
42
45
  return this.#client.appVersion;
43
46
  }
44
47
 
48
+ get env() {
49
+ return this.#env;
50
+ }
51
+
45
52
  get accountIdentifier() {
46
53
  return this.#client.accountIdentifier;
47
54
  }
@@ -144,7 +151,7 @@ export class WorkerClient {
144
151
  }
145
152
 
146
153
  async getInboxIdByIdentifier(identifier: Identifier) {
147
- return this.#client.findInboxIdByIdentifier(identifier);
154
+ return this.#client.findInboxIdByIdentity(identifier);
148
155
  }
149
156
 
150
157
  signWithInstallationKey(signatureText: string) {
@@ -185,7 +192,7 @@ export class WorkerClient {
185
192
  ) as Promise<Map<string, KeyPackageStatus>>;
186
193
  }
187
194
 
188
- async sendSyncRequest() {
189
- return this.#client.sendSyncRequest();
195
+ async sendSyncRequest(options: ArchiveOptions, serverUrl: string) {
196
+ return this.#client.device_sync().sendSyncRequest(options, serverUrl);
190
197
  }
191
198
  }
@@ -153,19 +153,19 @@ export class WorkerConversation {
153
153
  }
154
154
 
155
155
  async addMembersByIdentifiers(identifiers: Identifier[]) {
156
- return this.#group.addMembers(identifiers);
156
+ return this.#group.addMembersByIdentity(identifiers);
157
157
  }
158
158
 
159
159
  async addMembers(inboxIds: string[]) {
160
- return this.#group.addMembersByInboxId(inboxIds);
160
+ return this.#group.addMembers(inboxIds);
161
161
  }
162
162
 
163
163
  async removeMembersByIdentifiers(identifiers: Identifier[]) {
164
- return this.#group.removeMembers(identifiers);
164
+ return this.#group.removeMembersByIdentity(identifiers);
165
165
  }
166
166
 
167
167
  async removeMembers(inboxIds: string[]) {
168
- return this.#group.removeMembersByInboxId(inboxIds);
168
+ return this.#group.removeMembers(inboxIds);
169
169
  }
170
170
 
171
171
  async addAdmin(inboxId: string) {
package/src/constants.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * Pre-configured URLs for the XMTP network based on the environment
3
3
  *
4
+ * @deprecated Use `createBackend()` instead.
4
5
  * @constant
5
6
  * @property {string} local - The local URL for the XMTP network
6
7
  * @property {string} dev - The development URL for the XMTP network
@@ -24,4 +25,8 @@ export const HistorySyncUrls = {
24
25
  local: "http://localhost:5558",
25
26
  dev: "https://message-history.dev.ephemera.network",
26
27
  production: "https://message-history.production.ephemera.network",
28
+ "testnet-staging": "https://message-history.dev.ephemera.network",
29
+ "testnet-dev": "https://message-history.dev.ephemera.network",
30
+ testnet: "https://message-history.dev.ephemera.network",
31
+ mainnet: "https://message-history.production.ephemera.network",
27
32
  } as const;
package/src/index.ts CHANGED
@@ -5,6 +5,7 @@ export { Conversation } from "./Conversation";
5
5
  export { Dm } from "./Dm";
6
6
  export { Group } from "./Group";
7
7
  export { DecodedMessage } from "./DecodedMessage";
8
+ export { createBackend } from "./utils/createBackend";
8
9
  export { generateInboxId, getInboxIdForIdentifier } from "./utils/inboxId";
9
10
  export { metadataFieldName } from "./utils/metadata";
10
11
  export { ApiUrls, HistorySyncUrls } from "./constants";
@@ -16,7 +17,10 @@ export type {
16
17
  Action,
17
18
  Actions,
18
19
  ApiStats,
20
+ ArchiveOptions,
19
21
  Attachment,
22
+ Backend,
23
+ BackendBuilder,
20
24
  Consent,
21
25
  ConversationDebugInfo,
22
26
  ConversationListItem,
@@ -50,7 +54,6 @@ export type {
50
54
  Reaction,
51
55
  ReadReceipt,
52
56
  RemoteAttachment,
53
- RemoteAttachmentInfo,
54
57
  Reply,
55
58
  SendMessageOpts,
56
59
  SignatureRequestHandle,
@@ -62,6 +65,7 @@ export type {
62
65
  } from "@xmtp/wasm-bindings";
63
66
  export {
64
67
  ActionStyle,
68
+ BackupElementSelectionOption,
65
69
  ConsentEntityType,
66
70
  ConsentState,
67
71
  ContentType,
@@ -1,4 +1,8 @@
1
- import type { Identifier, KeyPackageStatus } from "@xmtp/wasm-bindings";
1
+ import type {
2
+ ArchiveOptions,
3
+ Identifier,
4
+ KeyPackageStatus,
5
+ } from "@xmtp/wasm-bindings";
2
6
  import type { ClientOptions } from "@/types/options";
3
7
  import type { SafeSigner } from "@/utils/signer";
4
8
 
@@ -8,6 +12,7 @@ export type ClientAction =
8
12
  id: string;
9
13
  result: {
10
14
  appVersion: string;
15
+ env: string;
11
16
  inboxId: string;
12
17
  installationId: string;
13
18
  installationIdBytes: Uint8Array;
@@ -216,5 +221,8 @@ export type ClientAction =
216
221
  action: "client.sendSyncRequest";
217
222
  id: string;
218
223
  result: undefined;
219
- data: undefined;
224
+ data: {
225
+ options: ArchiveOptions;
226
+ serverUrl: string;
227
+ };
220
228
  };
@@ -5,6 +5,7 @@ import type {
5
5
  import type {
6
6
  Actions,
7
7
  Attachment,
8
+ Backend,
8
9
  DeletedMessage,
9
10
  GroupUpdated,
10
11
  Intent,
@@ -17,10 +18,16 @@ import type {
17
18
  TransactionReference,
18
19
  WalletSendCalls,
19
20
  } from "@xmtp/wasm-bindings";
20
- import type { ApiUrls } from "@/constants";
21
21
  import type { DecodedMessage } from "@/DecodedMessage";
22
22
 
23
- export type XmtpEnv = keyof typeof ApiUrls;
23
+ export type XmtpEnv =
24
+ | "local"
25
+ | "dev"
26
+ | "production"
27
+ | "testnet-staging"
28
+ | "testnet-dev"
29
+ | "testnet"
30
+ | "mainnet";
24
31
 
25
32
  /**
26
33
  * Network options
@@ -35,15 +42,29 @@ export type NetworkOptions = {
35
42
  * specific endpoint
36
43
  */
37
44
  apiUrl?: string;
45
+ /**
46
+ * gatewayHost can be used to override the gateway endpoint
47
+ */
48
+ gatewayHost?: string;
49
+ /**
50
+ * Custom app version
51
+ */
52
+ appVersion?: string;
53
+ };
54
+
55
+ /**
56
+ * Device sync options
57
+ */
58
+ export type DeviceSyncOptions = {
38
59
  /**
39
60
  * historySyncUrl can be used to override the `env` flag and connect to a
40
61
  * specific endpoint for syncing history
41
62
  */
42
63
  historySyncUrl?: string | null;
43
64
  /**
44
- * gatewayHost can be used to override the gateway endpoint
65
+ * Disable device sync
45
66
  */
46
- gatewayHost?: string | null;
67
+ disableDeviceSync?: boolean;
47
68
  };
48
69
 
49
70
  export type ContentOptions = {
@@ -98,17 +119,10 @@ export type OtherOptions = {
98
119
  * Disable automatic registration when creating a client
99
120
  */
100
121
  disableAutoRegister?: boolean;
101
- /**
102
- * Disable device sync
103
- */
104
- disableDeviceSync?: boolean;
105
- /**
106
- * Custom app version
107
- */
108
- appVersion?: string;
109
122
  };
110
123
 
111
- export type ClientOptions = NetworkOptions &
124
+ export type ClientOptions = (NetworkOptions | { backend: Backend }) &
125
+ DeviceSyncOptions &
112
126
  ContentOptions &
113
127
  StorageOptions &
114
128
  OtherOptions;
@@ -0,0 +1,45 @@
1
+ import init, {
2
+ BackendBuilder,
3
+ XmtpEnv as BindingsEnv,
4
+ type Backend,
5
+ } from "@xmtp/wasm-bindings";
6
+ import type { NetworkOptions, XmtpEnv } from "@/types/options";
7
+
8
+ const envMap: Record<XmtpEnv, BindingsEnv> = {
9
+ local: BindingsEnv.Local,
10
+ dev: BindingsEnv.Dev,
11
+ production: BindingsEnv.Production,
12
+ "testnet-staging": BindingsEnv.TestnetStaging,
13
+ "testnet-dev": BindingsEnv.TestnetDev,
14
+ testnet: BindingsEnv.Testnet,
15
+ mainnet: BindingsEnv.Mainnet,
16
+ };
17
+
18
+ const reverseEnvMap: Record<BindingsEnv, XmtpEnv> = {
19
+ [BindingsEnv.Local]: "local",
20
+ [BindingsEnv.Dev]: "dev",
21
+ [BindingsEnv.Production]: "production",
22
+ [BindingsEnv.TestnetStaging]: "testnet-staging",
23
+ [BindingsEnv.TestnetDev]: "testnet-dev",
24
+ [BindingsEnv.Testnet]: "testnet",
25
+ [BindingsEnv.Mainnet]: "mainnet",
26
+ };
27
+
28
+ export const envToString = (env: BindingsEnv): XmtpEnv => {
29
+ return reverseEnvMap[env];
30
+ };
31
+
32
+ export const createBackend = async (
33
+ options?: NetworkOptions,
34
+ ): Promise<Backend> => {
35
+ await init();
36
+ const env = options?.env ?? "dev";
37
+ let builder = new BackendBuilder(envMap[env]);
38
+ // WASM builder methods consume `self` and return a new instance,
39
+ // so we must reassign from the return value.
40
+ if (options?.apiUrl) builder = builder.setApiUrl(options.apiUrl);
41
+ if (options?.gatewayHost)
42
+ builder = builder.setGatewayHost(options.gatewayHost);
43
+ if (options?.appVersion) builder = builder.setAppVersion(options.appVersion);
44
+ return builder.build();
45
+ };
@@ -1,51 +1,85 @@
1
1
  import {
2
- createClient as createWasmClient,
3
- DeviceSyncWorkerMode,
2
+ createClientWithBackend,
3
+ DeviceSyncMode,
4
4
  generateInboxId,
5
5
  getInboxIdForIdentifier,
6
+ type Backend,
6
7
  type Identifier,
7
8
  } from "@xmtp/wasm-bindings";
8
- import { ApiUrls, HistorySyncUrls } from "@/constants";
9
- import type { ClientOptions } from "@/types/options";
9
+ import type { ClientOptions, NetworkOptions } from "@/types/options";
10
+ import { createBackend, envToString } from "@/utils/createBackend";
11
+
12
+ const networkOptionKeys = [
13
+ "env",
14
+ "apiUrl",
15
+ "gatewayHost",
16
+ "appVersion",
17
+ ] as const;
18
+
19
+ const hasBackend = (options: object): options is { backend: Backend } => {
20
+ return "backend" in options;
21
+ };
22
+
23
+ const resolveBackend = async (
24
+ options?: Omit<ClientOptions, "codecs">,
25
+ ): Promise<Backend> => {
26
+ if (!options) {
27
+ return createBackend();
28
+ }
29
+
30
+ if (hasBackend(options)) {
31
+ // Validate that no NetworkOptions fields are also set
32
+ const conflicting = networkOptionKeys.filter(
33
+ (key) =>
34
+ key in options && (options as Record<string, unknown>)[key] != null,
35
+ );
36
+ if (conflicting.length > 0) {
37
+ throw new Error(
38
+ `Cannot specify both 'backend' and network options (${conflicting.join(", ")}). ` +
39
+ `Use either a pre-built Backend or network options, not both.`,
40
+ );
41
+ }
42
+ return options.backend;
43
+ }
44
+
45
+ // No backend provided — build one from NetworkOptions
46
+ return createBackend(options as NetworkOptions);
47
+ };
10
48
 
11
49
  export const createClient = async (
12
50
  identifier: Identifier,
13
51
  options?: Omit<ClientOptions, "codecs">,
14
52
  ) => {
15
- const env = options?.env || "dev";
16
- const host = options?.apiUrl || ApiUrls[env];
17
- const gatewayHost = options?.gatewayHost || undefined;
18
- const isSecure = host.startsWith("https");
53
+ const backend = await resolveBackend(options);
54
+
19
55
  const inboxId =
20
- (await getInboxIdForIdentifier(host, gatewayHost, isSecure, identifier)) ||
56
+ (await getInboxIdForIdentifier(backend, identifier)) ||
21
57
  generateInboxId(identifier);
58
+
59
+ const envString = envToString(backend.env);
60
+
22
61
  const dbPath =
23
62
  options?.dbPath === undefined
24
- ? `xmtp-${env}-${inboxId}.db3`
63
+ ? `xmtp-${envString}-${inboxId}.db3`
25
64
  : options.dbPath;
65
+
26
66
  const isLogging =
27
67
  options &&
28
68
  (options.loggingLevel !== undefined ||
29
69
  options.structuredLogging ||
30
70
  options.performanceLogging);
31
71
 
32
- const historySyncUrl =
33
- options?.historySyncUrl === undefined
34
- ? HistorySyncUrls[env]
35
- : options.historySyncUrl;
36
-
37
- const deviceSyncWorkerMode = options?.disableDeviceSync
38
- ? DeviceSyncWorkerMode.Disabled
39
- : DeviceSyncWorkerMode.Enabled;
72
+ const deviceSyncMode = options?.disableDeviceSync
73
+ ? DeviceSyncMode.Disabled
74
+ : DeviceSyncMode.Enabled;
40
75
 
41
- return createWasmClient(
42
- host,
76
+ const client = await createClientWithBackend(
77
+ backend,
43
78
  inboxId,
44
79
  identifier,
45
80
  dbPath,
46
81
  options?.dbEncryptionKey,
47
- historySyncUrl,
48
- deviceSyncWorkerMode,
82
+ deviceSyncMode,
49
83
  isLogging
50
84
  ? {
51
85
  structured: options.structuredLogging ?? false,
@@ -54,11 +88,8 @@ export const createClient = async (
54
88
  }
55
89
  : undefined,
56
90
  undefined, // allowOffline
57
- options?.appVersion,
58
- options?.gatewayHost,
59
91
  undefined, // nonce
60
- undefined, // authCallback
61
- undefined, // authHandle
62
- undefined, // clientMode
63
92
  );
93
+
94
+ return { client, env: envString };
64
95
  };