From 74fae6b999c6b2a76dfca52ef2f395e62ce1f5e2 Mon Sep 17 00:00:00 2001 From: Ry Racherbaumer Date: Wed, 24 Jan 2024 17:21:05 -0600 Subject: [PATCH 1/3] Add support for multiple wallets to consent --- .../src/helpers/caching/consent.test.ts | 58 +++++++++--- .../react-sdk/src/helpers/caching/consent.ts | 30 ++++-- packages/react-sdk/src/helpers/caching/db.ts | 4 +- .../react-sdk/src/hooks/useConsent.test.tsx | 14 ++- packages/react-sdk/src/hooks/useConsent.ts | 94 +++++++++++-------- 5 files changed, 136 insertions(+), 64 deletions(-) diff --git a/packages/react-sdk/src/helpers/caching/consent.test.ts b/packages/react-sdk/src/helpers/caching/consent.test.ts index b712f1ac..77adcb0d 100644 --- a/packages/react-sdk/src/helpers/caching/consent.test.ts +++ b/packages/react-sdk/src/helpers/caching/consent.test.ts @@ -24,26 +24,41 @@ describe("Consent helpers", () => { it("should add a new entry and update it", async () => { const undefinedEntry = await getCachedConsentState( testWallet1.account.address, + testWallet2.account.address, db, ); expect(undefinedEntry).toBeUndefined(); - await putConsentState(testWallet1.account.address, "allowed", db); + await putConsentState( + testWallet1.account.address, + testWallet2.account.address, + "allowed", + db, + ); const entry = await getCachedConsentState( testWallet1.account.address, + testWallet2.account.address, db, ); expect(entry).toEqual({ - peerAddress: testWallet1.account.address, + peerAddress: testWallet2.account.address, state: "allowed", + walletAddress: testWallet1.account.address, }); - await putConsentState(testWallet1.account.address, "denied", db); + await putConsentState( + testWallet1.account.address, + testWallet2.account.address, + "denied", + db, + ); const updatedEntry = await getCachedConsentState( testWallet1.account.address, + testWallet2.account.address, db, ); expect(updatedEntry).toEqual({ - peerAddress: testWallet1.account.address, + peerAddress: testWallet2.account.address, state: "denied", + walletAddress: testWallet1.account.address, }); }); }); @@ -53,31 +68,46 @@ describe("Consent helpers", () => { await bulkPutConsentState( [ { - peerAddress: testWallet1.account.address, + peerAddress: testWallet2.account.address, state: "allowed", + walletAddress: testWallet1.account.address, }, { - peerAddress: testWallet2.account.address, + peerAddress: testWallet1.account.address, state: "denied", + walletAddress: testWallet2.account.address, }, ], db, ); - const entries = await getCachedConsentEntries(db); - entries.forEach((entry) => { - expect(entry.entryType).toBe("address"); - expect(entry.permissionType).toBe( - entry.value === testWallet1.account.address ? "allowed" : "denied", - ); - }); + const entries = await getCachedConsentEntries( + testWallet1.account.address, + db, + ); + expect(entries[0].entryType).toBe("address"); + expect(entries[0].value).toBe(testWallet2.account.address); + expect(entries[0].permissionType).toBe("allowed"); + + const entries2 = await getCachedConsentEntries( + testWallet2.account.address, + db, + ); + expect(entries2[0].entryType).toBe("address"); + expect(entries2[0].value).toBe(testWallet1.account.address); + expect(entries2[0].permissionType).toBe("denied"); }); }); describe("loadConsentListFromCache", () => { it("should load consent list entries from the cache", async () => { const testClient = await Client.create(testWallet1, { env: "local" }); - await putConsentState(testWallet2.account.address, "denied", db); + await putConsentState( + testWallet1.account.address, + testWallet2.account.address, + "denied", + db, + ); await loadConsentListFromCache(testClient, db); expect(testClient.contacts.isDenied(testWallet2.account.address)).toBe( true, diff --git a/packages/react-sdk/src/helpers/caching/consent.ts b/packages/react-sdk/src/helpers/caching/consent.ts index bffc2e23..d753145c 100644 --- a/packages/react-sdk/src/helpers/caching/consent.ts +++ b/packages/react-sdk/src/helpers/caching/consent.ts @@ -7,18 +7,28 @@ import { ConsentListEntry } from "@xmtp/xmtp-js"; export type CachedConsentEntry = { peerAddress: string; state: ConsentState; + walletAddress: string; }; export type CachedConsentTable = Table; /** - * Retrieve a cached consent state by peer address + * Retrieve a cached consent state by wallet and peer address * * @returns The cached consent state if found, otherwise `undefined` */ -export const getCachedConsentState = async (peerAddress: string, db: Dexie) => { +export const getCachedConsentState = async ( + walletAddress: string, + peerAddress: string, + db: Dexie, +) => { const consentTable = db.table("consent") as CachedConsentTable; - return consentTable.where("peerAddress").equals(peerAddress).first(); + return consentTable + .where({ + walletAddress, + peerAddress, + }) + .first(); }; /** @@ -26,9 +36,14 @@ export const getCachedConsentState = async (peerAddress: string, db: Dexie) => { * * @returns An array of ConsentListEntry instances */ -export const getCachedConsentEntries = async (db: Dexie) => { +export const getCachedConsentEntries = async ( + walletAddress: string, + db: Dexie, +) => { const consentTable = db.table("consent") as CachedConsentTable; - return (await consentTable.toArray()).map((entry) => + const entries = await consentTable.where({ walletAddress }).toArray(); + + return entries.map((entry) => ConsentListEntry.fromAddress(entry.peerAddress, entry.state), ); }; @@ -37,7 +52,7 @@ export const getCachedConsentEntries = async (db: Dexie) => { * Load the cached consent list entries into the XMTP client */ export const loadConsentListFromCache = async (client: Client, db: Dexie) => { - const cachedEntries = await getCachedConsentEntries(db); + const cachedEntries = await getCachedConsentEntries(client.address, db); client.contacts.setConsentListEntries(cachedEntries); }; @@ -48,13 +63,14 @@ const putConsentStateMutex = new Mutex(); * Add or update a peer address's consent state */ export const putConsentState = async ( + walletAddress: string, peerAddress: string, state: ConsentState, db: Dexie, ) => putConsentStateMutex.runExclusive(async () => { const consentTable = db.table("consent") as CachedConsentTable; - await consentTable.put({ peerAddress, state }); + await consentTable.put({ peerAddress, state, walletAddress }); }); /** diff --git a/packages/react-sdk/src/helpers/caching/db.ts b/packages/react-sdk/src/helpers/caching/db.ts index 9b856b22..829df57c 100644 --- a/packages/react-sdk/src/helpers/caching/db.ts +++ b/packages/react-sdk/src/helpers/caching/db.ts @@ -107,8 +107,10 @@ export const getDbInstance = (options?: GetDBInstanceOptions) => { xmtpID `, consent: ` + [walletAddress+peerAddress], peerAddress, - state + state, + walletAddress `, }); } diff --git a/packages/react-sdk/src/hooks/useConsent.test.tsx b/packages/react-sdk/src/hooks/useConsent.test.tsx index 8e6f9367..1ab14e73 100644 --- a/packages/react-sdk/src/hooks/useConsent.test.tsx +++ b/packages/react-sdk/src/hooks/useConsent.test.tsx @@ -50,12 +50,14 @@ describe("useConsent", () => { await result.current.allow([testWallet2.account.address]); expect(allowSpy).toHaveBeenCalledWith([testWallet2.account.address]); const entry = await getCachedConsentState( + testWallet1.account.address, testWallet2.account.address, db, ); expect(entry).toEqual({ peerAddress: testWallet2.account.address, state: "allowed", + walletAddress: testWallet1.account.address, }); }); }); @@ -74,12 +76,14 @@ describe("useConsent", () => { await result.current.deny([testWallet2.account.address]); expect(allowSpy).toHaveBeenCalledWith([testWallet2.account.address]); const entry = await getCachedConsentState( + testWallet1.account.address, testWallet2.account.address, db, ); expect(entry).toEqual({ peerAddress: testWallet2.account.address, state: "denied", + walletAddress: testWallet1.account.address, }); }); }); @@ -100,7 +104,10 @@ describe("useConsent", () => { expect(list2[0].entryType).toEqual("address"); expect(list2[0].permissionType).toEqual("allowed"); expect(list2[0].value).toEqual(testWallet2.account.address); - const entries = await getCachedConsentEntries(db); + const entries = await getCachedConsentEntries( + testWallet1.account.address, + db, + ); expect(entries.length).toEqual(1); expect(entries[0].entryType).toEqual("address"); expect(entries[0].permissionType).toEqual("allowed"); @@ -124,7 +131,10 @@ describe("useConsent", () => { expect(list2[0].entryType).toEqual("address"); expect(list2[0].permissionType).toEqual("allowed"); expect(list2[0].value).toEqual(testWallet4.account.address); - const entries = await getCachedConsentEntries(db); + const entries = await getCachedConsentEntries( + testWallet3.account.address, + db, + ); expect(entries.length).toEqual(1); expect(entries[0].entryType).toEqual("address"); expect(entries[0].permissionType).toEqual("allowed"); diff --git a/packages/react-sdk/src/hooks/useConsent.ts b/packages/react-sdk/src/hooks/useConsent.ts index 789d9011..20ba2d9b 100644 --- a/packages/react-sdk/src/hooks/useConsent.ts +++ b/packages/react-sdk/src/hooks/useConsent.ts @@ -12,68 +12,82 @@ export const useConsent = () => { const allow = useCallback( async (addresses: string[]) => { - await client?.contacts.allow(addresses); - // update DB - await bulkPutConsentState( - addresses.map((peerAddress) => ({ - peerAddress, - state: "allowed", - })), - db, - ); + if (client?.address) { + await client?.contacts.allow(addresses); + // update DB + await bulkPutConsentState( + addresses.map((peerAddress) => ({ + peerAddress, + state: "allowed", + walletAddress: client.address, + })), + db, + ); + } }, - [client?.contacts, db], + [client?.address, client?.contacts, db], ); const deny = useCallback( async (addresses: string[]) => { - await client?.contacts.deny(addresses); - // update DB - await bulkPutConsentState( - addresses.map((peerAddress) => ({ - peerAddress, - state: "denied", - })), - db, - ); + if (client?.address) { + await client?.contacts.deny(addresses); + // update DB + await bulkPutConsentState( + addresses.map((peerAddress) => ({ + peerAddress, + state: "denied", + walletAddress: client.address, + })), + db, + ); + } }, - [client?.contacts, db], + [client?.address, client?.contacts, db], ); const loadConsentList = useCallback( async (startTime?: Date) => { - const entries = await client?.contacts.loadConsentList(startTime); + if (client?.address) { + const entries = await client?.contacts.loadConsentList(startTime); + if (entries) { + // update DB + await bulkPutConsentState( + entries.map((entry) => ({ + peerAddress: entry.value, + state: entry.permissionType, + walletAddress: client.address, + })), + db, + ); + } + return entries ?? []; + } + return []; + }, + [client?.address, client?.contacts, db], + ); + + const refreshConsentList = useCallback(async () => { + if (client?.address) { + // clear consent DB table + await db.table("consent").clear(); + const entries = await client?.contacts.refreshConsentList(); if (entries) { // update DB await bulkPutConsentState( entries.map((entry) => ({ peerAddress: entry.value, state: entry.permissionType, + walletAddress: client.address, })), db, ); } return entries ?? []; - }, - [client?.contacts, db], - ); - - const refreshConsentList = useCallback(async () => { - // clear consent DB table - await db.table("consent").clear(); - const entries = await client?.contacts.refreshConsentList(); - if (entries) { - // update DB - await bulkPutConsentState( - entries.map((entry) => ({ - peerAddress: entry.value, - state: entry.permissionType, - })), - db, - ); } - return entries ?? []; - }, [client?.contacts, db]); + return []; + }, [client?.address, client?.contacts, db]); return { allow, From a3fda9179ca375c2219ff32e9b7cb32593476dd5 Mon Sep 17 00:00:00 2001 From: Ry Racherbaumer Date: Wed, 24 Jan 2024 17:23:02 -0600 Subject: [PATCH 2/3] Create forty-otters-drop.md --- .changeset/forty-otters-drop.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/forty-otters-drop.md diff --git a/.changeset/forty-otters-drop.md b/.changeset/forty-otters-drop.md new file mode 100644 index 00000000..78e4b4d3 --- /dev/null +++ b/.changeset/forty-otters-drop.md @@ -0,0 +1,5 @@ +--- +"@xmtp/react-sdk": patch +--- + +Add support for multiple wallets to consent From 0946dfb6da33e06415148ccc4d15eb0b4f5ae579 Mon Sep 17 00:00:00 2001 From: Ry Racherbaumer Date: Thu, 25 Jan 2024 03:40:03 -0600 Subject: [PATCH 3/3] Update forty-otters-drop.md --- .changeset/forty-otters-drop.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.changeset/forty-otters-drop.md b/.changeset/forty-otters-drop.md index 78e4b4d3..5800af30 100644 --- a/.changeset/forty-otters-drop.md +++ b/.changeset/forty-otters-drop.md @@ -1,5 +1,7 @@ --- -"@xmtp/react-sdk": patch +"@xmtp/react-sdk": major --- Add support for multiple wallets to consent + +Since this changes the API of several exports, it's a breaking change that requires a major release.