WebSocket Realtime
Real-time presentation control, room management, and collaboration API
WebSocket Realtime API
Connect to for realtime presentation control and collaboration.
Connection
# WebSocket connections require a WebSocket client
# Using websocat for testing:
websocat "$WS_URL/ws?token=YOUR_ACCESS_TOKEN"
const ws = new WebSocket('$WS_URL/ws?token=' + accessToken)
ws.onopen = () => {
console.log('Connected to Cliqer WebSocket')
}
ws.onmessage = (event) => {
const message = JSON.parse(event.data)
console.log('Received:', message)
}
ws.onerror = (error) => {
console.error('WebSocket error:', error)
}
import websocket
import json
ws = websocket.WebSocketApp(
f"$WS_URL/ws?token={access_token}",
on_message=lambda ws, msg: print(json.loads(msg)),
on_error=lambda ws, err: print(f"Error: {err}"),
on_open=lambda ws: print("Connected")
)
ws.run_forever()
import (
"github.com/gorilla/websocket"
)
url := "$WS_URL/ws?token=" + accessToken
conn, _, err := websocket.DefaultDialer.Dial(url, nil)
if err != nil {
log.Fatal("dial:", err)
}
defer conn.Close()
for {
_, message, err := conn.ReadMessage()
if err != nil {
break
}
fmt.Printf("Received: %s\n", message)
}
use WebSocket\Client;
$client = new Client("$WS_URL/ws?token={$accessToken}");
while (true) {
$message = $client->receive();
$data = json_decode($message, true);
print_r($data);
}
use tokio_tungstenite::{connect_async, tungstenite::Message};
use futures_util::{StreamExt, SinkExt};
let url = format!("$WS_URL/ws?token={}", access_token);
let (mut ws_stream, _) = connect_async(&url).await?;
while let Some(msg) = ws_stream.next().await {
match msg? {
Message::Text(text) => {
let data: serde_json::Value = serde_json::from_str(&text)?;
println!("Received: {:?}", data);
}
_ => {}
}
}
Room Management
Create Room
Create a new presentation room as host.
# Send via WebSocket connection
echo '{"type":"createRoom"}' | websocat "$WS_URL/ws?token=TOKEN"
ws.send(JSON.stringify({ type: 'createRoom' }))
ws.onmessage = (event) => {
const msg = JSON.parse(event.data)
if (msg.type === 'roomCreated') {
console.log('Room ID:', msg.payload.roomId)
console.log('Socket ID:', msg.payload.socketId)
}
}
ws.send(json.dumps({"type": "createRoom"}))
# Response handler
def on_message(ws, message):
msg = json.loads(message)
if msg.get("type") == "roomCreated":
print(f"Room ID: {msg['payload']['roomId']}")
msg := map[string]string{"type": "createRoom"}
conn.WriteJSON(msg)
var response map[string]interface{}
conn.ReadJSON(&response)
if response["type"] == "roomCreated" {
payload := response["payload"].(map[string]interface{})
fmt.Printf("Room ID: %s\n", payload["roomId"])
}
$client->send(json_encode(['type' => 'createRoom']));
$response = json_decode($client->receive(), true);
if ($response['type'] === 'roomCreated') {
echo "Room ID: " . $response['payload']['roomId'];
}
let create_msg = serde_json::json!({"type": "createRoom"});
ws_stream.send(Message::Text(create_msg.to_string())).await?;
if let Some(Ok(Message::Text(text))) = ws_stream.next().await {
let response: serde_json::Value = serde_json::from_str(&text)?;
if response["type"] == "roomCreated" {
println!("Room ID: {}", response["payload"]["roomId"]);
}
}
Response:
{
"type": "roomCreated",
"payload": {
"roomId": "ABC123",
"socketId": "client_550e8400"
}
}
Join Room
Join an existing presentation room.
echo '{"type":"joinRoom","payload":{"room":"ABC123","presenterName":"John","joinAsHost":false}}' | websocat "$WS_URL/ws?token=TOKEN"
ws.send(JSON.stringify({
type: 'joinRoom',
payload: {
room: 'ABC123',
presenterName: 'John Doe',
joinAsHost: false
}
}))
ws.send(json.dumps({
"type": "joinRoom",
"payload": {
"room": "ABC123",
"presenterName": "John Doe",
"joinAsHost": False
}
}))
msg := map[string]interface{}{
"type": "joinRoom",
"payload": map[string]interface{}{
"room": "ABC123",
"presenterName": "John Doe",
"joinAsHost": false,
},
}
conn.WriteJSON(msg)
$client->send(json_encode([
'type' => 'joinRoom',
'payload' => [
'room' => 'ABC123',
'presenterName' => 'John Doe',
'joinAsHost' => false
]
]));
let join_msg = serde_json::json!({
"type": "joinRoom",
"payload": {
"room": "ABC123",
"presenterName": "John Doe",
"joinAsHost": false
}
});
ws_stream.send(Message::Text(join_msg.to_string())).await?;
Response:
{
"type": "joinedRoom",
"payload": {
"id": "client_550e8400",
"hosts": ["host_abc123"],
"needsRefresh": false
}
}
Slide Navigation
Control slide navigation in real-time.
| Event | Description | Payload |
|---|---|---|
webAction | Navigate slides | { action: 'next' | 'prev' | 'goto', slide?: number } |
clicksEnabled | Toggle click control | { clicksEnabled: boolean } |
countdown | Timer control | { type: 'start' | 'stop', time: number } |
presenterMouse | Laser pointer | { mouse: { x: number, y: number }, color?: string } |
needsRefresh | Request refresh | { forId: string } |
refreshRoom | Refresh all clients | { roomId: string } |
# Next slide
echo '{"type":"webAction","payload":{"action":"next"}}' | websocat "wss://..."
# Previous slide
echo '{"type":"webAction","payload":{"action":"prev"}}' | websocat "wss://..."
# Go to slide 5
echo '{"type":"webAction","payload":{"action":"goto","slide":5}}' | websocat "wss://..."
// Next slide
ws.send(JSON.stringify({
type: 'webAction',
payload: { action: 'next' }
}))
// Previous slide
ws.send(JSON.stringify({
type: 'webAction',
payload: { action: 'prev' }
}))
// Go to specific slide
ws.send(JSON.stringify({
type: 'webAction',
payload: { action: 'goto', slide: 5 }
}))
// Laser pointer
ws.send(JSON.stringify({
type: 'presenterMouse',
payload: { mouse: { x: 0.5, y: 0.3 }, color: '#ef4444' }
}))
# Next slide
ws.send(json.dumps({
"type": "webAction",
"payload": {"action": "next"}
}))
# Previous slide
ws.send(json.dumps({
"type": "webAction",
"payload": {"action": "prev"}
}))
# Go to specific slide
ws.send(json.dumps({
"type": "webAction",
"payload": {"action": "goto", "slide": 5}
}))
// Next slide
conn.WriteJSON(map[string]interface{}{
"type": "webAction",
"payload": map[string]string{"action": "next"},
})
// Previous slide
conn.WriteJSON(map[string]interface{}{
"type": "webAction",
"payload": map[string]string{"action": "prev"},
})
// Next slide
$client->send(json_encode([
'type' => 'webAction',
'payload' => ['action' => 'next']
]));
// Go to specific slide
$client->send(json_encode([
'type' => 'webAction',
'payload' => ['action' => 'goto', 'slide' => 5]
]));
// Next slide
let next_msg = serde_json::json!({
"type": "webAction",
"payload": {"action": "next"}
});
ws_stream.send(Message::Text(next_msg.to_string())).await?;
// Go to specific slide
let goto_msg = serde_json::json!({
"type": "webAction",
"payload": {"action": "goto", "slide": 5}
});
ws_stream.send(Message::Text(goto_msg.to_string())).await?;
Messaging
Send messages between hosts and clients.
# Message to hosts (from client)
echo '{"type":"messageToHosts","payload":{"text":"Question about slide 3"}}' | websocat "wss://..."
# Message to clients (from host)
echo '{"type":"messageToClients","payload":{"text":"Welcome everyone!"}}' | websocat "wss://..."
// Client sends message to hosts
ws.send(JSON.stringify({
type: 'messageToHosts',
payload: { text: 'Question about slide 3' }
}))
// Host sends message to all clients
ws.send(JSON.stringify({
type: 'messageToClients',
payload: { text: 'Welcome everyone!' }
}))
// Listen for messages
ws.onmessage = (event) => {
const msg = JSON.parse(event.data)
if (msg.type === 'messageFromHost') {
console.log(`Host says: ${msg.payload.originalPayload.text}`)
}
if (msg.type === 'messageFromClient') {
console.log(`${msg.payload.fromPresenterName}: ${msg.payload.originalPayload.text}`)
}
}
# Client sends message to hosts
ws.send(json.dumps({
"type": "messageToHosts",
"payload": {"text": "Question about slide 3"}
}))
# Host sends message to all clients
ws.send(json.dumps({
"type": "messageToClients",
"payload": {"text": "Welcome everyone!"}
}))
// Message to hosts
conn.WriteJSON(map[string]interface{}{
"type": "messageToHosts",
"payload": map[string]string{"text": "Question about slide 3"},
})
// Message to clients (as host)
conn.WriteJSON(map[string]interface{}{
"type": "messageToClients",
"payload": map[string]string{"text": "Welcome everyone!"},
})
// Message to hosts
$client->send(json_encode([
'type' => 'messageToHosts',
'payload' => ['text' => 'Question about slide 3']
]));
// Message to clients (as host)
$client->send(json_encode([
'type' => 'messageToClients',
'payload' => ['text' => 'Welcome everyone!']
]));
// Message to hosts
let msg = serde_json::json!({
"type": "messageToHosts",
"payload": {"text": "Question about slide 3"}
});
ws_stream.send(Message::Text(msg.to_string())).await?;
Incoming Message Format:
{
"type": "messageFromHost",
"payload": {
"fromClientId": "host_abc123",
"fromPresenterName": "Host",
"originalPayload": { "text": "Welcome everyone!" }
}
}
Event Reference
Outgoing Events (Client → Server)
| Event | Description |
|---|---|
createRoom | Create a new room |
joinRoom | Join existing room |
webAction | Slide navigation |
clicksEnabled | Toggle click control |
countdown | Timer control |
presenterMouse | Laser pointer position |
messageToHosts | Send message to hosts |
messageToClients | Send message to clients (host only) |
needsRefresh | Request refresh for specific client |
refreshRoom | Refresh all clients in room |
closeStream | End presentation (host only) |
logout | Disconnect from room |
Incoming Events (Server → Client)
| Event | Description |
|---|---|
roomCreated | Room creation confirmed |
joinedRoom | Successfully joined room |
clientJoined | New client joined room |
webAction | Slide navigation event |
clicksEnabled | Click control toggled |
countdown | Timer event |
presenterMouse | Laser pointer position |
messageFromHost | Message from host |
messageFromClient | Message from client |
refresh | Refresh requested |
logout | Disconnection notice |
error | Error message |