@xmtp/browser-sdk 6.5.0 → 7.0.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.
@@ -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
  };
@@ -1,10 +1,9 @@
1
1
  import init, {
2
2
  generateInboxId as wasmGenerateInboxId,
3
3
  getInboxIdForIdentifier as wasmGetInboxIdForIdentifier,
4
+ type Backend,
4
5
  type Identifier,
5
6
  } from "@xmtp/wasm-bindings";
6
- import { ApiUrls } from "@/constants";
7
- import type { XmtpEnv } from "@/types/options";
8
7
 
9
8
  /**
10
9
  * Generates an inbox ID for a given identifier
@@ -22,25 +21,16 @@ export const generateInboxId = async (
22
21
  };
23
22
 
24
23
  /**
25
- * Gets the inbox ID for a specific identifier and optional environment
24
+ * Gets the inbox ID for a specific identifier using a Backend
26
25
  *
26
+ * @param backend - The Backend instance for API communication
27
27
  * @param identifier - The identifier to get the inbox ID for
28
- * @param env - Optional XMTP environment configuration (default: "dev")
29
- * @param gatewayHost - Optional gateway host override
30
28
  * @returns Promise that resolves with the inbox ID for the identifier
31
29
  */
32
30
  export const getInboxIdForIdentifier = async (
31
+ backend: Backend,
33
32
  identifier: Identifier,
34
- env?: XmtpEnv,
35
- gatewayHost?: string,
36
33
  ): Promise<string | undefined> => {
37
34
  await init();
38
- const host = env ? ApiUrls[env] : ApiUrls.dev;
39
- const isSecure = host.startsWith("https");
40
- return wasmGetInboxIdForIdentifier(
41
- host,
42
- gatewayHost ?? null,
43
- isSecure,
44
- identifier,
45
- );
35
+ return wasmGetInboxIdForIdentifier(backend, identifier);
46
36
  };
@@ -1,23 +1,19 @@
1
1
  import init, {
2
2
  inboxStateFromInboxIds as wasmInboxStateFromInboxIds,
3
+ type Backend,
3
4
  } from "@xmtp/wasm-bindings";
4
- import { ApiUrls } from "@/constants";
5
- import type { XmtpEnv } from "@/types/options";
6
5
 
7
6
  /**
8
- * Gets the inbox state for the specified inbox IDs without a client
7
+ * Gets the inbox state for the specified inbox IDs using a Backend
9
8
  *
9
+ * @param backend - The Backend instance for API communication
10
10
  * @param inboxIds - The inbox IDs to get the state for
11
- * @param env - Optional XMTP environment configuration (default: "dev")
12
- * @param gatewayHost - Optional gateway host override
13
11
  * @returns The inbox state for the specified inbox IDs
14
12
  */
15
13
  export const inboxStateFromInboxIds = async (
14
+ backend: Backend,
16
15
  inboxIds: string[],
17
- env?: XmtpEnv,
18
- gatewayHost?: string,
19
16
  ) => {
20
17
  await init();
21
- const host = ApiUrls[env ?? "dev"];
22
- return wasmInboxStateFromInboxIds(host, gatewayHost ?? null, inboxIds);
18
+ return wasmInboxStateFromInboxIds(backend, inboxIds);
23
19
  };
@@ -1,11 +1,10 @@
1
1
  import init, {
2
2
  applySignatureRequest,
3
3
  revokeInstallationsSignatureRequest,
4
+ type Backend,
4
5
  type Identifier,
5
6
  type SignatureRequestHandle,
6
7
  } from "@xmtp/wasm-bindings";
7
- import { ApiUrls } from "@/constants";
8
- import type { XmtpEnv } from "@/types/options";
9
8
  import type { Signer } from "@/utils/signer";
10
9
 
11
10
  /**
@@ -17,28 +16,24 @@ import type { Signer } from "@/utils/signer";
17
16
  *
18
17
  * It is highly recommended to use the `revokeInstallations` function instead.
19
18
  *
19
+ * @param backend - The Backend instance for API communication
20
20
  * @param identifier - The identifier to revoke installations for
21
21
  * @param inboxId - The inbox ID to revoke installations for
22
22
  * @param installationIds - The installation IDs to revoke
23
- * @param env - Optional XMTP environment configuration (default: "dev")
24
- * @param gatewayHost - Optional gateway host override
25
23
  * @returns The signature text and signature request ID
26
24
  */
