Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update DB schema, add migration #288

Merged
merged 17 commits into from
Jul 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/slimy-shrimps-attend.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@xmtp/react-sdk": patch
---

Update DB schema, add migration
2 changes: 1 addition & 1 deletion apps/react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
"@xmtp/content-type-reply": "^1.1.11",
"@xmtp/content-type-text": "^1.0.0",
"@xmtp/react-sdk": "workspace:*",
"@xmtp/xmtp-js": "^12.0.0",
"@xmtp/xmtp-js": "^12.1.0",
"date-fns": "^3.6.0",
"react": "^18.3.1",
"react-18-blockies": "^1.0.6",
Expand Down
4 changes: 2 additions & 2 deletions apps/react/src/components/library/ReactionsBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,13 @@ export const ReactionsBar: React.FC<ReactionsBarProps> = ({
{
content: emoji,
schema: "unicode",
reference: message.xmtpID,
reference: message.id,
action: "added",
},
ContentTypeReaction,
);
},
[conversation, message.xmtpID, sendMessage],
[conversation, message.id, sendMessage],
);

return (
Expand Down
10 changes: 2 additions & 8 deletions apps/react/src/components/library/ReactionsContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,19 +68,13 @@ export const ReactionsContent: React.FC<ReactionsContentProps> = ({
{
content: emoji,
schema: "unicode",
reference: message.xmtpID,
reference: message.id,
action: hasReacted ? "removed" : "added",
},
ContentTypeReaction,
);
},
[
client?.address,
conversation,
emojiReactions,
message.xmtpID,
sendMessage,
],
[client?.address, conversation, emojiReactions, message.id, sendMessage],
);

