From 6f9ac70425f432505ce094c15bb1e12b056015c5 Mon Sep 17 00:00:00 2001 From: Ry Racherbaumer Date: Fri, 1 Nov 2024 09:52:40 -0500 Subject: [PATCH 01/11] Upgrade WASM bindings --- sdks/browser-sdk/package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/sdks/browser-sdk/package.json b/sdks/browser-sdk/package.json index d5b0ca68..7c02434a 100644 --- a/sdks/browser-sdk/package.json +++ b/sdks/browser-sdk/package.json @@ -65,7 +65,7 @@ "@xmtp/content-type-primitives": "^1.0.1", "@xmtp/content-type-text": "^1.0.0", "@xmtp/proto": "^3.70.0", - "@xmtp/wasm-bindings": "^0.0.1", + "@xmtp/wasm-bindings": "^0.0.2", "uuid": "^10.0.0" }, "devDependencies": { diff --git a/yarn.lock b/yarn.lock index cbd2d21b..b62dfcb2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4569,7 +4569,7 @@ __metadata: "@xmtp/content-type-primitives": "npm:^1.0.1" "@xmtp/content-type-text": "npm:^1.0.0" "@xmtp/proto": "npm:^3.70.0" - "@xmtp/wasm-bindings": "npm:^0.0.1" + "@xmtp/wasm-bindings": "npm:^0.0.2" playwright: "npm:^1.48.1" rollup: "npm:^4.18.1" rollup-plugin-dts: "npm:^6.1.1" @@ -4960,10 +4960,10 @@ __metadata: languageName: node linkType: hard -"@xmtp/wasm-bindings@npm:^0.0.1": - version: 0.0.1 - resolution: "@xmtp/wasm-bindings@npm:0.0.1" - checksum: 10/0af5824306d9499376e5a0e0a33d4372239d7b5d7e91ad6ac131749cf4461c3a8171fe7756a1da6902871d43241b1c68202f85c887ca7ae4e480616ad968d8a4 +"@xmtp/wasm-bindings@npm:^0.0.2": + version: 0.0.2 + resolution: "@xmtp/wasm-bindings@npm:0.0.2" + checksum: 10/7aea7984776e4928185d76310c98a3c93ebc2288771ef7f385437064ce972ab9afced95a875c71d98f2389d3275b8a805d5da6cbd33aee87cd7922a13eebd5bd languageName: node linkType: hard From 77f4e996105d1b15fc65ab806e3a0a72a987eff3 Mon Sep 17 00:00:00 2001 From: Ry Racherbaumer Date: Fri, 1 Nov 2024 09:53:47 -0500 Subject: [PATCH 02/11] applySignaturesRequests => applySignatures --- sdks/browser-sdk/src/Client.ts | 4 ++-- sdks/browser-sdk/src/WorkerClient.ts | 2 +- sdks/browser-sdk/src/types/clientEvents.ts | 2 +- sdks/browser-sdk/src/workers/client.ts | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/sdks/browser-sdk/src/Client.ts b/sdks/browser-sdk/src/Client.ts index 50d395ea..b50a3e04 100644 --- a/sdks/browser-sdk/src/Client.ts +++ b/sdks/browser-sdk/src/Client.ts @@ -99,8 +99,8 @@ export class Client extends ClientWorkerClass { return this.sendMessage("addSignature", { type, bytes }); } - async applySignaturesRequests() { - return this.sendMessage("applySignaturesRequests", undefined); + async applySignatures() { + return this.sendMessage("applySignatures", undefined); } async registerIdentity() { diff --git a/sdks/browser-sdk/src/WorkerClient.ts b/sdks/browser-sdk/src/WorkerClient.ts index 8b630317..b0ca2a32 100644 --- a/sdks/browser-sdk/src/WorkerClient.ts +++ b/sdks/browser-sdk/src/WorkerClient.ts @@ -84,7 +84,7 @@ export class WorkerClient { return this.#client.addSignature(type, bytes); } - async applySignaturesRequests() { + async applySignatures() { return this.#client.applySignatureRequests(); } diff --git a/sdks/browser-sdk/src/types/clientEvents.ts b/sdks/browser-sdk/src/types/clientEvents.ts index 7b491262..1cddf034 100644 --- a/sdks/browser-sdk/src/types/clientEvents.ts +++ b/sdks/browser-sdk/src/types/clientEvents.ts @@ -79,7 +79,7 @@ export type ClientEvents = }; } | { - action: "applySignaturesRequests"; + action: "applySignatures"; id: string; result: undefined; data: undefined; diff --git a/sdks/browser-sdk/src/workers/client.ts b/sdks/browser-sdk/src/workers/client.ts index 548f8812..56799cf0 100644 --- a/sdks/browser-sdk/src/workers/client.ts +++ b/sdks/browser-sdk/src/workers/client.ts @@ -107,8 +107,8 @@ self.onmessage = async (event: MessageEvent) => { result: undefined, }); break; - case "applySignaturesRequests": - await client.applySignaturesRequests(); + case "applySignatures": + await client.applySignatures(); postMessage({ id, action, From 48368e6e395c1ee81afc01647ac377a3b21509e8 Mon Sep 17 00:00:00 2001 From: Ry Racherbaumer Date: Fri, 1 Nov 2024 09:55:54 -0500 Subject: [PATCH 03/11] Add 1:1 messaging --- sdks/browser-sdk/src/Conversation.ts | 6 ++ sdks/browser-sdk/src/Conversations.ts | 30 ++++++++++ sdks/browser-sdk/src/WorkerConversation.ts | 4 ++ sdks/browser-sdk/src/WorkerConversations.ts | 32 +++++++++++ sdks/browser-sdk/src/types/clientEvents.ts | 40 ++++++++++++++ sdks/browser-sdk/src/workers/client.ts | 61 +++++++++++++++++++++ 6 files changed, 173 insertions(+) diff --git a/sdks/browser-sdk/src/Conversation.ts b/sdks/browser-sdk/src/Conversation.ts index 01e73b38..5f46256e 100644 --- a/sdks/browser-sdk/src/Conversation.ts +++ b/sdks/browser-sdk/src/Conversation.ts @@ -282,4 +282,10 @@ export class Conversation { state, }); } + + async dmPeerInboxId() { + return this.#client.sendMessage("getDmPeerInboxId", { + id: this.#id, + }); + } } diff --git a/sdks/browser-sdk/src/Conversations.ts b/sdks/browser-sdk/src/Conversations.ts index f2c3316f..75e4b8dc 100644 --- a/sdks/browser-sdk/src/Conversations.ts +++ b/sdks/browser-sdk/src/Conversations.ts @@ -28,6 +28,12 @@ export class Conversations { }); } + async getDmByInboxId(inboxId: string) { + return this.#client.sendMessage("getDmByInboxId", { + inboxId, + }); + } + async list(options?: SafeListConversationsOptions) { const conversations = await this.#client.sendMessage("getConversations", { options, @@ -39,6 +45,22 @@ export class Conversations { ); } + async listGroups( + options?: Omit, + ) { + return this.#client.sendMessage("getGroups", { + options, + }); + } + + async listDms( + options?: Omit, + ) { + return this.#client.sendMessage("getDms", { + options, + }); + } + async newGroup(accountAddresses: string[], options?: SafeCreateGroupOptions) { const conversation = await this.#client.sendMessage("newGroup", { accountAddresses, @@ -47,4 +69,12 @@ export class Conversations { return new Conversation(this.#client, conversation.id, conversation); } + + async newDm(accountAddress: string) { + const conversation = await this.#client.sendMessage("newDm", { + accountAddress, + }); + + return new Conversation(this.#client, conversation.id, conversation); + } } diff --git a/sdks/browser-sdk/src/WorkerConversation.ts b/sdks/browser-sdk/src/WorkerConversation.ts index b92abcb2..8a2bc970 100644 --- a/sdks/browser-sdk/src/WorkerConversation.ts +++ b/sdks/browser-sdk/src/WorkerConversation.ts @@ -166,4 +166,8 @@ export class WorkerConversation { updateConsentState(state: WasmConsentState) { this.#group.update_consent_state(state); } + + dmPeerInboxId() { + return this.#group.dm_peer_inbox_id(); + } } diff --git a/sdks/browser-sdk/src/WorkerConversations.ts b/sdks/browser-sdk/src/WorkerConversations.ts index d7a631b8..1f2109de 100644 --- a/sdks/browser-sdk/src/WorkerConversations.ts +++ b/sdks/browser-sdk/src/WorkerConversations.ts @@ -43,6 +43,15 @@ export class WorkerConversations { } } + getDmByInboxId(inboxId: string) { + try { + const group = this.#conversations.find_dm_by_target_inbox_id(inboxId); + return new WorkerConversation(this.#client, group); + } catch { + return undefined; + } + } + async list(options?: SafeListConversationsOptions) { const groups = (await this.#conversations.list( options ? fromSafeListConversationsOptions(options) : undefined, @@ -50,6 +59,24 @@ export class WorkerConversations { return groups.map((group) => new WorkerConversation(this.#client, group)); } + async listGroups( + options?: Omit, + ) { + const groups = (await this.#conversations.list_groups( + options ? fromSafeListConversationsOptions(options) : undefined, + )) as WasmGroup[]; + return groups.map((group) => new WorkerConversation(this.#client, group)); + } + + async listDms( + options?: Omit, + ) { + const groups = (await this.#conversations.list_dms( + options ? fromSafeListConversationsOptions(options) : undefined, + )) as WasmGroup[]; + return groups.map((group) => new WorkerConversation(this.#client, group)); + } + async newGroup(accountAddresses: string[], options?: SafeCreateGroupOptions) { const group = await this.#conversations.create_group( accountAddresses, @@ -57,4 +84,9 @@ export class WorkerConversations { ); return new WorkerConversation(this.#client, group); } + + async newDm(accountAddress: string) { + const group = await this.#conversations.create_dm(accountAddress); + return new WorkerConversation(this.#client, group); + } } diff --git a/sdks/browser-sdk/src/types/clientEvents.ts b/sdks/browser-sdk/src/types/clientEvents.ts index 1cddf034..c61e3f99 100644 --- a/sdks/browser-sdk/src/types/clientEvents.ts +++ b/sdks/browser-sdk/src/types/clientEvents.ts @@ -164,6 +164,14 @@ export type ClientEvents = id: string; }; } + | { + action: "getDmByInboxId"; + id: string; + result: SafeConversation | undefined; + data: { + inboxId: string; + }; + } | { action: "getConversations"; id: string; @@ -172,6 +180,22 @@ export type ClientEvents = options?: SafeListConversationsOptions; }; } + | { + action: "getGroups"; + id: string; + result: SafeConversation[]; + data: { + options?: Omit; + }; + } + | { + action: "getDms"; + id: string; + result: SafeConversation[]; + data: { + options?: Omit; + }; + } | { action: "newGroup"; id: string; @@ -181,6 +205,14 @@ export type ClientEvents = options?: SafeCreateGroupOptions; }; } + | { + action: "newDm"; + id: string; + result: SafeConversation; + data: { + accountAddress: string; + }; + } | { action: "syncConversations"; id: string; @@ -399,6 +431,14 @@ export type ClientEvents = id: string; state: WasmConsentState; }; + } + | { + action: "getDmPeerInboxId"; + id: string; + result: string; + data: { + id: string; + }; }; export type ClientEventsActions = ClientEvents["action"]; diff --git a/sdks/browser-sdk/src/workers/client.ts b/sdks/browser-sdk/src/workers/client.ts index 56799cf0..ea18abf9 100644 --- a/sdks/browser-sdk/src/workers/client.ts +++ b/sdks/browser-sdk/src/workers/client.ts @@ -203,6 +203,30 @@ self.onmessage = async (event: MessageEvent) => { }); break; } + case "getGroups": { + const conversations = await client.conversations.listGroups( + data.options, + ); + postMessage({ + id, + action, + result: conversations.map((conversation) => + toSafeConversation(conversation), + ), + }); + break; + } + case "getDms": { + const conversations = await client.conversations.listDms(data.options); + postMessage({ + id, + action, + result: conversations.map((conversation) => + toSafeConversation(conversation), + ), + }); + break; + } case "newGroup": { const conversation = await client.conversations.newGroup( data.accountAddresses, @@ -215,6 +239,17 @@ self.onmessage = async (event: MessageEvent) => { }); break; } + case "newDm": { + const conversation = await client.conversations.newDm( + data.accountAddress, + ); + postMessage({ + id, + action, + result: toSafeConversation(conversation), + }); + break; + } case "syncConversations": { await client.conversations.sync(); postMessage({ @@ -242,6 +277,15 @@ self.onmessage = async (event: MessageEvent) => { }); break; } + case "getDmByInboxId": { + const conversation = client.conversations.getDmByInboxId(data.inboxId); + postMessage({ + id, + action, + result: conversation ? toSafeConversation(conversation) : undefined, + }); + break; + } /** * Group actions */ @@ -678,6 +722,23 @@ self.onmessage = async (event: MessageEvent) => { } break; } + case "getDmPeerInboxId": { + const group = client.conversations.getConversationById(data.id); + if (group) { + const result = group.dmPeerInboxId(); + postMessage({ + id, + action, + result, + }); + } else { + postMessageError({ + id, + action, + error: "Group not found", + }); + } + } } } catch (e) { postMessageError({ From ad0fa8e3f052211bda7c19c73115f186ce51ac7a Mon Sep 17 00:00:00 2001 From: Ry Racherbaumer Date: Fri, 1 Nov 2024 09:56:46 -0500 Subject: [PATCH 04/11] Add list options for messages and groups --- sdks/browser-sdk/src/utils/conversions.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/sdks/browser-sdk/src/utils/conversions.ts b/sdks/browser-sdk/src/utils/conversions.ts index 103a1056..52d88731 100644 --- a/sdks/browser-sdk/src/utils/conversions.ts +++ b/sdks/browser-sdk/src/utils/conversions.ts @@ -12,7 +12,10 @@ import { WasmListMessagesOptions, type WasmConsentEntityType, type WasmConsentState, + type WasmConversationType, type WasmDeliveryStatus, + type WasmDirection, + type WasmGroupMembershipState, type WasmGroupMessageKind, type WasmGroupPermissionsOptions, type WasmInboxState, @@ -141,6 +144,7 @@ export const toSafeMessage = (message: WasmMessage): SafeMessage => ({ export type SafeListMessagesOptions = { delivery_status?: WasmDeliveryStatus; + direction?: WasmDirection; limit?: bigint; sent_after_ns?: bigint; sent_before_ns?: bigint; @@ -150,6 +154,7 @@ export const toSafeListMessagesOptions = ( options: WasmListMessagesOptions, ): SafeListMessagesOptions => ({ delivery_status: options.delivery_status, + direction: options.direction, limit: options.limit, sent_after_ns: options.sent_after_ns, sent_before_ns: options.sent_before_ns, @@ -163,9 +168,12 @@ export const fromSafeListMessagesOptions = ( options.sent_after_ns, options.limit, options.delivery_status, + options.direction, ); export type SafeListConversationsOptions = { + allowed_states?: WasmGroupMembershipState[]; + conversation_type?: WasmConversationType; created_after_ns?: bigint; created_before_ns?: bigint; limit?: bigint; @@ -183,6 +191,8 @@ export const fromSafeListConversationsOptions = ( options: SafeListConversationsOptions, ): WasmListConversationsOptions => new WasmListConversationsOptions( + options.allowed_states, + options.conversation_type, options.created_after_ns, options.created_before_ns, options.limit, From c8a526f8f55c639c571f54aec3138eb1c8721a47 Mon Sep 17 00:00:00 2001 From: Ry Racherbaumer Date: Fri, 1 Nov 2024 13:39:53 -0500 Subject: [PATCH 05/11] Add getRevokeInstallationsSignatureText to client --- sdks/browser-sdk/src/Client.ts | 10 ++++++++-- sdks/browser-sdk/src/workers/client.ts | 9 +++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/sdks/browser-sdk/src/Client.ts b/sdks/browser-sdk/src/Client.ts index b50a3e04..089661a3 100644 --- a/sdks/browser-sdk/src/Client.ts +++ b/sdks/browser-sdk/src/Client.ts @@ -95,6 +95,10 @@ export class Client extends ClientWorkerClass { return this.sendMessage("getRevokeWalletSignatureText", { accountAddress }); } + async getRevokeInstallationsSignatureText() { + return this.sendMessage("getRevokeInstallationsSignatureText", undefined); + } + async addSignature(type: WasmSignatureRequestType, bytes: Uint8Array) { return this.sendMessage("addSignature", { type, bytes }); } @@ -119,8 +123,10 @@ export class Client extends ClientWorkerClass { return this.sendMessage("findInboxIdByAddress", { address }); } - async inboxState(refreshFromNetwork: boolean) { - return this.sendMessage("inboxState", { refreshFromNetwork }); + async inboxState(refreshFromNetwork?: boolean) { + return this.sendMessage("inboxState", { + refreshFromNetwork: refreshFromNetwork ?? false, + }); } async getLatestInboxState(inboxId: string) { diff --git a/sdks/browser-sdk/src/workers/client.ts b/sdks/browser-sdk/src/workers/client.ts index ea18abf9..35da1b29 100644 --- a/sdks/browser-sdk/src/workers/client.ts +++ b/sdks/browser-sdk/src/workers/client.ts @@ -99,6 +99,15 @@ self.onmessage = async (event: MessageEvent) => { }); break; } + case "getRevokeInstallationsSignatureText": { + const result = await client.getRevokeInstallationsSignatureText(); + postMessage({ + id, + action, + result, + }); + break; + } case "addSignature": await client.addSignature(data.type, data.bytes); postMessage({ From 0f641305e09aa180bd0ea2c3efc5951ab9a942c4 Mon Sep 17 00:00:00 2001 From: Ry Racherbaumer Date: Fri, 1 Nov 2024 13:40:26 -0500 Subject: [PATCH 06/11] Use dbPath option on client create --- sdks/browser-sdk/src/utils/createClient.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sdks/browser-sdk/src/utils/createClient.ts b/sdks/browser-sdk/src/utils/createClient.ts index e5f6cbaf..7b1e3316 100644 --- a/sdks/browser-sdk/src/utils/createClient.ts +++ b/sdks/browser-sdk/src/utils/createClient.ts @@ -14,7 +14,8 @@ export const createClient = async ( await init(); const host = options?.apiUrl ?? ApiUrls[options?.env ?? "dev"]; - const dbPath = `xmtp-${options?.env ?? "dev"}-${accountAddress}.db3`; + const dbPath = + options?.dbPath ?? `xmtp-${options?.env ?? "dev"}-${accountAddress}.db3`; const inboxId = (await getInboxIdForAddress(host, accountAddress)) || From e4fc2f56e15792aaa7d4752fe0b58823fa48d969 Mon Sep 17 00:00:00 2001 From: Ry Racherbaumer Date: Fri, 1 Nov 2024 13:41:00 -0500 Subject: [PATCH 07/11] Update browser SDK tests --- sdks/browser-sdk/test/Client.test.ts | 193 ++++++++++++++++++-- sdks/browser-sdk/test/Conversation.test.ts | 39 +++- sdks/browser-sdk/test/Conversations.test.ts | 83 ++++++++- sdks/browser-sdk/test/Utils.test.ts | 2 +- sdks/browser-sdk/test/helpers.ts | 4 +- 5 files changed, 302 insertions(+), 19 deletions(-) diff --git a/sdks/browser-sdk/test/Client.test.ts b/sdks/browser-sdk/test/Client.test.ts index 404a0a5d..d5683ccf 100644 --- a/sdks/browser-sdk/test/Client.test.ts +++ b/sdks/browser-sdk/test/Client.test.ts @@ -1,11 +1,19 @@ +import { + WasmConsentEntityType, + WasmConsentState, + WasmSignatureRequestType, +} from "@xmtp/wasm-bindings"; +import { v4 } from "uuid"; +import { toBytes } from "viem"; import { describe, expect, it } from "vitest"; +import { Conversation } from "@/Conversation"; import { createClient, createRegisteredClient, createUser, } from "@test/helpers"; -describe("Client", () => { +describe.concurrent("Client", () => { it("should create a client", async () => { const user = createUser(); const client = await createClient(user); @@ -22,9 +30,13 @@ describe("Client", () => { const client2 = await createRegisteredClient(user); expect(await client2.isRegistered()).toBe(true); expect(await client2.getCreateInboxSignatureText()).toBeUndefined(); - expect( - Object.fromEntries(await client2.canMessage([user.account.address])), - ).toEqual({ + }); + + it("should be able to message registered identity", async () => { + const user = createUser(); + const client = await createRegisteredClient(user); + const canMessage = await client.canMessage([user.account.address]); + expect(Object.fromEntries(canMessage)).toEqual({ [user.account.address.toLowerCase()]: true, }); }); @@ -48,19 +60,178 @@ describe("Client", () => { user.account.address.toLowerCase(), ]); expect(inboxState.recoveryAddress).toBe(user.account.address.toLowerCase()); + + const user2 = createUser(); + const client2 = await createClient(user2); + const inboxState2 = await client2.getLatestInboxState(client.inboxId!); + expect(inboxState2.inboxId).toBe(client.inboxId); + expect(inboxState.installations.length).toBe(1); + expect(inboxState.installations[0].id).toBe(client.installationId); + expect(inboxState2.accountAddresses).toEqual([ + user.account.address.toLowerCase(), + ]); + expect(inboxState2.recoveryAddress).toBe( + user.account.address.toLowerCase(), + ); }); - it("should get latest inbox state from inbox ID", async () => { + it("should add a wallet association to the client", async () => { const user = createUser(); + const user2 = createUser(); const client = await createRegisteredClient(user); - const inboxState = await client.getLatestInboxState(client.inboxId!); - expect(inboxState.inboxId).toBe(client.inboxId); - expect(inboxState.installations.map((install) => install.id)).toEqual([ - client.installationId, - ]); + const signatureText = await client.getAddWalletSignatureText( + user2.account.address, + ); + expect(signatureText).toBeDefined(); + + // sign message + const signature = await user.wallet.signMessage({ + message: signatureText!, + }); + const signature2 = await user2.wallet.signMessage({ + message: signatureText!, + }); + + await client.addSignature( + WasmSignatureRequestType.AddWallet, + toBytes(signature), + ); + await client.addSignature( + WasmSignatureRequestType.AddWallet, + toBytes(signature2), + ); + await client.applySignatures(); + + const inboxState = await client.inboxState(); + expect(inboxState.accountAddresses.length).toEqual(2); + expect(inboxState.accountAddresses).toContain( + user.account.address.toLowerCase(), + ); + expect(inboxState.accountAddresses).toContain( + user2.account.address.toLowerCase(), + ); + }); + + it("should revoke a wallet association from the client", async () => { + const user = createUser(); + const user2 = createUser(); + const client = await createRegisteredClient(user); + const signatureText = await client.getAddWalletSignatureText( + user2.account.address, + ); + expect(signatureText).toBeDefined(); + + // sign message + const signature = await user.wallet.signMessage({ + message: signatureText!, + }); + const signature2 = await user2.wallet.signMessage({ + message: signatureText!, + }); + + await client.addSignature( + WasmSignatureRequestType.AddWallet, + toBytes(signature), + ); + await client.addSignature( + WasmSignatureRequestType.AddWallet, + toBytes(signature2), + ); + await client.applySignatures(); + + const signatureText2 = await client.getRevokeWalletSignatureText( + user2.account.address, + ); + expect(signatureText2).toBeDefined(); + + // sign message + const signature3 = await user.wallet.signMessage({ + message: signatureText2!, + }); + + await client.addSignature( + WasmSignatureRequestType.RevokeWallet, + toBytes(signature3), + ); + await client.applySignatures(); + const inboxState = await client.inboxState(); expect(inboxState.accountAddresses).toEqual([ user.account.address.toLowerCase(), ]); - expect(inboxState.recoveryAddress).toBe(user.account.address.toLowerCase()); + }); + + it("should revoke all installations", async () => { + const user = createUser(); + + const client = await createRegisteredClient(user); + user.uuid = v4(); + const client2 = await createRegisteredClient(user); + user.uuid = v4(); + const client3 = await createRegisteredClient(user); + + const inboxState = await client3.inboxState(true); + expect(inboxState.installations.length).toBe(3); + + const installationIds = inboxState.installations.map((i) => i.id); + expect(installationIds).toContain(client.installationId); + expect(installationIds).toContain(client2.installationId); + expect(installationIds).toContain(client3.installationId); + + const signatureText = await client3.getRevokeInstallationsSignatureText(); + expect(signatureText).toBeDefined(); + + // sign message + const signature = await user.wallet.signMessage({ + message: signatureText!, + }); + + await client3.addSignature( + WasmSignatureRequestType.RevokeInstallations, + toBytes(signature), + ); + await client3.applySignatures(); + const inboxState2 = await client3.inboxState(true); + + expect(inboxState2.installations.length).toBe(1); + expect(inboxState2.installations[0].id).toBe(client3.installationId); + }); + + it("should manage consent states", async () => { + const user1 = createUser(); + const user2 = createUser(); + const client1 = await createRegisteredClient(user1); + const client2 = await createRegisteredClient(user2); + const group = await client1.conversations.newGroup([user2.account.address]); + + await client2.conversations.sync(); + const group2 = await client2.conversations.getConversationById(group.id); + + expect(group2).not.toBeNull(); + + expect( + await client2.getConsentState(WasmConsentEntityType.GroupId, group2!.id), + ).toBe(WasmConsentState.Unknown); + + await client2.setConsentStates([ + { + entityType: WasmConsentEntityType.GroupId, + entity: group2!.id, + state: WasmConsentState.Allowed, + }, + ]); + + expect( + await client2.getConsentState(WasmConsentEntityType.GroupId, group2!.id), + ).toBe(WasmConsentState.Allowed); + + const convo = new Conversation(client2, group2!.id, group2); + + expect(await convo.consentState()).toBe(WasmConsentState.Allowed); + + await convo.updateConsentState(WasmConsentState.Denied); + + expect( + await client2.getConsentState(WasmConsentEntityType.GroupId, group2!.id), + ).toBe(WasmConsentState.Denied); }); }); diff --git a/sdks/browser-sdk/test/Conversation.test.ts b/sdks/browser-sdk/test/Conversation.test.ts index 1bcd1cc6..696df11b 100644 --- a/sdks/browser-sdk/test/Conversation.test.ts +++ b/sdks/browser-sdk/test/Conversation.test.ts @@ -1,4 +1,6 @@ +import { WasmConsentState } from "@xmtp/wasm-bindings"; import { describe, expect, it } from "vitest"; +import { Conversation } from "@/Conversation"; import { ContentTypeTest, createRegisteredClient, @@ -6,7 +8,7 @@ import { TestCodec, } from "@test/helpers"; -describe("Conversation", () => { +describe.concurrent("Conversation", () => { it("should update conversation name", async () => { const user1 = createUser(); const user2 = createUser(); @@ -372,4 +374,39 @@ describe("Conversation", () => { expect(superAdmins3.length).toBe(1); expect(superAdmins3).toContain(client1.inboxId); }); + + it("should manage group consent state", async () => { + const user1 = createUser(); + const user2 = createUser(); + const user3 = createUser(); + const client1 = await createRegisteredClient(user1); + const client2 = await createRegisteredClient(user2); + const client3 = await createRegisteredClient(user3); + const group = await client1.conversations.newGroup([user2.account.address]); + expect(group).toBeDefined(); + const dmGroup = await client1.conversations.newDm(user3.account.address); + expect(dmGroup).toBeDefined(); + + await client2.conversations.sync(); + const group2 = await client2.conversations.getConversationById(group.id); + expect(group2).toBeDefined(); + + const groupConvo = new Conversation(client2, group2!.id, group2); + + expect(await groupConvo.consentState()).toBe(WasmConsentState.Unknown); + await groupConvo.send("gm!"); + expect(await groupConvo.consentState()).toBe(WasmConsentState.Allowed); + + await client3.conversations.sync(); + const dmGroup2 = await client3.conversations.getConversationById( + dmGroup.id, + ); + expect(dmGroup2).toBeDefined(); + + const dmConvo = new Conversation(client3, dmGroup2!.id, dmGroup2); + + expect(await dmConvo.consentState()).toBe(WasmConsentState.Unknown); + await dmConvo.send("gm!"); + expect(await dmConvo.consentState()).toBe(WasmConsentState.Allowed); + }); }); diff --git a/sdks/browser-sdk/test/Conversations.test.ts b/sdks/browser-sdk/test/Conversations.test.ts index b0d5ee7a..bdd2b58f 100644 --- a/sdks/browser-sdk/test/Conversations.test.ts +++ b/sdks/browser-sdk/test/Conversations.test.ts @@ -1,13 +1,18 @@ -import { WasmGroupPermissionsOptions } from "@xmtp/wasm-bindings"; +import { + WasmConsentState, + WasmGroupPermissionsOptions, +} from "@xmtp/wasm-bindings"; import { describe, expect, it } from "vitest"; import { createRegisteredClient, createUser } from "@test/helpers"; -describe("Conversations", () => { +describe.concurrent("Conversations", () => { it("should not have initial conversations", async () => { const user = createUser(); const client = await createRegisteredClient(user); - const conversations = await client.conversations.list(); - expect(conversations.length).toBe(0); + + expect((await client.conversations.list()).length).toBe(0); + expect((await client.conversations.listDms()).length).toBe(0); + expect((await client.conversations.listGroups()).length).toBe(0); }); it("should create a new conversation", async () => { @@ -19,7 +24,6 @@ describe("Conversations", () => { user2.account.address, ]); expect(conversation).toBeDefined(); - expect( (await client1.conversations.getConversationById(conversation.id))?.id, ).toBe(conversation.id); @@ -65,6 +69,75 @@ describe("Conversations", () => { const conversations2 = await client2.conversations.list(); expect(conversations2.length).toBe(1); expect(conversations2[0].id).toBe(conversation.id); + + expect((await client2.conversations.listDms()).length).toBe(0); + expect((await client2.conversations.listGroups()).length).toBe(1); + }); + + it("should create a dm group", async () => { + const user1 = createUser(); + const user2 = createUser(); + const client1 = await createRegisteredClient(user1); + const client2 = await createRegisteredClient(user2); + const group = await client1.conversations.newDm(user2.account.address); + expect(group).toBeDefined(); + expect(group.id).toBeDefined(); + expect(group.createdAtNs).toBeDefined(); + expect(group.createdAt).toBeDefined(); + expect(group.isActive).toBe(true); + expect(group.name).toBe(""); + expect(group.permissions?.policyType).toBe( + WasmGroupPermissionsOptions.CustomPolicy, + ); + expect(group.permissions?.policySet).toEqual({ + addAdminPolicy: 1, + addMemberPolicy: 1, + removeAdminPolicy: 1, + removeMemberPolicy: 1, + updateGroupDescriptionPolicy: 0, + updateGroupImageUrlSquarePolicy: 0, + updateGroupNamePolicy: 0, + updateGroupPinnedFrameUrlPolicy: 0, + }); + expect(group.addedByInboxId).toBe(client1.inboxId); + expect((await group.messages()).length).toBe(1); + const members = await group.members(); + expect(members.length).toBe(2); + const memberInboxIds = members.map((member) => member.inboxId); + expect(memberInboxIds).toContain(client1.inboxId); + expect(memberInboxIds).toContain(client2.inboxId); + expect(group.metadata?.conversationType).toBe("dm"); + expect(group.metadata?.creatorInboxId).toBe(client1.inboxId); + + expect(await group.consentState()).toBe(WasmConsentState.Allowed); + + const group1 = await client1.conversations.list(); + expect(group1.length).toBe(1); + expect(group1[0].id).toBe(group.id); + expect(await group1[0].dmPeerInboxId()).toBe(client2.inboxId); + + expect((await client1.conversations.listDms()).length).toBe(1); + expect((await client1.conversations.listGroups()).length).toBe(0); + + expect((await client2.conversations.list()).length).toBe(0); + + await client2.conversations.sync(); + + const group2 = await client2.conversations.list(); + expect(group2.length).toBe(1); + expect(group2[0].id).toBe(group.id); + expect(await group2[0].dmPeerInboxId()).toBe(client1.inboxId); + + expect((await client2.conversations.listDms()).length).toBe(1); + expect((await client2.conversations.listGroups()).length).toBe(0); + + const dm1 = await client1.conversations.getDmByInboxId(client2.inboxId!); + expect(dm1).toBeDefined(); + expect(dm1!.id).toBe(group.id); + + const dm2 = await client2.conversations.getDmByInboxId(client1.inboxId!); + expect(dm2).toBeDefined(); + expect(dm2!.id).toBe(group.id); }); it("should get a group by ID", async () => { diff --git a/sdks/browser-sdk/test/Utils.test.ts b/sdks/browser-sdk/test/Utils.test.ts index b7b7692f..18b45359 100644 --- a/sdks/browser-sdk/test/Utils.test.ts +++ b/sdks/browser-sdk/test/Utils.test.ts @@ -2,7 +2,7 @@ import { describe, expect, it } from "vitest"; import { Utils } from "@/Utils"; import { createRegisteredClient, createUser } from "@test/helpers"; -describe("Utils", () => { +describe.concurrent("Utils", () => { it("should generate inbox id", async () => { const utils = new Utils(); const inboxId = await utils.generateInboxId("0x1234"); diff --git a/sdks/browser-sdk/test/helpers.ts b/sdks/browser-sdk/test/helpers.ts index cbeeaa4c..563d1ded 100644 --- a/sdks/browser-sdk/test/helpers.ts +++ b/sdks/browser-sdk/test/helpers.ts @@ -4,6 +4,7 @@ import { type EncodedContent, } from "@xmtp/content-type-primitives"; import { WasmSignatureRequestType } from "@xmtp/wasm-bindings"; +import { v4 } from "uuid"; import { createWalletClient, http, toBytes } from "viem"; import { generatePrivateKey, privateKeyToAccount } from "viem/accounts"; import { sepolia } from "viem/chains"; @@ -21,6 +22,7 @@ export const createUser = () => { chain: sepolia, transport: http(), }), + uuid: v4(), }; }; @@ -44,7 +46,7 @@ export const createClient = async (user: User, options?: ClientOptions) => { }; return Client.create(user.account.address, { ...opts, - dbPath: `./test-${user.account.address}.db3`, + dbPath: `./test-${user.uuid}.db3`, }); }; From c8e14a77b29cd8ec2a92324f9903ca91120a8838 Mon Sep 17 00:00:00 2001 From: Ry Racherbaumer Date: Fri, 1 Nov 2024 13:41:07 -0500 Subject: [PATCH 08/11] Update Node SDK test --- sdks/node-sdk/test/Conversations.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sdks/node-sdk/test/Conversations.test.ts b/sdks/node-sdk/test/Conversations.test.ts index 6f027add..40304967 100644 --- a/sdks/node-sdk/test/Conversations.test.ts +++ b/sdks/node-sdk/test/Conversations.test.ts @@ -84,7 +84,8 @@ describe("Conversations", () => { const group = await client1.conversations.newDm(user2.account.address); expect(group).toBeDefined(); expect(group.id).toBeDefined(); - expect(group.createdAtNs).toBeTypeOf("number"); + expect(group.createdAtNs).toBeDefined(); + expect(group.createdAt).toBeDefined(); expect(group.isActive).toBe(true); expect(group.name).toBe(""); expect(group.permissions.policyType).toBe( From 8fe8d1292891927330c055c17da3dfe52dff89cd Mon Sep 17 00:00:00 2001 From: Ry Racherbaumer Date: Fri, 1 Nov 2024 14:06:42 -0500 Subject: [PATCH 09/11] Add comment about dbPath validation --- sdks/browser-sdk/src/utils/createClient.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sdks/browser-sdk/src/utils/createClient.ts b/sdks/browser-sdk/src/utils/createClient.ts index 7b1e3316..068d59bb 100644 --- a/sdks/browser-sdk/src/utils/createClient.ts +++ b/sdks/browser-sdk/src/utils/createClient.ts @@ -14,6 +14,10 @@ export const createClient = async ( await init(); const host = options?.apiUrl ?? ApiUrls[options?.env ?? "dev"]; + // TODO: add db path validation + // - must end with .db3 + // - must not contain invalid characters + // - must not start with a dot const dbPath = options?.dbPath ?? `xmtp-${options?.env ?? "dev"}-${accountAddress}.db3`; From fe97cc511d68aa898b354cecfb5b0496c1a865fc Mon Sep 17 00:00:00 2001 From: Ry Racherbaumer Date: Fri, 1 Nov 2024 14:07:00 -0500 Subject: [PATCH 10/11] Fix toSafeListConversationsOptions --- sdks/browser-sdk/src/utils/conversions.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sdks/browser-sdk/src/utils/conversions.ts b/sdks/browser-sdk/src/utils/conversions.ts index 52d88731..da5581c5 100644 --- a/sdks/browser-sdk/src/utils/conversions.ts +++ b/sdks/browser-sdk/src/utils/conversions.ts @@ -182,6 +182,8 @@ export type SafeListConversationsOptions = { export const toSafeListConversationsOptions = ( options: WasmListConversationsOptions, ): SafeListConversationsOptions => ({ + allowed_states: options.allowed_states, + conversation_type: options.conversation_type, created_after_ns: options.created_after_ns, created_before_ns: options.created_before_ns, limit: options.limit, From 04a82447e16f389d464ebccc618c7ed2ff6f44e9 Mon Sep 17 00:00:00 2001 From: Ry Racherbaumer Date: Fri, 1 Nov 2024 14:07:11 -0500 Subject: [PATCH 11/11] Add break to last case --- sdks/browser-sdk/src/workers/client.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/sdks/browser-sdk/src/workers/client.ts b/sdks/browser-sdk/src/workers/client.ts index 35da1b29..8ac3f46d 100644 --- a/sdks/browser-sdk/src/workers/client.ts +++ b/sdks/browser-sdk/src/workers/client.ts @@ -747,6 +747,7 @@ self.onmessage = async (event: MessageEvent) => { error: "Group not found", }); } + break; } } } catch (e) {