@xmtp/browser-sdk 0.0.2 → 0.0.4

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
@@ -9,41 +9,48 @@ import type {
9
9
  import { TextCodec } from "@xmtp/content-type-text";
10
10
  import {
11
11
  GroupMessageKind,
12
+ SignatureRequestType,
12
13
  type ConsentEntityType,
13
- type SignatureRequestType,
14
14
  } from "@xmtp/wasm-bindings";
15
15
  import { ClientWorkerClass } from "@/ClientWorkerClass";
16
16
  import { Conversations } from "@/Conversations";
17
- import type { ClientOptions } from "@/types";
17
+ import type { ClientOptions, XmtpEnv } from "@/types";
18
18
  import {
19
19
  fromSafeEncodedContent,
20
20
  toSafeEncodedContent,
21
21
  type SafeConsent,
22
22
  type SafeMessage,
23
23
  } from "@/utils/conversions";
24
+ import { isSmartContractSigner, type Signer } from "@/utils/signer";
24
25
 
25
26
  export class Client extends ClientWorkerClass {
26
- address: string;
27
-
28
- options?: ClientOptions;
29
-
30
- #isReady = false;
31
-
27
+ #accountAddress: string;
28
+ #codecs: Map<string, ContentCodec>;
29
+ #conversations: Conversations;
30
+ #encryptionKey: Uint8Array;
32
31
  #inboxId: string | undefined;
33
-
34
32
  #installationId: string | undefined;
33
+ #isReady = false;
34
+ #signer: Signer;
35
+ options?: ClientOptions;
35
36
 
36
- #conversations: Conversations;
37
-
38
- #codecs: Map<string, ContentCodec>;
39
-
40
- constructor(address: string, options?: ClientOptions) {
37
+ constructor(
38
+ signer: Signer,
39
+ accountAddress: string,
40
+ encryptionKey: Uint8Array,
41
+ options?: ClientOptions,
42
+ ) {
41
43
  const worker = new Worker(new URL("./workers/client", import.meta.url), {
42
44
  type: "module",
43
45
  });
44
- super(worker, options?.enableLogging ?? false);
45
- this.address = address;
46
+ super(
47
+ worker,
48
+ options?.loggingLevel !== undefined && options.loggingLevel !== "off",
49
+ );
50
+ this.#accountAddress = accountAddress;
46
51
  this.options = options;
52
+ this.#encryptionKey = encryptionKey;
53
+ this.#signer = signer;
47
54
  this.#conversations = new Conversations(this);
48
55
  const codecs = [
49
56
  new GroupUpdatedCodec(),
@@ -55,9 +62,14 @@ export class Client extends ClientWorkerClass {
55
62
  );
56
63
  }
57
64
 
65
+ get accountAddress() {
66
+ return this.#accountAddress;
67
+ }
68
+
58
69
  async init() {
59
70
  const result = await this.sendMessage("init", {
60
- address: this.address,
71
+ address: this.accountAddress,
72
+ encryptionKey: this.#encryptionKey,
61
73
  options: this.options,
62
74
  });
63
75
  this.#inboxId = result.inboxId;
@@ -65,9 +77,20 @@ export class Client extends ClientWorkerClass {
65
77
  this.#isReady = true;
66
78
  }
67
79
 
68
- static async create(address: string, options?: ClientOptions) {
69
- const client = new Client(address, options);
80
+ static async create(
81
+ signer: Signer,
82
+ encryptionKey: Uint8Array,
83
+ options?: ClientOptions,
84
+ ) {
85
+ const address = await signer.getAddress();
86
+ const client = new Client(signer, address, encryptionKey, options);
87
+
70
88
  await client.init();
89
+
90
+ if (!options?.disableAutoRegister) {
91
+ await client.register();
92
+ }
93
+
71
94
  return client;
72
95
  }
73
96
 
@@ -83,34 +106,124 @@ export class Client extends ClientWorkerClass {
83
106
  return this.#installationId;
84
107
  }
85
108
 
86
- async getCreateInboxSignatureText() {
87
- return this.sendMessage("getCreateInboxSignatureText", undefined);
109
+ async #createInboxSignatureText() {
110
+ return this.sendMessage("createInboxSignatureText", undefined);
88
111
  }
89
112
 
90
- async getAddWalletSignatureText(accountAddress: string) {
91
- return this.sendMessage("getAddWalletSignatureText", { accountAddress });
113
+ async #addAccountSignatureText(newAccountAddress: string) {
114
+ return this.sendMessage("addAccountSignatureText", {
115
+ newAccountAddress,
116
+ });
92
117
  }
93
118
 
94
- async getRevokeWalletSignatureText(accountAddress: string) {
95
- return this.sendMessage("getRevokeWalletSignatureText", { accountAddress });
119
+ async #removeAccountSignatureText(accountAddress: string) {
120
+ return this.sendMessage("removeAccountSignatureText", { accountAddress });
96
121
  }
97
122
 
98
- async getRevokeInstallationsSignatureText() {
99
- return this.sendMessage("getRevokeInstallationsSignatureText", undefined);
123
+ async #revokeInstallationsSignatureText() {
124
+ return this.sendMessage("revokeInstallationsSignatureText", undefined);
100
125
  }
101
126
 
102
- async addSignature(type: SignatureRequestType, bytes: Uint8Array) {
103
- return this.sendMessage("addSignature", { type, bytes });
127
+ async #addSignature(
128
+ signatureType: SignatureRequestType,
129
+ signatureText: string,
130
+ signer: Signer,
131
+ ) {
132
+ const signature = await signer.signMessage(signatureText);
133
+
134
+ if (isSmartContractSigner(signer)) {
135
+ await this.sendMessage("addScwSignature", {
136
+ type: signatureType,
137
+ bytes: signature,
138
+ chainId: signer.getChainId(),
139
+ blockNumber: signer.getBlockNumber(),
140
+ });
141
+ } else {
142
+ await this.sendMessage("addSignature", {
143
+ type: signatureType,
144
+ bytes: signature,
145
+ });
146
+ }
104
147
  }
105
148
 
106
- async applySignatures() {
149
+ async #applySignatures() {
107
150
  return this.sendMessage("applySignatures", undefined);
108
151
  }
109
152
 
110
- async registerIdentity() {
153
+ async register() {
154
+ const signatureText = await this.#createInboxSignatureText();
155
+
156
+ // if the signature text is not available, the client is already registered
157
+ if (!signatureText) {
158
+ return;
159
+ }
160
+
161
+ await this.#addSignature(
162
+ SignatureRequestType.CreateInbox,
163
+ signatureText,
164
+ this.#signer,
165
+ );
166
+
111
167
  return this.sendMessage("registerIdentity", undefined);
112
168
  }
113
169
 
170
+ async addAccount(newAccountSigner: Signer) {
171
+ const signatureText = await this.#addAccountSignatureText(
172
+ await newAccountSigner.getAddress(),
173
+ );
174
+
175
+ if (!signatureText) {
176
+ throw new Error("Unable to generate add account signature text");
177
+ }
178
+
179
+ await this.#addSignature(
180
+ SignatureRequestType.AddWallet,
181
+ signatureText,
182
+ this.#signer,
183
+ );
184
+
185
+ await this.#addSignature(
186
+ SignatureRequestType.AddWallet,
187
+ signatureText,
188
+ newAccountSigner,
189
+ );
190
+
191
+ await this.#applySignatures();
192
+ }
193
+
194
+ async removeAccount(accountAddress: string) {
195
+ const signatureText =
196
+ await this.#removeAccountSignatureText(accountAddress);
197
+
198
+ if (!signatureText) {
199
+ throw new Error("Unable to generate remove account signature text");
200
+ }
201
+
202
+ await this.#addSignature(
203
+ SignatureRequestType.RevokeWallet,
204
+ signatureText,
205
+ this.#signer,
206
+ );
207
+
208
+ await this.#applySignatures();
209
+ }
210
+
211
+ async revokeInstallations() {
212
+ const signatureText = await this.#revokeInstallationsSignatureText();
213
+
214
+ if (!signatureText) {
215
+ throw new Error("Unable to generate revoke installations signature text");
216
+ }
217
+
218
+ await this.#addSignature(
219
+ SignatureRequestType.RevokeInstallations,
220
+ signatureText,
221
+ this.#signer,
222
+ );
223
+
224
+ await this.#applySignatures();
225
+ }
226
+
114
227
  async isRegistered() {
115
228
  return this.sendMessage("isRegistered", undefined);
116
229
  }
@@ -119,6 +232,23 @@ export class Client extends ClientWorkerClass {
119
232
  return this.sendMessage("canMessage", { accountAddresses });
120
233
  }
121
234
 
235
+ static async canMessage(accountAddresses: string[], env?: XmtpEnv) {
236
+ const accountAddress = "0x0000000000000000000000000000000000000000";
237
+ const signer: Signer = {
238
+ getAddress: () => accountAddress,
239
+ signMessage: () => new Uint8Array(),
240
+ };
241
+ const client = await Client.create(
242
+ signer,
243
+ window.crypto.getRandomValues(new Uint8Array(32)),
244
+ {
245
+ disableAutoRegister: true,
246
+ env,
247
+ },
248
+ );
249
+ return client.canMessage(accountAddresses);
250
+ }
251
+
122
252
  async findInboxIdByAddress(address: string) {
123
253
  return this.sendMessage("findInboxIdByAddress", { address });
124
254
  }
@@ -23,9 +23,10 @@ export class WorkerClient {
23
23
 
24
24
  static async create(
25
25
  accountAddress: string,
26
+ encryptionKey: Uint8Array,
26
27
  options?: Omit<ClientOptions, "codecs">,
27
28
  ) {
28
- const client = await createClient(accountAddress, options);
29
+ const client = await createClient(accountAddress, encryptionKey, options);
29
30
  return new WorkerClient(client);
30
31
  }
31
32
 
@@ -45,7 +46,7 @@ export class WorkerClient {
45
46
  return this.#client.isRegistered;
46
47
  }
47
48
 
48
- async getCreateInboxSignatureText() {
49
+ async createInboxSignatureText() {
49
50
  try {
50
51
  return await this.#client.createInboxSignatureText();
51
52
  } catch {
@@ -53,7 +54,7 @@ export class WorkerClient {
53
54
  }
54
55
  }
55
56
 
56
- async getAddWalletSignatureText(accountAddress: string) {
57
+ async addAccountSignatureText(accountAddress: string) {
57
58
  try {
58
59
  return await this.#client.addWalletSignatureText(
59
60
  this.#accountAddress,
@@ -64,7 +65,7 @@ export class WorkerClient {
64
65
  }
65
66
  }
66
67
 
67
- async getRevokeWalletSignatureText(accountAddress: string) {
68
+ async removeAccountSignatureText(accountAddress: string) {
68
69
  try {
69
70
  return await this.#client.revokeWalletSignatureText(accountAddress);
70
71
  } catch {
@@ -72,7 +73,7 @@ export class WorkerClient {
72
73
  }
73
74
  }
74
75
 
75
- async getRevokeInstallationsSignatureText() {
76
+ async revokeInstallationsSignatureText() {
76
77
  try {
77
78
  return await this.#client.revokeInstallationsSignatureText();
78
79
  } catch {
@@ -84,6 +85,15 @@ export class WorkerClient {
84
85
  return this.#client.addSignature(type, bytes);
85
86
  }
86
87
 
88
+ async addScwSignature(
89
+ type: SignatureRequestType,
90
+ bytes: Uint8Array,
91
+ chainId: bigint,
92
+ blockNumber?: bigint,
93
+ ) {
94
+ return this.#client.addScwSignature(type, bytes, chainId, blockNumber);
95
+ }
96
+
87
97
  async applySignatures() {
88
98
  return this.#client.applySignatureRequests();
89
99
  }
@@ -2,12 +2,12 @@ import type {
2
2
  ConsentState,
3
3
  Conversation,
4
4
  EncodedContent,
5
+ GroupMember,
5
6
  } from "@xmtp/wasm-bindings";
6
7
  import {
7
8
  fromSafeListMessagesOptions,
8
9
  toSafeGroupMember,
9
10
  type SafeListMessagesOptions,
10
- type WasmGroupMember,
11
11
  } from "@/utils/conversions";
12
12
  import type { WorkerClient } from "@/WorkerClient";
13
13
 
@@ -73,13 +73,13 @@ export class WorkerConversation {
73
73
  get metadata() {
74
74
  const metadata = this.#group.groupMetadata();
75
75
  return {
76
- creatorInboxId: metadata.creator_inbox_id(),
77
- conversationType: metadata.conversation_type(),
76
+ creatorInboxId: metadata.creatorInboxId(),
77
+ conversationType: metadata.conversationType(),
78
78
  };
79
79
  }
80
80
 
81
81
  async members() {
82
- const members = (await this.#group.listMembers()) as WasmGroupMember[];
82
+ const members = (await this.#group.listMembers()) as GroupMember[];
83
83
  return members.map((member) => toSafeGroupMember(member));
84
84
  }
85
85
 
package/src/index.ts CHANGED
@@ -1,9 +1,41 @@
1
1
  export { Client } from "./Client";
2
2
  export { Conversations } from "./Conversations";
3
3
  export { Conversation } from "./Conversation";
4
- export * from "./DecodedMessage";
4
+ export type { MessageDeliveryStatus, MessageKind } from "./DecodedMessage";
5
+ export { DecodedMessage } from "./DecodedMessage";
5
6
  export { Utils } from "./Utils";
6
7
  export { ApiUrls } from "./constants";
7
8
  export type * from "./types";
8
9
  export * from "./utils/conversions";
9
- export type * from "@xmtp/wasm-bindings";
10
+ export {
11
+ ConsentEntityType,
12
+ ConsentState,
13
+ ConversationType,
14
+ CreateGroupOptions,
15
+ DeliveryStatus,
16
+ GroupMembershipState,
17
+ EncodedContent,
18
+ GroupMember,
19
+ GroupMetadata,
20
+ GroupPermissions,
21
+ GroupMessageKind,
22
+ GroupPermissionsOptions,
23
+ InboxState,
24
+ Installation,
25
+ ListConversationsOptions,
26
+ ListMessagesOptions,
27
+ Message,
28
+ PermissionLevel,
29
+ PermissionPolicy,
30
+ PermissionPolicySet,
31
+ PermissionUpdateType,
32
+ SignatureRequestType,
33
+ SortDirection,
34
+ Consent,
35
+ ContentTypeId,
36
+ } from "@xmtp/wasm-bindings";
37
+ export {
38
+ isSmartContractSigner,
39
+ type Signer,
40
+ type SmartContractSigner,
41
+ } from "./utils/signer";
@@ -38,25 +38,26 @@ export type ClientEvents =
38
38
  };
39
39
  data: {
40
40
  address: string;
41
+ encryptionKey: Uint8Array;
41
42
  options?: ClientOptions;
42
43
  };
43
44
  }
44
45
  | {
45
- action: "getCreateInboxSignatureText";
46
+ action: "createInboxSignatureText";
46
47
  id: string;
47
48
  result: string | undefined;
48
49
  data: undefined;
49
50
  }
50
51
  | {
51
- action: "getAddWalletSignatureText";
52
+ action: "addAccountSignatureText";
52
53
  id: string;
53
54
  result: string | undefined;
54
55
  data: {
55
- accountAddress: string;
56
+ newAccountAddress: string;
56
57
  };
57
58
  }
58
59
  | {
59
- action: "getRevokeWalletSignatureText";
60
+ action: "removeAccountSignatureText";
60
61
  id: string;
61
62
  result: string | undefined;
62
63
  data: {
@@ -64,7 +65,7 @@ export type ClientEvents =
64
65
  };
65
66
  }
66
67
  | {
67
- action: "getRevokeInstallationsSignatureText";
68
+ action: "revokeInstallationsSignatureText";
68
69
  id: string;
69
70
  result: string | undefined;
70
71
  data: undefined;
@@ -78,6 +79,17 @@ export type ClientEvents =
78
79
  bytes: Uint8Array;
79
80
  };
80
81
  }
82
+ | {
83
+ action: "addScwSignature";
84
+ id: string;
85
+ result: undefined;
86
+ data: {
87
+ type: SignatureRequestType;
88
+ bytes: Uint8Array;
89
+ chainId: bigint;
90
+ blockNumber?: bigint;
91
+ };
92
+ }
81
93
  | {
82
94
  action: "applySignatures";
83
95
  id: string;
@@ -18,16 +18,6 @@ export type NetworkOptions = {
18
18
  apiUrl?: string;
19
19
  };
20
20
 
21
- /**
22
- * Encryption options
23
- */
24
- export type EncryptionOptions = {
25
- /**
26
- * Encryption key to use for the local DB
27
- */
28
- encryptionKey?: Uint8Array;
29
- };
30
-
31
21
  /**
32
22
  * Storage options
33
23
  */
@@ -47,13 +37,24 @@ export type ContentOptions = {
47
37
 
48
38
  export type OtherOptions = {
49
39
  /**
50
- * Enable logging of events between the client and worker
40
+ * Enable structured JSON logging
41
+ */
42
+ structuredLogging?: boolean;
43
+ /**
44
+ * Enable performance metrics
45
+ */
46
+ performanceLogging?: boolean;
47
+ /**
48
+ * Logging level
49
+ */
50
+ loggingLevel?: "off" | "error" | "warn" | "info" | "debug" | "trace";
51
+ /**
52
+ * Disable automatic registration when creating a client
51
53
  */
52
- enableLogging?: boolean;
54
+ disableAutoRegister?: boolean;
53
55
  };
54
56
 
55
57
  export type ClientOptions = NetworkOptions &
56
- EncryptionOptions &
57
58
  StorageOptions &
58
59
  ContentOptions &
59
60
  OtherOptions;
@@ -341,36 +341,12 @@ export type SafeGroupMember = {
341
341
  permissionLevel: PermissionLevel;
342
342
  };
343
343
 
344
- export class WasmGroupMember {
345
- account_addresses: string[];
346
- consent_state: ConsentState;
347
- inbox_id: string;
348
- installation_ids: string[];
349
- permission_level: PermissionLevel;
350
-
351
- constructor(
352
- inbox_id: string,
353
- account_addresses: string[],
354
- installation_ids: string[],
355
- permission_level: PermissionLevel,
356
- consent_state: ConsentState,
357
- ) {
358
- this.inbox_id = inbox_id;
359
- this.account_addresses = account_addresses;
360
- this.installation_ids = installation_ids;
361
- this.permission_level = permission_level;
362
- this.consent_state = consent_state;
363
- }
364
- }
365
-
366
- export const toSafeGroupMember = (
367
- member: WasmGroupMember,
368
- ): SafeGroupMember => ({
369
- accountAddresses: member.account_addresses,
370
- consentState: member.consent_state,
371
- inboxId: member.inbox_id,
372
- installationIds: member.installation_ids,
373
- permissionLevel: member.permission_level,
344
+ export const toSafeGroupMember = (member: GroupMember): SafeGroupMember => ({
345
+ accountAddresses: member.accountAddresses,
346
+ consentState: member.consentState,
347
+ inboxId: member.inboxId,
348
+ installationIds: member.installationIds,
349
+ permissionLevel: member.permissionLevel,
374
350
  });
375
351
 
376
352
  export const fromSafeGroupMember = (member: SafeGroupMember): GroupMember =>
@@ -2,12 +2,14 @@ import init, {
2
2
  createClient as createWasmClient,
3
3
  generateInboxId,
4
4
  getInboxIdForAddress,
5
+ LogOptions,
5
6
  } from "@xmtp/wasm-bindings";
6
7
  import { ApiUrls } from "@/constants";
7
8
  import type { ClientOptions } from "@/types";
8
9
 
9
10
  export const createClient = async (
10
11
  accountAddress: string,
12
+ encryptionKey: Uint8Array,
11
13
  options?: Omit<ClientOptions, "codecs">,
12
14
  ) => {
13
15
  // initialize WASM module
@@ -25,11 +27,25 @@ export const createClient = async (
25
27
  (await getInboxIdForAddress(host, accountAddress)) ||
26
28
  generateInboxId(accountAddress);
27
29
 
30
+ const isLogging =
31
+ options &&
32
+ (options.loggingLevel !== undefined ||
33
+ options.structuredLogging ||
34
+ options.performanceLogging);
35
+
28
36
  return createWasmClient(
29
37
  host,
30
38
  inboxId,
31
39
  accountAddress,
32
40
  dbPath,
33
- options?.encryptionKey,
41
+ encryptionKey,
42
+ undefined,
43
+ isLogging
44
+ ? new LogOptions(
45
+ options.structuredLogging ?? false,
46
+ options.performanceLogging ?? false,
47
+ options.loggingLevel,
48
+ )
49
+ : undefined,
34
50
  );
35
51
  };
@@ -0,0 +1,19 @@
1
+ export type SignMessage = (message: string) => Promise<Uint8Array> | Uint8Array;
2
+ export type GetAddress = () => Promise<string> | string;
3
+ export type GetChainId = () => bigint;
4
+ export type GetBlockNumber = () => bigint;
5
+
6
+ export type Signer = {
7
+ getAddress: GetAddress;
8
+ signMessage: SignMessage;
9
+ // these fields indicate that the signer is a smart contract wallet
10
+ getBlockNumber?: GetBlockNumber;
11
+ getChainId?: GetChainId;
12
+ };
13
+
14
+ export type SmartContractSigner = Required<Signer>;
15
+
16
+ export const isSmartContractSigner = (
17
+ signer: Signer,
18
+ ): signer is SmartContractSigner =>
19
+ "getBlockNumber" in signer && "getChainId" in signer;
@@ -56,8 +56,14 @@ self.onmessage = async (event: MessageEvent<ClientEventsClientMessageData>) => {
56
56
  * Client actions
57
57
  */
58
58
  case "init":
59
- client = await WorkerClient.create(data.address, data.options);
60
- enableLogging = data.options?.enableLogging ?? false;
59
+ client = await WorkerClient.create(
60
+ data.address,
61
+ data.encryptionKey,
62
+ data.options,
63
+ );
64
+ enableLogging =
65
+ data.options?.loggingLevel !== undefined &&
66
+ data.options.loggingLevel !== "off";
61
67
  postMessage({
62
68
  id,
63
69
  action,
@@ -67,8 +73,8 @@ self.onmessage = async (event: MessageEvent<ClientEventsClientMessageData>) => {
67
73
  },
68
74
  });
69
75
  break;
70
- case "getCreateInboxSignatureText": {
71
- const result = await client.getCreateInboxSignatureText();
76
+ case "createInboxSignatureText": {
77
+ const result = await client.createInboxSignatureText();
72
78
  postMessage({
73
79
  id,
74
80
  action,
@@ -76,9 +82,9 @@ self.onmessage = async (event: MessageEvent<ClientEventsClientMessageData>) => {
76
82
  });
77
83
  break;
78
84
  }
79
- case "getAddWalletSignatureText": {
80
- const result = await client.getAddWalletSignatureText(
81
- data.accountAddress,
85
+ case "addAccountSignatureText": {
86
+ const result = await client.addAccountSignatureText(
87
+ data.newAccountAddress,
82
88
  );
83
89
  postMessage({
84
90
  id,
@@ -87,8 +93,8 @@ self.onmessage = async (event: MessageEvent<ClientEventsClientMessageData>) => {
87
93
  });
88
94
  break;
89
95
  }
90
- case "getRevokeWalletSignatureText": {
91
- const result = await client.getRevokeWalletSignatureText(
96
+ case "removeAccountSignatureText": {
97
+ const result = await client.removeAccountSignatureText(
92
98
  data.accountAddress,
93
99
  );
94
100
  postMessage({
@@ -98,8 +104,8 @@ self.onmessage = async (event: MessageEvent<ClientEventsClientMessageData>) => {
98
104
  });
99
105
  break;
100
106
  }
101
- case "getRevokeInstallationsSignatureText": {
102
- const result = await client.getRevokeInstallationsSignatureText();
107
+ case "revokeInstallationsSignatureText": {
108
+ const result = await client.revokeInstallationsSignatureText();
103
109
  postMessage({
104
110
  id,
105
111
  action,
@@ -115,6 +121,19 @@ self.onmessage = async (event: MessageEvent<ClientEventsClientMessageData>) => {
115
121
  result: undefined,
116
122
  });
117
123
  break;
124
+ case "addScwSignature":
125
+ await client.addScwSignature(
126
+ data.type,
127
+ data.bytes,
128
+ data.chainId,
129
+ data.blockNumber,
130
+ );
131
+ postMessage({
132
+ id,
133
+ action,
134
+ result: undefined,
135
+ });
136
+ break;
118
137
  case "applySignatures":
119
138
  await client.applySignatures();
120
139
  postMessage({