Chat Room
Simple message passing with player presence. The simplest multiplayer pattern.
When to Use
- Chat applications
- Game lobbies
- Party game waiting rooms
- Social spaces
The Pattern
import { connect } from '@watchtower-sdk/core'
const room = await connect('chat-room', {
name: 'Alice' // Display name
})
const messages = []
// Send a chat message
function sendMessage(text) {
room.broadcast({
type: 'chat',
text,
name: room.players.find(p => p.id === room.playerId)?.name
})
// Add to local messages too
messages.push({
from: room.playerId,
name: 'You',
text,
time: Date.now()
})
renderMessages()
}
// Receive messages
room.on('message', (from, data) => {
if (data.type === 'chat') {
messages.push({
from,
name: data.name || from,
text: data.text,
time: Date.now()
})
renderMessages()
}
})
// Player events
room.on('join', (player) => {
messages.push({
type: 'system',
text: `${player.name || player.id} joined`,
time: Date.now()
})
renderMessages()
updatePlayerList()
})
room.on('leave', (player) => {
messages.push({
type: 'system',
text: `${player.name || player.id} left`,
time: Date.now()
})
renderMessages()
updatePlayerList()
})
function updatePlayerList() {
// room.players contains everyone in the room
const list = room.players.map(p => p.name || p.id)
renderPlayerList(list)
}Key Points
- Use player names — pass
namein connect options - System messages — announce joins and leaves
- Local echo — show your own messages immediately
- Player list — use
room.playersfor presence
Message Types
Expand beyond basic chat with different message types:
// Chat
room.broadcast({ type: 'chat', text: 'Hello!' })
// Emote
room.broadcast({ type: 'emote', emote: 'wave' })
// Typing indicator
room.broadcast({ type: 'typing', isTyping: true })
// Status update
room.broadcast({ type: 'status', status: 'away' })
// Receive
room.on('message', (from, data) => {
switch (data.type) {
case 'chat':
addChatMessage(from, data.text)
break
case 'emote':
showEmote(from, data.emote)
break
case 'typing':
setTypingIndicator(from, data.isTyping)
break
case 'status':
updateUserStatus(from, data.status)
break
}
})Private Messages
// Send to specific player
function sendPrivateMessage(toPlayerId, text) {
room.send(toPlayerId, {
type: 'dm',
text,
from: room.playerId
})
}
// Receive
room.on('message', (from, data) => {
if (data.type === 'dm') {
addPrivateMessage(from, data.text)
}
})Full Example
<!DOCTYPE html>
<html>
<head>
<title>Chat Room</title>
<style>
body { font-family: sans-serif; margin: 20px; }
#messages { height: 300px; overflow-y: auto; border: 1px solid #ccc; padding: 10px; }
.system { color: #888; font-style: italic; }
#players { margin-top: 10px; color: #666; }
</style>
</head>
<body>
<h1>Chat Room</h1>
<div id="messages"></div>
<div id="players"></div>
<form id="form">
<input id="input" placeholder="Type a message..." autocomplete="off" />
<button>Send</button>
</form>
<script type="module">
import { connect } from 'https://unpkg.com/@watchtower-sdk/core'
const name = prompt('Your name?') || 'Anonymous'
const room = await connect(location.hash.slice(1) || undefined, { name })
// Update URL with room code
location.hash = room.code
document.title = 'Chat: ' + room.code
const messagesEl = document.getElementById('messages')
const playersEl = document.getElementById('players')
const form = document.getElementById('form')
const input = document.getElementById('input')
function addMessage(html) {
messagesEl.innerHTML += html + '<br>'
messagesEl.scrollTop = messagesEl.scrollHeight
}
function updatePlayers() {
playersEl.textContent = 'Online: ' + room.players.map(p => p.name || p.id).join(', ')
}
// Initial player list
updatePlayers()
addMessage('<span class="system">Connected to room ' + room.code + '</span>')
// Send message
form.onsubmit = (e) => {
e.preventDefault()
if (!input.value.trim()) return
room.broadcast({ type: 'chat', text: input.value, name })
addMessage('<b>You:</b> ' + input.value)
input.value = ''
}
// Receive messages
room.on('message', (from, data) => {
if (data.type === 'chat') {
addMessage('<b>' + (data.name || from) + ':</b> ' + data.text)
}
})
room.on('join', (player) => {
addMessage('<span class="system">' + (player.name || player.id) + ' joined</span>')
updatePlayers()
})
room.on('leave', (player) => {
addMessage('<span class="system">' + (player.name || player.id) + ' left</span>')
updatePlayers()
})
</script>
</body>
</html>Next Steps
- Add emoji reactions
- Add typing indicators
- Add user avatars in metadata