27
25
  export const revokeInstallationsSignatureText = async (
26
+ backend: Backend,
28
27
  identifier: Identifier,
29
28
  inboxId: string,
30
29
  installationIds: Uint8Array[],
31
- env?: XmtpEnv,
32
- gatewayHost?: string,
33
30
  ): Promise<{
34
31
  signatureText: string;
35
32
  signatureRequest: SignatureRequestHandle;
36
33
  }> => {
37
34
  await init();
38
- const host = ApiUrls[env ?? "dev"];
39
35
  const signatureRequest = revokeInstallationsSignatureRequest(
40
- host,
41
- gatewayHost ?? null,
36
+ backend,
42
37
  identifier,
43
38
  inboxId,
44
39
  installationIds,
@@ -50,32 +45,28 @@ export const revokeInstallationsSignatureText = async (
50
45
  /**
51
46
  * Revokes installations for a given inbox ID
52
47
  *
48
+ * @param backend - The Backend instance for API communication
53
49
  * @param signer - The signer to use
54
50
  * @param inboxId - The inbox ID to revoke installations for
55
51
  * @param installationIds - The installation IDs to revoke
56
- * @param env - Optional XMTP environment configuration (default: "dev")
57
- * @param gatewayHost - Optional gateway host override
58
52
  * @returns Promise that resolves when the revoke installations operation is complete
59
53
  */
60
54
  export const revokeInstallations = async (
55
+ backend: Backend,
61
56
  signer: Signer,
62
57
  inboxId: string,
63
58
  installationIds: Uint8Array[],
64
- env?: XmtpEnv,
65
- gatewayHost?: string,
66
59
  ): Promise<void> => {
67
60
  await init();
68
61
  const identifier = await signer.getIdentifier();
69
62
  const { signatureText, signatureRequest } =
70
63
  await revokeInstallationsSignatureText(
64
+ backend,
71
65
  identifier,
72
66
  inboxId,
73
67
  installationIds,
74
- env,
75
- gatewayHost,
76
68
  );
77
69
  const signature = await signer.signMessage(signatureText);
78
- const host = ApiUrls[env ?? "dev"];
79
70
 
80
71
  switch (signer.type) {
81
72
  case "EOA":
@@ -91,5 +82,5 @@ export const revokeInstallations = async (
91
82
  break;
92
83
  }
93
84
 
94
- await applySignatureRequest(host, gatewayHost ?? null, signatureRequest);
85
+ await applySignatureRequest(backend, signatureRequest);
95
86
  };
@@ -1,4 +1,6 @@
1
- import type { Identifier } from "@xmtp/wasm-bindings";
1
+ import { IdentifierKind, type Identifier } from "@xmtp/wasm-bindings";
2
+ import { toBytes } from "viem";
3
+ import { generatePrivateKey, privateKeyToAccount } from "viem/accounts";
2
4
 
3
5
  type SignMessage = (message: string) => Promise<Uint8Array> | Uint8Array;
4
6
  type GetIdentifier = () => Promise<Identifier> | Identifier;
@@ -36,6 +38,40 @@ export type SafeSigner =
36
38
  blockNumber?: bigint;
37
39
  };
38
40
 
41
+ export const createEOASigner = (key = generatePrivateKey()): Signer => {
42
+ const account = privateKeyToAccount(key);
43
+ return {
44
+ type: "EOA",
45
+ getIdentifier: () => ({
46
+ identifier: account.address.toLowerCase(),
47
+ identifierKind: IdentifierKind.Ethereum,
48
+ }),
49
+ signMessage: async (message: string) => {
50
+ const signature = await account.signMessage({ message });
51
+ return toBytes(signature);
52
+ },
53
+ };
54
+ };
55
+
56
+ export const createSCWSigner = (
57
+ address: `0x${string}`,
58
+ signMessage: (message: string) => Promise<string> | string,
59
+ chainId: bigint,
60
+ ): Signer => {
61
+ return {
62
+ type: "SCW",
63
+ getIdentifier: () => ({
64
+ identifier: address.toLowerCase(),
65
+ identifierKind: IdentifierKind.Ethereum,
66
+ }),
67
+ signMessage: async (message: string) => {
68
+ const signature = await signMessage(message);
69
+ return toBytes(signature);
70
+ },
71
+ getChainId: () => chainId,
72
+ };
73
+ };
74
+
39
75
  export const toSafeSigner = async (
40
76
  signer: Signer,
41
77
  signature: Uint8Array,
@@ -91,6 +91,7 @@ self.onmessage = async (
91
91
  action,
92
92
  result: {
93
93
  appVersion: maybeClient.appVersion,
94
+ env: maybeClient.env,
94
95
  inboxId: maybeClient.inboxId,
95
96
  installationId: maybeClient.installationId,
96
97
  installationIdBytes: maybeClient.installationIdBytes,
@@ -338,10 +339,45 @@ self.onmessage = async (
338
339
  break;
339
340
  }
340
341
  case "client.sendSyncRequest": {
341
- await client.sendSyncRequest();
342
+ await client.sendSyncRequest(data.options, data.serverUrl);
342
343
  postMessage({ id, action, result: undefined });
343
344
  break;
344
345
  }
346
+ case "client.sendSyncArchive": {
347
+ await client.sendSyncArchive(data.options, data.serverUrl, data.pin);
348
+ postMessage({ id, action, result: undefined });
349
+ break;
350
+ }
351
+ case "client.processSyncArchive": {
352
+ await client.processSyncArchive(data.archivePin);
353
+ postMessage({ id, action, result: undefined });
354
+ break;
355
+ }
356
+ case "client.listAvailableArchives": {
357
+ const result = client.listAvailableArchives(data.daysCutoff);
358
+ postMessage({ id, action, result });
359
+ break;
360
+ }
361
+ case "client.createArchive": {
362
+ const result = await client.createArchive(data.opts, data.key);
363
+ postMessage({ id, action, result });
364
+ break;
365
+ }
366
+ case "client.importArchive": {
367
+ await client.importArchive(data.data, data.key);
368
+ postMessage({ id, action, result: undefined });
369
+ break;
370
+ }
371
+ case "client.archiveMetadata": {
372
+ const result = await client.archiveMetadata(data.data, data.key);
373
+ postMessage({ id, action, result });
374
+ break;
375
+ }
376
+ case "client.syncAllDeviceSyncGroups": {
377
+ const result = await client.syncAllDeviceSyncGroups();
378
+ postMessage({ id, action, result });
379
+ break;
380
+ }
345
381
  /**
346
382
  * Debug information actions
347
383
  */