#Chat Application
A basic chat application with messages, typing indicators, and user presence.
#Backend Configuration
// dialogue.config.ts
import { createDialogue, defineEvent } from "./dialogue";
import { z } from "zod";
// Define events
export const Message = defineEvent("message", {
schema: z.object({
text: z.string().min(1).max(2000),
senderId: z.string(),
replyTo: z.string().optional(),
}),
});
export const Typing = defineEvent("typing", {
schema: z.object({
isTyping: z.boolean(),
}),
});
export const UserJoined = defineEvent("user:joined", {
schema: z.object({
userId: z.string(),
username: z.string(),
}),
});
export const UserLeft = defineEvent("user:left", {
schema: z.object({
userId: z.string(),
}),
});
export const dialogue = createDialogue({
port: 3000,
rooms: {
general: {
name: "General Chat",
description: "Public chat room for everyone",
events: [Message, Typing, UserJoined, UserLeft],
defaultSubscriptions: ["message", "user:joined", "user:left"],
syncHistoryOnJoin: 50, // Send last 50 messages on join
},
},
hooks: {
clients: {
onConnected: async (client) => {
// Get user info from database
const user = await getUserById(client.userId);
// Auto-join general chat
client.join("general");
// Notify others
dialogue.trigger(
"general",
UserJoined,
{
userId: client.userId,
username: user.name,
},
client.userId
);
},
},
},
});
// Persist messages to database
dialogue.on("general", Message, async (msg) => {
await db.messages.create({
roomId: msg.roomId,
text: msg.data.text,
senderId: msg.from,
replyTo: msg.data.replyTo,
createdAt: new Date(msg.timestamp),
});
});#Frontend Client
// chat-client.ts
import { createDialogueClient } from "./client";
interface ChatMessage {
text: string;
senderId: string;
replyTo?: string;
}
function createChatClient(url: string, userId: string, token: string) {
const client = createDialogueClient({
url,
auth: { userId, token },
});
let room: RoomContext | null = null;
return {
async connect(): Promise<void> {
await client.connect();
room = await client.join("general");
},
onMessage(handler: (msg: ChatMessage, from: string) => void): () => void {
if (!room) throw new Error("Not connected");
return room.on<ChatMessage>("message", (msg) => {
handler(msg.data, msg.from);
});
},
onUserJoined(handler: (userId: string, username: string) => void): () => void {
if (!room) throw new Error("Not connected");
return room.on<{ userId: string; username: string }>(
"user:joined",
(msg) => {
handler(msg.data.userId, msg.data.username);
}
);
},
sendMessage(text: string, replyTo?: string): void {
if (!room) throw new Error("Not connected");
room.trigger("message", {
text,
senderId: client.userId,
replyTo,
});
},
setTyping(isTyping: boolean): void {
if (!room) return;
room.trigger("typing", { isTyping });
},
disconnect(): void {
room?.leave();
client.disconnect();
},
};
}This documentation reflects the current implementation and is subject to evolution. Contributions and feedback are welcome.