-
Notifications
You must be signed in to change notification settings - Fork 43
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
13 changed files
with
577 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
# React Vite example app | ||
|
||
Use this React Vite example app as a tool to start building an app with XMTP. This basic messaging app has an intentionally unopinionated UI to help make it easier for you to build with. | ||
|
||
The app is built using the [React XMTP client SDK](/packages/react-sdk/README.md), [React](https://react.dev/), [Vite](https://vitejs.dev/), and [RainbowKit](https://www.rainbowkit.com/). | ||
|
||
To keep up with the latest example app developments, see the [Issues tab](https://github.com/xmtp/xmtp-web/issues) in this repo. | ||
|
||
To learn more about XMTP and get answers to frequently asked questions, see the [XMTP documentation](https://xmtp.org/docs). | ||
|
||
## Limitations | ||
|
||
This example app isn't a complete solution. For example, the list of conversations doesn't update when new messages arrive in existing conversations. | ||
|
||
## Developing | ||
|
||
1. In `packages/react-sdk`, run `yarn build` to build the React SDK. | ||
2. In `examples/react-vite`, run `yarn dev` to start the development server. | ||
|
||
## Useful commands | ||
|
||
- `yarn build`: Builds the example app | ||
- `yarn clean`: Removes `node_modules`, `dist`, and `.turbo` folders | ||
- `yarn dev`: Launches the example app and watches for changes, which will trigger a rebuild | ||
- `yarn format`: Runs prettier format and write changes | ||
- `yarn format:check`: Runs prettier format check | ||
- `yarn lint`: Runs ESLint | ||
- `yarn typecheck`: Runs `tsc` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
<!doctype html> | ||
<html lang="en"> | ||
<head> | ||
<meta charset="UTF-8" /> | ||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||
<title>XMTP V3 Browser SDK Example</title> | ||
</head> | ||
<body> | ||
<div id="root"></div> | ||
<script type="module" src="/src/main.tsx"></script> | ||
</body> | ||
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
{ | ||
"name": "@xmtp/react-vite-browser-sdk-example", | ||
"version": "0.0.0", | ||
"private": true, | ||
"type": "module", | ||
"scripts": { | ||
"clean": "rm -rf .turbo && rm -rf node_modules && yarn clean:dist", | ||
"clean:dist": "rm -rf dist", | ||
"dev": "vite", | ||
"quickstart": "yarn dev", | ||
"typecheck": "tsc" | ||
}, | ||
"dependencies": { | ||
"@rainbow-me/rainbowkit": "^2.1.3", | ||
"@tanstack/react-query": "^5.51.1", | ||
"@wagmi/core": "^2.11.7", | ||
"@xmtp/browser-sdk": "workspace:*", | ||
"react": "^18.3.1", | ||
"react-dom": "^18.3.1", | ||
"viem": "^2.17.4", | ||
"wagmi": "^2.10.10" | ||
}, | ||
"devDependencies": { | ||
"@types/react": "^18.3.3", | ||
"@types/react-dom": "^18.3.0", | ||
"@vitejs/plugin-react": "^4.3.1", | ||
"autoprefixer": "^10.4.19", | ||
"postcss": "^8.4.39", | ||
"postcss-preset-env": "^9.6.0", | ||
"tsconfig": "workspace:*", | ||
"typescript": "^5.5.3", | ||
"vite": "^5.4.9" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
module.exports = { | ||
plugins: { | ||
"postcss-preset-env": {}, | ||
}, | ||
}; |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,253 @@ | ||
import { | ||
Conversation, | ||
type Client, | ||
type DecodedMessage, | ||
} from "@xmtp/browser-sdk"; | ||
import { useState } from "react"; | ||
import { createClient } from "./createClient"; | ||
|
||
export const App = () => { | ||
const [client, setClient] = useState<Client | undefined>(undefined); | ||
const [conversations, setConversations] = useState<Conversation[]>([]); | ||
const [messages, setMessages] = useState<Map<string, DecodedMessage[]>>( | ||
new Map(), | ||
); | ||
|
||
const handleCreateClient = async () => { | ||
setClient(await createClient("key1")); | ||
}; | ||
|
||
const handleResetClient = () => { | ||
if (client) { | ||
client.close(); | ||
} | ||
setClient(undefined); | ||
setConversations([]); | ||
setMessages(new Map()); | ||
}; | ||
|
||
const handleListGroups = async () => { | ||
if (client) { | ||
const groups = await client.conversations.list(); | ||
setConversations(groups); | ||
} | ||
}; | ||
|
||
const handleUpdateGroupName = async (groupId: string, elementId: string) => { | ||
if (client) { | ||
const conversation = new Conversation(client, groupId); | ||
await conversation.sync(); | ||
const element = document.getElementById(elementId) as HTMLInputElement; | ||
const name = element.value; | ||
await conversation.updateName(name); | ||
element.value = ""; | ||
await handleListGroups(); | ||
} | ||
}; | ||
|
||
const handleUpdateGroupDescription = async ( | ||
groupId: string, | ||
elementId: string, | ||
) => { | ||
if (client) { | ||
const conversation = new Conversation(client, groupId); | ||
await conversation.sync(); | ||
const element = document.getElementById(elementId) as HTMLInputElement; | ||
const description = element.value; | ||
await conversation.updateDescription(description); | ||
element.value = ""; | ||
await handleListGroups(); | ||
} | ||
}; | ||
|
||
const handleListGroupMessages = async (groupId: string) => { | ||
if (client) { | ||
const conversation = new Conversation(client, groupId); | ||
await conversation.sync(); | ||
const groupMessages = await conversation.messages(); | ||
setMessages((prevMessages) => { | ||
const newMessages = new Map(prevMessages); | ||
newMessages.set(groupId, groupMessages); | ||
return newMessages; | ||
}); | ||
} | ||
}; | ||
|
||
const handleSendGroupMessage = async (groupId: string, elementId: string) => { | ||
if (client) { | ||
const conversation = new Conversation(client, groupId); | ||
await conversation.sync(); | ||
const element = document.getElementById(elementId) as HTMLInputElement; | ||
const message = element.value; | ||
await conversation.send(message); | ||
element.value = ""; | ||
} | ||
}; | ||
|
||
const handleCreateGroup = async () => { | ||
if (client) { | ||
const element = document.getElementById( | ||
"create-group-name", | ||
) as HTMLInputElement; | ||
const name = element.value; | ||
const group = await client.conversations.newGroup([]); | ||
await group.sync(); | ||
await group.updateName(name); | ||
element.value = ""; | ||
await handleListGroups(); | ||
} | ||
}; | ||
|
||
const handleSyncGroup = async (groupId: string) => { | ||
if (client) { | ||
const conversation = new Conversation(client, groupId); | ||
await conversation.sync(); | ||
await handleListGroupMessages(groupId); | ||
} | ||
}; | ||
|
||
return ( | ||
<div className="App"> | ||
<h1>XMTP V3</h1> | ||
<div className="Actions"> | ||
{!client && ( | ||
<button onClick={() => void handleCreateClient()} type="button"> | ||
Create client | ||
</button> | ||
)} | ||
{client && ( | ||
<> | ||
<button | ||
onClick={() => { | ||
handleResetClient(); | ||
}} | ||
type="button"> | ||
Reset client | ||
</button> | ||
<button onClick={() => void handleListGroups()} type="button"> | ||
List groups | ||
</button> | ||
</> | ||
)} | ||
</div> | ||
{client && ( | ||
<> | ||
<div className="Client"> | ||
<h2>Client details</h2> | ||
<div className="ClientDetail"> | ||
<div>Address:</div> | ||
<div>{client.address}</div> | ||
</div> | ||
<div className="ClientDetail"> | ||
<div>Inbox ID:</div> | ||
<div>{client.inboxId}</div> | ||
</div> | ||
<div className="ClientDetail"> | ||
<div>Installation ID:</div> | ||
<div>{client.installationId}</div> | ||
</div> | ||
</div> | ||
<div className="ConversationActions"> | ||
<div className="ConversationAction"> | ||
<input id="create-group-name" type="text" /> | ||
<button onClick={() => void handleCreateGroup()} type="button"> | ||
Create group | ||
</button> | ||
</div> | ||
</div> | ||
</> | ||
)} | ||
{conversations.length > 0 && ( | ||
<div className="Conversations"> | ||
<h2>Conversations</h2> | ||
<div className="ConversationWrapper"> | ||
{conversations.map((conversation) => ( | ||
<div className="Conversation" key={conversation.id}> | ||
<h3>{conversation.id}</h3> | ||
<div className="ConversationActions"> | ||
<div className="ConversationAction"> | ||
<input id={`group-name-${conversation.id}`} type="text" /> | ||
<button | ||
onClick={() => | ||
void handleUpdateGroupName( | ||
conversation.id, | ||
`group-name-${conversation.id}`, | ||
) | ||
} | ||
type="button"> | ||
Update group name | ||
</button> | ||
</div> | ||
<div className="ConversationAction"> | ||
<input | ||
id={`group-description-${conversation.id}`} | ||
type="text" | ||
/> | ||
<button | ||
onClick={() => | ||
void handleUpdateGroupDescription( | ||
conversation.id, | ||
`group-description-${conversation.id}`, | ||
) | ||
} | ||
type="button"> | ||
Update group description | ||
</button> | ||
</div> | ||
<div className="ConversationAction"> | ||
<button | ||
onClick={() => void handleSyncGroup(conversation.id)} | ||
type="button"> | ||
Sync group | ||
</button> | ||
<button | ||
onClick={() => | ||
void handleListGroupMessages(conversation.id) | ||
} | ||
type="button"> | ||
List messages | ||
</button> | ||
</div> | ||
<div className="ConversationAction"> | ||
<input | ||
id={`group-send-message-${conversation.id}`} | ||
type="text" | ||
/> | ||
<button | ||
onClick={() => | ||
void handleSendGroupMessage( | ||
conversation.id, | ||
`group-send-message-${conversation.id}`, | ||
) | ||
} | ||
type="button"> | ||
Send message | ||
</button> | ||
</div> | ||
</div> | ||
<div className="ConversationDetail"> | ||
<div>Name:</div> | ||
<div>{conversation.name}</div> | ||
</div> | ||
<div className="ConversationDetail"> | ||
<div>Description:</div> | ||
<div>{conversation.description}</div> | ||
</div> | ||
{messages.get(conversation.id) && ( | ||
<div className="ConversationMessages"> | ||
<h3>Messages</h3> | ||
{messages.get(conversation.id)?.map((message) => ( | ||
<div className="ConversationMessage" key={message.id}> | ||
<pre>{JSON.stringify(message.content, null, 2)}</pre> | ||
</div> | ||
))} | ||
</div> | ||
)} | ||
</div> | ||
))} | ||
</div> | ||
</div> | ||
)} | ||
</div> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import { Client, WasmSignatureRequestType } from "@xmtp/browser-sdk"; | ||
import { toBytes } from "viem/utils"; | ||
import { createWallet } from "./wallets"; | ||
|
||
type Wallet = ReturnType<typeof createWallet>; | ||
|
||
export const getSignature = async (client: Client, wallet: Wallet) => { | ||
const signatureText = await client.getCreateInboxSignatureText(); | ||
if (signatureText) { | ||
const signature = await wallet.signMessage({ | ||
message: signatureText, | ||
}); | ||
return toBytes(signature); | ||
} | ||
return null; | ||
}; | ||
|
||
export const createClient = async (walletKey: string) => { | ||
const wallet = createWallet(walletKey); | ||
const client = await Client.create(wallet.account.address, { | ||
env: "local", | ||
}); | ||
const isRegistered = await client.isRegistered(); | ||
if (!isRegistered) { | ||
const signature = await getSignature(client, wallet); | ||
if (signature) { | ||
await client.addSignature( | ||
WasmSignatureRequestType.CreateInbox, | ||
signature, | ||
); | ||
} | ||
await client.registerIdentity(); | ||
} | ||
return client; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
interface ImportMeta { | ||
env: { | ||
VITE_PROJECT_ID: string; | ||
}; | ||
} | ||
|
||
declare module "*.module.css" { | ||
const classes: { [key: string]: string }; | ||
export default classes; | ||
} | ||
|
||
declare module "*.png" { | ||
const src: string; | ||
export default src; | ||
} |
Oops, something went wrong.