-
-
Notifications
You must be signed in to change notification settings - Fork 755
Understanding Rooms
Rooms are a first-class concept in Atmosphere 4.0. A Room is a named group of connections with built-in presence tracking, message history, direct messaging, and authorization.
Rooms provide a higher-level abstraction over Broadcasters — you work with named groups and members instead of managing individual resources.
Tip: The easiest way to use Rooms is the
@RoomServiceannotation, which auto-creates the Room with optional history — no boilerplate required. The examples below show the programmatic API for when you need more control.
@Configuration
public class RoomsConfig {
private final AtmosphereFramework framework;
public RoomsConfig(AtmosphereFramework framework) {
this.framework = framework;
}
@Bean
public RoomManager roomManager() {
return RoomManager.getOrCreate(framework);
}
@EventListener(ApplicationReadyEvent.class)
public void setupRooms() {
// Register the protocol interceptor for client ↔ server room messages
var interceptor = new RoomProtocolInterceptor();
interceptor.configure(framework.getAtmosphereConfig());
framework.interceptor(interceptor);
RoomManager manager = roomManager();
Room lobby = manager.room("lobby");
// New joiners get the last 50 messages
lobby.enableHistory(50);
// Track who joins and leaves
lobby.onPresence(event -> {
var memberId = event.memberInfo() != null
? event.memberInfo().id()
: event.member().uuid();
log.info("{} {} room '{}'",
memberId, event.type(), event.room().name());
});
}
}import { Atmosphere, AtmosphereRooms } from 'atmosphere.js';
const atmosphere = new Atmosphere();
const rooms = new AtmosphereRooms(atmosphere, {
url: '/atmosphere/chat',
transport: 'websocket',
});
const lobby = await rooms.join('lobby', { id: 'alice' }, {
message: (data, member) => console.log(`${member.id}: ${data}`),
join: (event) => console.log(`${event.member.id} joined`),
leave: (event) => console.log(`${event.member.id} left`),
joined: (name, members) => console.log(`Joined ${name}, members:`, members),
});
lobby.broadcast('Hello everyone!');
lobby.sendTo('bob', 'Private message');
lobby.leave();The entry point for creating and managing rooms. Each room is backed by a dedicated Broadcaster.
// Create from AtmosphereFramework
RoomManager rooms = RoomManager.create(framework);
// Or get/create a singleton (recommended)
RoomManager rooms = RoomManager.getOrCreate(framework);
// Get or create a room
Room lobby = rooms.room("lobby");
// Check existence
rooms.exists("lobby"); // true
// List all rooms
rooms.all(); // Collection<Room>
rooms.count(); // int
// Destroy a room
rooms.destroy("lobby"); // removes all members
rooms.destroyAll();A named group of connections.
Room room = rooms.room("lobby");
// Members
room.join(resource); // Add a connection
room.join(resource, new RoomMember("alice")); // Add with identity
room.leave(resource); // Remove a connection
room.members(); // Set<AtmosphereResource>
room.memberInfo(); // Map<uuid, RoomMember>
room.memberOf(resource); // Optional<RoomMember>
room.size(); // Member count
room.isEmpty();
room.contains(resource);
// Messaging
room.broadcast("Hello!"); // To all members
room.broadcast("Hello!", sender); // To all except sender
room.sendTo("Hello!", targetUuid); // Direct message by UUID
// Presence
room.onPresence(event -> { /* JOIN or LEAVE */ });
// History
room.enableHistory(50); // Cache last 50 messages
// Lifecycle
room.destroy();
room.isDestroyed();Application-level identity for a room member, stable across reconnects:
// Simple
var member = new RoomMember("alice");
// With metadata
var member = new RoomMember("alice", Map.of(
"displayName", "Alice Smith",
"avatar", "https://example.com/alice.jpg"
));
member.id(); // "alice"
member.metadata(); // unmodifiable MapFired when a member joins or leaves a room:
room.onPresence(event -> {
event.type(); // PresenceEvent.Type.JOIN or LEAVE
event.room(); // the Room
event.member(); // AtmosphereResource
event.memberInfo(); // RoomMember (may be null)
});The RoomProtocolInterceptor bridges the atmosphere.js client room protocol to the server-side Room API. It intercepts JSON messages from clients and routes them to the appropriate Room operations.
// Register manually
var interceptor = new RoomProtocolInterceptor();
interceptor.configure(framework.getAtmosphereConfig());
framework.interceptor(interceptor);The interceptor handles four message types from the client:
| Message Type | Action |
|---|---|
join |
Joins the room, sends ack with member list, broadcasts presence |
leave |
Leaves the room, broadcasts leave presence |
broadcast |
Broadcasts message to all room members (except sender) |
direct |
Sends a direct message to a specific member by ID |
The RoomInterceptor provides automatic room joining based on URL path:
RoomManager rooms = RoomManager.create(framework);
framework.interceptor(new RoomInterceptor(rooms));
// Requests to /room/lobby auto-join the "lobby" room
// Requests to /room/general auto-join the "general" roomCustom base path:
framework.interceptor(new RoomInterceptor(rooms, "/chat/"));
// Now /chat/lobby → room "lobby"Control who can join, leave, broadcast, or send direct messages:
// 1. Implement RoomAuthorizer
public class MyRoomAuthorizer implements RoomAuthorizer {
@Override
public boolean authorize(AtmosphereResource r, String roomName, RoomAction action) {
// Check permissions based on resource, room name, and action
return true;
}
}
// 2. Annotate your handler
@RoomAuth(authorizer = MyRoomAuthorizer.class)
@ManagedService(path = "/chat")
public class Chat { /* ... */ }Available actions: JOIN, LEAVE, BROADCAST, SEND_TO.
Expose room information via REST (Spring Boot example):
@RestController
@RequestMapping("/api/rooms")
public class ChatRoomsController {
private final RoomManager roomManager;
public ChatRoomsController(RoomManager roomManager) {
this.roomManager = roomManager;
}
@GetMapping
public List<Map<String, Object>> listRooms() {
return roomManager.all().stream()
.map(room -> Map.of(
"name", room.name(),
"members", room.size(),
"memberDetails", room.memberInfo().values()
))
.toList();
}
}atmosphere.js 5.0 includes room hooks for popular frameworks:
import { useRoom } from 'atmosphere.js/react';
function ChatRoom() {
const { members, messages, broadcast } = useRoom<ChatMessage>({
request: { url: '/atmosphere/chat', transport: 'websocket' },
room: 'lobby',
member: { id: 'alice' },
});
return (
<div>
<p>Members: {members.map(m => m.id).join(', ')}</p>
{messages.map((msg, i) => <p key={i}>{msg.member.id}: {msg.data}</p>)}
<button onClick={() => broadcast({ text: 'Hello!' })}>Send</button>
</div>
);
}<script setup lang="ts">
import { useRoom } from 'atmosphere.js/vue';
const { members, messages, broadcast } = useRoom<ChatMessage>(
{ url: '/atmosphere/chat', transport: 'websocket' },
'lobby',
{ id: 'alice' },
);
</script><script>
import { createRoomStore } from 'atmosphere.js/svelte';
const { store: lobby, broadcast } = createRoomStore(
{ url: '/atmosphere/chat', transport: 'websocket' },
'lobby',
{ id: 'alice' },
);
</script>
<p>Members: {$lobby.members.map(m => m.id).join(', ')}</p>A VirtualRoomMember is a non-connection-based participant — an AI agent, bot, or server-side service that receives room messages and can respond, without a WebSocket or HTTP connection.
Room lobby = rooms.room("lobby");
// Add an AI assistant to the room
var settings = AiConfig.get();
lobby.joinVirtual(new LlmRoomMember("assistant", settings.client(), settings.model()));
// Any message broadcast in the room is received by the AI,
// which generates a response and broadcasts it back
lobby.broadcast("What is the weather today?");Virtual members:
- Receive messages via the
onMessage(Room, senderId, message)callback - Can call
room.broadcast()to reply to all members - Fire presence events (
PresenceEvent.isVirtual()returnstrue) - Report metadata visible in presence events (e.g.,
{"type": "llm", "model": "gemini-2.5-flash"})
Use room.leaveVirtual(member) to remove a virtual member. See AI / LLM Streaming — Virtual Room Members for the full guide.