Skip to content

Commit 6fd7637

Browse files
fix(website): generate markdown docs and guides consistently
1 parent 5054e2c commit 6fd7637

File tree

5 files changed

+628
-44
lines changed

5 files changed

+628
-44
lines changed

website/public/guides/chat.md

Lines changed: 396 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,396 @@
1+
# Building a Realtime Chat App with Actors
2+
3+
In this guide, we're building a realtime chat application using the Rivet library. The app consists of:
4+
5+
- **ChatRoom Actor**: A server-side component that:
6+
- Uses tags to create separate chat channels
7+
- Stores the message history in persistent state
8+
- Provides methods for sending messages and retrieving history
9+
- Broadcasts new messages to all connected clients
10+
11+
- **Web Client**: A browser-based UI that:
12+
- Prompts users for username and channel name
13+
- Connects to the appropriate channel via tags
14+
- Displays the chat interface
15+
- Loads message history on connection
16+
- Shows new messages in realtime
17+
- Allows users to send messages
18+
19+
## Set up your project
20+
Create a new actor project:
21+
22+
```sh
23+
npx create-actor@latest chat-room -p rivet -t counter
24+
```
25+
26+
This command creates a new Rivet actor project with the necessary configuration files and dependencies. We're using the counter template as a starting point and will modify it for our chat application.
27+
28+
## Define the Chat Room actor
29+
Create a file called `src/chat-room.ts` and add the base class structure:
30+
31+
```typescript
32+
import { Actor, type Rpc } from "actor-core";
33+
34+
// State managed by the actor
35+
export interface State {
36+
messages: { username: string; message: string }[];
37+
}
38+
39+
export default class ChatRoom extends Actor<State> {
40+
// Methods will be added in the following steps
41+
}
42+
```
43+
44+
### Step 1: Initialize the actor state
45+
46+
First, add the `_onInitialize` method to set up the initial state:
47+
48+
```typescript
49+
export default class ChatRoom extends Actor<State> {
50+
_onInitialize() {
51+
return { messages: [] };
52+
}
53+
}
54+
```
55+
56+
This method runs when the actor is first created, initializing an empty messages array.
57+
58+
### Step 2: Add message sending functionality
59+
60+
Next, add the method to send messages:
61+
62+
```typescript
63+
export default class ChatRoom extends Actor<State> {
64+
// ...previous code...
65+
66+
sendMessage(
67+
_rpc: Rpc<ChatRoom>,
68+
username: string,
69+
message: string
70+
): void {
71+
// Save message to persistent storage
72+
this._state.messages.push({ username, message });
73+
74+
// Broadcast message to all connected clients
75+
this._broadcast("newMessage", username, message);
76+
}
77+
}
78+
```
79+
80+
This method:
81+
- Takes a username and message as parameters
82+
- Adds the message to the actor's state for persistence
83+
- Broadcasts the message to all connected clients
84+
85+
### Step 3: Add history retrieval
86+
87+
Finally, add a method to retrieve chat history:
88+
89+
```typescript
90+
export default class ChatRoom extends Actor<State> {
91+
// ...previous code...
92+
93+
getHistory(_rpc: Rpc<ChatRoom>): { username: string; message: string }[] {
94+
return this._state.messages;
95+
}
96+
}
97+
```
98+
99+
This method allows clients to fetch all previous messages when they connect.
100+
101+
```typescript
102+
import { Actor, type Rpc } from "actor-core";
103+
104+
// State managed by the actor
105+
export interface State {
106+
messages: { username: string; message: string }[];
107+
}
108+
109+
export default class ChatRoom extends Actor<State> {
110+
_onInitialize(): State {
111+
return { messages: [] };
112+
}
113+
114+
sendMessage(
115+
_rpc: Rpc<ChatRoom>,
116+
username: string,
117+
message: string
118+
): void {
119+
// Save message to persistent storage
120+
this._state.messages.push({ username, message });
121+
122+
// Broadcast message to all connected clients
123+
// Event name is 'newMessage', clients can listen for this event
124+
this._broadcast("newMessage", username, message);
125+
}
126+
127+
getHistory(_rpc: Rpc<ChatRoom>): { username: string; message: string }[] {
128+
return this._state.messages;
129+
}
130+
}
131+
```
132+
133+
### Step 4: Deploy to Rivet
134+
Deploy your actor with:
135+
136+
```sh
137+
cd chat-room
138+
npm run deploy
139+
```
140+
141+
Follow the prompts to:
142+
1. Sign in to your Rivet account
143+
2. Create or select a project
144+
3. Choose an environment
145+
146+
After deployment, you'll receive your Actor Manager URL, which clients will use to connect to your chat room.
147+
148+
## Build a web client
149+
Create a simple web client to interact with your chat room:
150+
151+
### Step 1: Create the HTML structure
152+
153+
```html
154+
<!DOCTYPE html>
155+
<html>
156+
<head>
157+
<title>Rivet Chat Room</title>
158+
<style>
159+
body { font-family: sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }
160+
#message-list { height: 400px; overflow-y: auto; list-style: none; padding: 10px; border: 1px solid #ccc; margin-bottom: 10px; }
161+
#message-form { display: flex; }
162+
#message-input { flex: 1; padding: 8px; }
163+
button { padding: 8px 16px; background: #0070f3; color: white; border: none; }
164+
</style>
165+
</head>
166+
<body>
167+
<h1>Rivet Chat Room</h1>
168+
<ul id="message-list"></ul>
169+
<form id="message-form">
170+
<input id="message-input" placeholder="Type a message..." autocomplete="off">
171+
<button type="submit">Send</button>
172+
</form>
173+
</body>
174+
</html>
175+
```
176+
177+
### Step 2: Add the client script
178+
179+
Add this script tag just before the closing `</head>` tag:
180+
181+
```html
182+
<script type="module">
183+
import { Client } from 'https://unpkg.com/actor-core/dist/browser/index.js';
184+
185+
// Replace with your Actor Manager URL from deployment
186+
const client = new Client('https://your-actor-manager-url.rivet.run');
187+
188+
let username = prompt('Enter your username:');
189+
if (!username) username = 'Anonymous';
190+
191+
let channel = prompt('Enter channel name:', 'general');
192+
if (!channel) channel = 'general';
193+
194+
async function init() {
195+
// Connect to chat room with channel tag
196+
const chatRoom = await client.get({
197+
name: 'chat-room',
198+
channel, // Use channel as a tag to separate different chat rooms
199+
});
200+
201+
// Store reference for use in event handlers
202+
// In a production app, you'd use a more encapsulated approach
203+
window.chatRoom = chatRoom;
204+
}
205+
206+
init().catch(console.error);
207+
</script>
208+
```
209+
210+
### Step 3: Load messages and listen for updates
211+
212+
Update your init function and add the addMessage helper function:
213+
214+
```html
215+
<script type="module">
216+
// ...previous code...
217+
218+
async function init() {
219+
// ...previous code...
220+
221+
try {
222+
// Load chat history
223+
const messages = await chatRoom.getHistory();
224+
messages.forEach(msg => {
225+
addMessage(msg.username, msg.message);
226+
});
227+
228+
// Listen for new messages
229+
chatRoom.on('newMessage', (username, message) => {
230+
addMessage(username, message);
231+
});
232+
} catch (error) {
233+
console.error("Failed to load chat history:", error);
234+
alert("Error loading chat history. Please try refreshing the page.");
235+
}
236+
}
237+
238+
function addMessage(username, message) {
239+
const messageList = document.getElementById('message-list');
240+
const item = document.createElement('li');
241+
242+
// Create elements instead of using innerHTML to prevent XSS
243+
const usernameSpan = document.createElement('strong');
244+
usernameSpan.textContent = username;
245+
246+
item.appendChild(usernameSpan);
247+
item.appendChild(document.createTextNode(': ' + message));
248+
249+
messageList.appendChild(item);
250+
messageList.scrollTop = messageList.scrollHeight;
251+
}
252+
</script>
253+
```
254+
255+
### Step 4: Handle sending messages
256+
257+
Add the form submit handler to your init function:
258+
259+
```html
260+
<script type="module">
261+
// ...previous code...
262+
263+
async function init() {
264+
// ...previous code...
265+
266+
// Update page title with channel name
267+
document.title = `Chat: ${channel}`;
268+
269+
// Add channel name to the UI
270+
const heading = document.querySelector('h1');
271+
heading.textContent = `Rivet Chat Room - ${channel}`;
272+
273+
// Send message on form submit
274+
document.getElementById('message-form').addEventListener('submit', async (e) => {
275+
e.preventDefault();
276+
const input = document.getElementById('message-input');
277+
const message = input.value.trim();
278+
279+
if (message) {
280+
try {
281+
await chatRoom.sendMessage(username, message);
282+
input.value = '';
283+
} catch (error) {
284+
console.error("Failed to send message:", error);
285+
alert("Error sending message. Please try again.");
286+
}
287+
}
288+
});
289+
}
290+
</script>
291+
```
292+
293+
```html
294+
<!DOCTYPE html>
295+
<html>
296+
<head>
297+
<title>Rivet Chat Room</title>
298+
<style>
299+
body { font-family: sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }
300+
#message-list { height: 400px; overflow-y: auto; list-style: none; padding: 10px; border: 1px solid #ccc; margin-bottom: 10px; }
301+
#message-form { display: flex; }
302+
#message-input { flex: 1; padding: 8px; }
303+
button { padding: 8px 16px; background: #0070f3; color: white; border: none; }
304+
</style>
305+
<script type="module">
306+
import { Client } from 'https://unpkg.com/actor-core/dist/browser/index.js';
307+
308+
// Replace with your Actor Manager URL from deployment
309+
const client = new Client('https://your-actor-manager-url.rivet.run');
310+
311+
let username = prompt('Enter your username:');
312+
if (!username) username = 'Anonymous';
313+
314+
let channel = prompt('Enter channel name:', 'general');
315+
if (!channel) channel = 'general';
316+
317+
async function init() {
318+
// Connect to chat room with channel tag
319+
const chatRoom = await client.get({
320+
name: 'chat-room',
321+
channel, // Use channel as a tag to separate different chat rooms
322+
});
323+
324+
// Store reference for use in event handlers
325+
// In a production app, you'd use a more encapsulated approach
326+
window.chatRoom = chatRoom;
327+
328+
try {
329+
// Load chat history
330+
const messages = await chatRoom.getHistory();
331+
messages.forEach(msg => {
332+
addMessage(msg.username, msg.message);
333+
});
334+
335+
// Listen for new messages
336+
chatRoom.on('newMessage', (username, message) => {
337+
addMessage(username, message);
338+
});
339+
} catch (error) {
340+
console.error("Failed to load chat history:", error);
341+
alert("Error loading chat history. Please try refreshing the page.");
342+
}
343+
344+
// Update page title with channel name
345+
document.title = `Chat: ${channel}`;
346+
347+
// Add channel name to the UI
348+
const heading = document.querySelector('h1');
349+
heading.textContent = `Rivet Chat Room - ${channel}`;
350+
351+
// Send message on form submit
352+
document.getElementById('message-form').addEventListener('submit', async (e) => {
353+
e.preventDefault();
354+
const input = document.getElementById('message-input');
355+
const message = input.value.trim();
356+
357+
if (message) {
358+
try {
359+
await chatRoom.sendMessage(username, message);
360+
input.value = '';
361+
} catch (error) {
362+
console.error("Failed to send message:", error);
363+
alert("Error sending message. Please try again.");
364+
}
365+
}
366+
});
367+
}
368+
369+
function addMessage(username, message) {
370+
const messageList = document.getElementById('message-list');
371+
const item = document.createElement('li');
372+
373+
// Create elements instead of using innerHTML to prevent XSS
374+
const usernameSpan = document.createElement('strong');
375+
usernameSpan.textContent = username;
376+
377+
item.appendChild(usernameSpan);
378+
item.appendChild(document.createTextNode(': ' + message));
379+
380+
messageList.appendChild(item);
381+
messageList.scrollTop = messageList.scrollHeight;
382+
}
383+
384+
init().catch(console.error);
385+
</script>
386+
</head>
387+
<body>
388+
<h1>Rivet Chat Room</h1>
389+
<ul id="message-list"></ul>
390+
<form id="message-form">
391+
<input id="message-input" placeholder="Type a message..." autocomplete="off">
392+
<button type="submit">Send</button>
393+
</form>
394+
</body>
395+
</html>
396+
```

0 commit comments

Comments
 (0)