return (
Expand Down
10 changes: 1 addition & 9 deletions packages/react-sdk/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,15 +43,7 @@ yarn add react @xmtp/react-sdk @xmtp/xmtp-js @xmtp/content-type-reaction @xmtp/c

### Buffer polyfill

The Node Buffer API must be polyfilled in some cases. To do so, add the `buffer` dependency to your project and then polyfill it in your entry file.

**Example**

```ts
import { Buffer } from "buffer";

window.Buffer = window.Buffer ?? Buffer;
```
If you run into issues with Buffer and polyfills, see this [solution](https://xmtp.org/docs/faq#why-is-my-app-failing-with-a-buffer-is-not-found-error).

#### Create React App

Expand Down
4 changes: 2 additions & 2 deletions packages/react-sdk/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@
"@xmtp/content-type-reply": "^1.1.11",
"@xmtp/content-type-text": "^1.0.0",
"@xmtp/tsconfig": "workspace:*",
"@xmtp/xmtp-js": "^12.0.0",
"@xmtp/xmtp-js": "^12.1.0",
"eslint": "^8.57.0",
"eslint-config-xmtp-web": "workspace:*",
"fake-indexeddb": "^6.0.0",
Expand All @@ -120,7 +120,7 @@
"@xmtp/content-type-reaction": "^1.1.7",
"@xmtp/content-type-remote-attachment": "^1.1.8",
"@xmtp/content-type-reply": "^1.1.9",
"@xmtp/xmtp-js": "^12.0.0",
"@xmtp/xmtp-js": "^12.1.0",
"react": "^16.14.0 || ^17 || ^18"
},
"engines": {
Expand Down
34 changes: 14 additions & 20 deletions packages/react-sdk/src/contexts/XMTPContext.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import { createContext, useMemo, useState } from "react";
import { createContext, useMemo, useRef, useState } from "react";
import type { Client } from "@xmtp/xmtp-js";
import Dexie from "dexie";
import type { ContentCodec } from "@xmtp/content-type-primitives";
import type Dexie from "dexie";
import type {
ContentTypeConfiguration,
ContentTypeMessageProcessors,
ContentTypeMessageValidators,
} from "@/helpers/caching/db";
import { getDbInstance } from "@/helpers/caching/db";
import { combineNamespaces } from "@/helpers/combineNamespaces";
import { combineMessageProcessors } from "@/helpers/combineMessageProcessors";
import { combineCodecs } from "@/helpers/combineCodecs";
Expand All @@ -21,11 +20,15 @@
/**
* Content codecs used by the XMTP client instance
*/
codecs: ContentCodec<any>[];

Check warning on line 23 in packages/react-sdk/src/contexts/XMTPContext.tsx

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type
/**
* Local DB instance
* Content type configurations used by the XMTP client instance
*/
db: Dexie;
contentTypeConfigs?: ContentTypeConfiguration[];
/**
* Reference to Dexie database instance
*/
dbRef: React.MutableRefObject<Dexie | null>;
/**
* Namespaces for content types
*/
Expand All @@ -44,11 +47,10 @@
validators: ContentTypeMessageValidators;
};

const initialDb = new Dexie("__XMTP__");

export const XMTPContext = createContext<XMTPContextValue>({
codecs: [],
db: initialDb,
contentTypeConfigs: [],
dbRef: { current: null },
namespaces: {},
processors: {},
setClient: () => {},
Expand All @@ -73,6 +75,7 @@
contentTypeConfigs,
}) => {
const [client, setClient] = useState<Client | undefined>(initialClient);
const dbRef = useRef<Dexie | null>(null);

// combine all message processors
const processors = useMemo(
Expand All @@ -98,28 +101,19 @@
[contentTypeConfigs],
);

// DB instance for caching
const db = useMemo(
() =>
getDbInstance({
db: initialDb,
contentTypeConfigs,
}),
[contentTypeConfigs],
);

// memo-ize the context value to prevent unnecessary re-renders
const value = useMemo(
() => ({
client,
codecs,
db,
contentTypeConfigs: contentTypeConfigs ?? [],
dbRef,
namespaces,
processors,
setClient,
validators,
}),
[client, codecs, db, namespaces, processors, validators],
[client, codecs, contentTypeConfigs, namespaces, processors, validators],
);

return <XMTPContext.Provider value={value}>{children}</XMTPContext.Provider>;
Expand Down
22 changes: 17 additions & 5 deletions packages/react-sdk/src/helpers/caching/consent.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
const testWallet1 = createRandomWallet();
const testWallet2 = createRandomWallet();

const db = getDbInstance();
const db = await getDbInstance();

beforeEach(async () => {
await clearCache(db);
Expand All @@ -25,50 +25,59 @@ describe("Consent helpers", () => {
it("should add a new entry and update it", async () => {
const undefinedEntry = await getCachedConsentEntry(
testWallet1.account.address,
"address",
testWallet2.account.address,
db,
);
expect(undefinedEntry).toBeUndefined();
await putConsentState(
testWallet1.account.address,
"address",
testWallet2.account.address,
"allowed",
db,
);
const entry = await getCachedConsentEntry(
testWallet1.account.address,
"address",
testWallet2.account.address,
db,
);
expect(entry).toEqual({
peerAddress: testWallet2.account.address,
type: "address",
value: testWallet2.account.address,
state: "allowed",
walletAddress: testWallet1.account.address,
});
const state = await getCachedConsentState(
testWallet1.account.address,
"address",
testWallet2.account.address,
db,
);
expect(state).toBe("allowed");
await putConsentState(
testWallet1.account.address,
"address",
testWallet2.account.address,
"denied",
db,
);
const updatedEntry = await getCachedConsentEntry(
testWallet1.account.address,
"address",
testWallet2.account.address,
db,
);
expect(updatedEntry).toEqual({
peerAddress: testWallet2.account.address,
value: testWallet2.account.address,
type: "address",
state: "denied",
walletAddress: testWallet1.account.address,
});
const updatedState = await getCachedConsentState(
testWallet1.account.address,
"address",
testWallet2.account.address,
db,
);
Expand All @@ -81,12 +90,14 @@ describe("Consent helpers", () => {
await bulkPutConsentState(
[
{
peerAddress: testWallet2.account.address,
value: testWallet2.account.address,
type: "address",
state: "allowed",
walletAddress: testWallet1.account.address,
},
{
peerAddress: testWallet1.account.address,
value: testWallet1.account.address,
type: "address",
state: "denied",
walletAddress: testWallet2.account.address,
},
Expand Down Expand Up @@ -117,6 +128,7 @@ describe("Consent helpers", () => {
const testClient = await Client.create(testWallet1, { env: "local" });
await putConsentState(
testWallet1.account.address,
"address",
testWallet2.account.address,
"denied",
db,
Expand Down
35 changes: 20 additions & 15 deletions packages/react-sdk/src/helpers/caching/consent.ts
Original file line number Diff line number Diff line change
@@ -1,42 +1,45 @@
import type { Table } from "dexie";
import type Dexie from "dexie";
import { Mutex } from "async-mutex";
import type { Client, ConsentState } from "@xmtp/xmtp-js";
import type { Client, ConsentState, ConsentListEntryType } from "@xmtp/xmtp-js";
import { ConsentListEntry } from "@xmtp/xmtp-js";

export type CachedConsentEntry = {
peerAddress: string;
type: ConsentListEntryType;
value: string;
state: ConsentState;
walletAddress: string;
};

export type CachedConsentTable = Table<CachedConsentEntry, string>;

export type CachedConsentEntryMap = {
[peerAddress: string]: ConsentListEntry;
[value: string]: ConsentListEntry;
};

/**
* Retrieve a cached consent entry by wallet and peer address
* Retrieve a cached consent entry by wallet address, type, and value
*
* @returns The cached consent entry if found, otherwise `undefined`
*/
export const getCachedConsentEntry = async (
walletAddress: string,
peerAddress: string,
type: ConsentListEntryType,
value: string,
db: Dexie,
) => {
const consentTable = db.table("consent") as CachedConsentTable;
return consentTable
.where({
walletAddress,
peerAddress,
type,
value,
})
.first();
};

/**
* Retrieve all cached consent entries
* Retrieve all cached consent entries for a given wallet address
*
* @returns An array of ConsentListEntry instances
*/
Expand All @@ -47,7 +50,7 @@ export const getCachedConsentEntries = async (
const consentTable = db.table("consent") as CachedConsentTable;
const entries = await consentTable.where({ walletAddress }).toArray();
return entries.map((entry) =>
ConsentListEntry.fromAddress(entry.peerAddress, entry.state),
ConsentListEntry.fromAddress(entry.value, entry.state),
);
};

Expand All @@ -72,16 +75,17 @@ export const getCachedConsentEntriesMap = async (
};

/**
* Retrieve a cached consent state by wallet and peer address
* Retrieve a cached consent state by wallet address, type, and value
*
* @returns The cached consent state if found, otherwise `undefined`
*/
export const getCachedConsentState = async (
walletAddress: string,
peerAddress: string,
type: ConsentListEntryType,
value: string,
db: Dexie,
) => {
const entry = await getCachedConsentEntry(walletAddress, peerAddress, db);
const entry = await getCachedConsentEntry(walletAddress, type, value, db);
return entry?.state ?? "unknown";
};

Expand All @@ -97,21 +101,22 @@ export const loadConsentListFromCache = async (client: Client, db: Dexie) => {
const putConsentStateMutex = new Mutex();

/**
* Add or update a peer address's consent state
* Add or update a consent state
*/
export const putConsentState = async (
walletAddress: string,
peerAddress: string,
type: ConsentListEntryType,
value: string,
state: ConsentState,
db: Dexie,
) =>
putConsentStateMutex.runExclusive(async () => {
const consentTable = db.table("consent") as CachedConsentTable;
await consentTable.put({ peerAddress, state, walletAddress });
await consentTable.put({ type, value, state, walletAddress });
});

/**
* Add or update multiple peer addresses' consent state
* Add or update multiple consent states
*/
export const bulkPutConsentState = async (
entries: CachedConsentEntry[],
Expand Down
Loading
Loading