WebSocket-Echtzeit

Verbinden Sie sich mit wss://cliqer.io/ws, um einen Cliqer-Raum aus Ihrer eigenen Software oder Ihrem Show-Control-System wie mit einem Fern-Presenter zu steuern: Folien wechseln, den Timer des Hosts bedienen, mit einem Laserpunkt zeigen und dem Host Nachrichten senden. Ihre App tritt dem Raum in der Rolle eines Vortragenden bei; gehostet wird der Raum immer von der Cliqer-Desktop-App.
session_token (Browser senden es automatisch), einem Query-Parameter ?token= oder einem Header Authorization: Bearer. Das Token wird beim Verbindungsaufbau geprüft, nicht bei jedem Befehl. Wenn es abläuft, öffnen Sie also eine neue Verbindung mit einem frischen Token und treten dem Raum erneut bei. Ohne Token können Sie sich verbinden, die API aber nicht nutzen. Team-API-Schlüssel (cliqer_...) authentifizieren nur die REST-Endpunkte; auf diesem Socket ist ein Schlüssel kein Session-Token.Verbindung
# 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);
}
_ => {}
}
}
Der Server sendet connected mit Ihrer clientId, sobald er die Verbindung eingerichtet hat. Warten Sie darauf, bevor Sie etwas senden: Eine Nachricht, die früher eintrifft, wird mit einem error abgelehnt, und die Verbindung wird geschlossen.
Verbindung & Raumablauf
Foliensteuerung
Steuern Sie die Folien in einem Raum aus Ihrer eigenen Software, wie mit einem Fern-Presenter. Ihre App tritt dem Raum in der Rolle eines Vortragenden bei. Der Host sieht sie daher in der Liste der Vortragenden unter ihrem presenterName und steuert sie wie alle anderen Vortragenden: Der Operator kann ihre Klicks abschalten, sie entfernen oder sie sperren.
- Verbinden Sie sich mit einem Session-Token (
?token=, einem HeaderAuthorization: Beareroder dem Cookiesession_token). Team-API-Schlüssel funktionieren nur an den REST-Endpunkten, nicht auf diesem Socket. - Der Plan des Raumbesitzers muss die Client API enthalten: über das API-Add-on bei Presenter, Studio HD und Studio 4K, inklusive bei Studio 8K und Enterprise.
- Folien- und Timer-Befehle werden nur ausgeführt, solange der Host Klicks von Ihrer Verbindung zulässt.
Dem Raum beitreten
{ "type": "joinRoom", "payload": { "room": "ABC123", "presenterName": "Show control", "api": true } }
- Senden Sie dies nach dem Ereignis
connected: Der Server lehnt alles ab, was davor eintrifft. presenterNameist der Name, den der Host sieht (StandardAPI). Ihre App ist im Raum immer ein Vortragender, nie der Host.- Warten Sie auf ein Ereignis
clicksEnabledmit dem Payloadtrue, bevor Sie Befehle senden. Der Host sendet es, sobald er Ihre Verbindung eingerichtet hat, und erneut, wann immer der Operator Ihre Klicks ein- oder ausschaltet. - Bewahren Sie den
presenterKeyausjoinedRoomauf und senden Sie ihn bei einer erneuten Verbindung injoinRoommit, damit der Host Ihre Klickberechtigung beibehält. - Senden Sie alle 15 Sekunden den Text-Frame
ping, um die Verbindung offen zu halten. Der Server antwortet mit dem Text-Framepong(kein JSON).
Befehle senden
{ "type": "webAction", "payload": { "action": "goto", "slide": 5, "id": "a3" } }
action | Zusatzfeld | Funktion |
|---|---|---|
next | Nächste Folie (oder nächster Animationsschritt) | |
prev | Vorherige Folie (oder vorheriger Animationsschritt) | |
first | Erste Folie | |
last | Letzte Folie | |
goto | slide: ganze Zahl von 1 bis 9999 | Zu dieser Folie springen |
blackout | on: true oder false | Bildschirm schwarz schalten oder wieder anzeigen |
idist optional: 1 bis 64 Buchstaben, Ziffern,_,.,:oder-. Sie wird im Ergebnis zurückgegeben. Verwenden Sie für jeden Befehl eine neueid: Der Host verwirft einen Befehl, dessenider in den letzten 5 Sekunden bereits von Ihrer Verbindung erhalten hat, sodass eine Wiederholung desselben Befehls nur einmal ausgeführt wird.first,last,gotoundblackoutwerden nur auf einem Host ausgeführt, dessen letztescontrols-Ereignis sie innavaufführt. Ein Host, der keincontrols-Ereignis sendet, unterstützt nurnextundprev. Sind mehrere Hosts im Raum, führt jeder Host die Befehle aus, die er unterstützt.nextoderprevbei schwarzem Bildschirm zeigt ihn wieder an, ohne die Folie zu wechseln.- Ein
gotoüber die letzte Folie hinaus wird ignoriert. - Senden Sie höchstens 10 Befehle pro Sekunde, Folien- und Timer-Befehle zusammengerechnet. Der Host verwirft außerdem eine Wiederholung desselben Befehls innerhalb von 50 Millisekunden.
Was die einzelnen Präsentations-Apps derzeit unterstützen:
| Präsentations-App | next, prev | first, last | goto | blackout |
|---|---|---|---|---|
| PowerPoint unter Windows | Ja | Ja | Ja | Ja |
| PowerPoint auf dem Mac | Ja | Ja | Nein | Nein |
| Keynote | Ja | Nein | Nein | Ja |
| Google Slides (mit verbundener Browsererweiterung) | Ja | Ja | Nein | Nein |
| vMix | Ja | Nein | Nein | Nein |
| Fenster im Fokus | Ja | Nein | Nein | Nein |
Das Ereignis controls liefert die aktuelle Antwort für den Host, den Sie steuern: Lesen Sie nav daraus und nicht aus dieser Tabelle.
# One connection: wait for `connected`, join as an API client, give the host a
# moment to set it up, then send commands. Replies print as they arrive.
( sleep 2
echo '{"type":"joinRoom","payload":{"room":"ABC123","presenterName":"Show control","api":true}}'
sleep 3
echo '{"type":"webAction","payload":{"action":"next","id":"a1"}}'
sleep 1
echo '{"type":"webAction","payload":{"action":"prev","id":"a2"}}'
sleep 1 ) | websocat "$WS_URL/ws?token=YOUR_SESSION_TOKEN"
const ws = new WebSocket('$WS_URL/ws?token=' + accessToken)
const send = (message: object) => ws.send(JSON.stringify(message))
let ready = false
ws.onmessage = (event) => {
if (event.data === 'pong') return
const msg = JSON.parse(event.data)
if (msg.type === 'connected') {
send({ type: 'joinRoom', payload: { room: 'ABC123', presenterName: 'Show control', api: true } })
setInterval(() => ws.send('ping'), 15000) // keeps the connection open
}
if (msg.type === 'clicksEnabled' && msg.payload === true && !ready) {
ready = true
send({ type: 'webAction', payload: { action: 'next', id: 'a1' } })
// Also: { action: 'prev' }, { action: 'first' }, { action: 'last' },
// { action: 'goto', slide: 5 }, { action: 'blackout', on: true }
}
if (msg.type === 'controls') console.log('Commands this host supports:', ['next', 'prev', ...msg.payload.nav])
if (msg.type === 'webActionResult') console.log(`${msg.payload.action} delivered to ${msg.payload.hosts} host(s)`)
if (msg.type === 'slide') console.log('Slide:', msg.payload?.number ?? 'no deck open')
if (msg.type === 'error') console.error(msg.error, msg.payload?.message)
}
import json
import threading
import time
import websocket
ready = False
def send(ws, message):
ws.send(json.dumps(message))
def keepalive(ws): # keeps the connection open
while ws.sock and ws.sock.connected:
time.sleep(15)
ws.send("ping")
def on_message(ws, message):
global ready
if message == "pong":
return
msg = json.loads(message)
if msg["type"] == "connected":
send(ws, {"type": "joinRoom", "payload": {"room": "ABC123", "presenterName": "Show control", "api": True}})
threading.Thread(target=keepalive, args=(ws,), daemon=True).start()
elif msg["type"] == "clicksEnabled" and msg["payload"] is True and not ready:
ready = True
send(ws, {"type": "webAction", "payload": {"action": "next", "id": "a1"}})
# Also: {"action": "goto", "slide": 5}, {"action": "blackout", "on": True}, ...
elif msg["type"] == "webActionResult":
print(f"{msg['payload']['action']} delivered to {msg['payload']['hosts']} host(s)")
elif msg["type"] == "slide":
print("Slide:", msg["payload"]["number"] if msg["payload"] else "no deck open")
elif msg["type"] == "error":
print("Error:", msg["error"], msg["payload"]["message"])
ws = websocket.WebSocketApp(f"$WS_URL/ws?token={access_token}", on_message=on_message)
ws.run_forever()
import (
"encoding/json"
"fmt"
"log"
"sync"
"time"
"github.com/gorilla/websocket"
)
conn, _, err := websocket.DefaultDialer.Dial("$WS_URL/ws?token="+accessToken, nil)
if err != nil {
log.Fatal("dial:", err)
}
defer conn.Close()
var writeMu sync.Mutex // one writer at a time
send := func(message interface{}) {
writeMu.Lock()
defer writeMu.Unlock()
conn.WriteJSON(message)
}
go func() { // keeps the connection open
for range time.Tick(15 * time.Second) {
writeMu.Lock()
conn.WriteMessage(websocket.TextMessage, []byte("ping"))
writeMu.Unlock()
}
}()
ready := false
for {
_, data, err := conn.ReadMessage()
if err != nil {
break
}
if string(data) == "pong" {
continue
}
var msg struct {
Type string `json:"type"`
Error string `json:"error"`
Payload json.RawMessage `json:"payload"`
}
if json.Unmarshal(data, &msg) != nil {
continue
}
switch msg.Type {
case "connected":
send(map[string]interface{}{
"type": "joinRoom",
"payload": map[string]interface{}{"room": "ABC123", "presenterName": "Show control", "api": true},
})
case "clicksEnabled":
if string(msg.Payload) == "true" && !ready {
ready = true
send(map[string]interface{}{
"type": "webAction",
"payload": map[string]interface{}{"action": "next", "id": "a1"},
})
// Also: {"action": "goto", "slide": 5}, {"action": "blackout", "on": true}, ...
}
case "webActionResult", "slide", "error":
fmt.Printf("%s %s %s\n", msg.Type, msg.Error, msg.Payload)
}
}
use WebSocket\Client;
use WebSocket\TimeoutException;
// A 15 second read timeout doubles as the keepalive timer.
$client = new Client("$WS_URL/ws?token={$accessToken}", ['timeout' => 15]);
$ready = false;
while (true) {
try {
$message = $client->receive();
} catch (TimeoutException $e) {
$client->text('ping'); // keeps the connection open
continue;
}
if ($message === null) break; // closed
if ($message === 'pong') continue;
$msg = json_decode($message, true);
if ($msg['type'] === 'connected') {
$client->text(json_encode([
'type' => 'joinRoom',
'payload' => ['room' => 'ABC123', 'presenterName' => 'Show control', 'api' => true]
]));
} elseif ($msg['type'] === 'clicksEnabled' && $msg['payload'] === true && !$ready) {
$ready = true;
$client->text(json_encode([
'type' => 'webAction',
'payload' => ['action' => 'next', 'id' => 'a1']
]));
// Also: ['action' => 'goto', 'slide' => 5], ['action' => 'blackout', 'on' => true], ...
} elseif (in_array($msg['type'], ['webActionResult', 'slide', 'error'], true)) {
echo json_encode($msg), PHP_EOL;
}
}
use futures_util::{SinkExt, StreamExt};
use serde_json::{json, Value};
use tokio_tungstenite::{connect_async, tungstenite::Message};
let url = format!("$WS_URL/ws?token={}", access_token);
let (ws_stream, _) = connect_async(&url).await?;
let (mut write, mut read) = ws_stream.split();
let every = std::time::Duration::from_secs(15);
let mut keepalive = tokio::time::interval_at(tokio::time::Instant::now() + every, every);
let mut ready = false;
loop {
tokio::select! {
_ = keepalive.tick() => write.send(Message::Text("ping".into())).await?, // keeps the connection open
frame = read.next() => {
let Some(frame) = frame else { break };
let Message::Text(text) = frame? else { continue };
if text.as_str() == "pong" { continue }
let msg: Value = serde_json::from_str(&text)?;
match msg["type"].as_str() {
Some("connected") => {
let join = json!({"type": "joinRoom", "payload": {"room": "ABC123", "presenterName": "Show control", "api": true}});
write.send(Message::Text(join.to_string().into())).await?;
}
Some("clicksEnabled") if msg["payload"] == true && !ready => {
ready = true;
let next = json!({"type": "webAction", "payload": {"action": "next", "id": "a1"}});
write.send(Message::Text(next.to_string().into())).await?;
// Also: {"action": "goto", "slide": 5}, {"action": "blackout", "on": true}, ...
}
Some("webActionResult") | Some("slide") | Some("error") => println!("{}", msg),
_ => {}
}
}
}
}
Ergebnisse und Ereignisse
Jeder Befehl wird mit einem webActionResult oder einem error beantwortet.
{ "type": "webActionResult", "payload": { "action": "next", "id": "a1", "hosts": 1 } }
hosts ist die Anzahl der Hosts, an die der Befehl zugestellt wurde. Den Nachweis, dass er ausgeführt wurde, liefert das nächste slide-Ereignis des Hosts; blackout ändert außerdem blackout im nächsten controls-Ereignis des Hosts. Ein Befehl, den der Host nicht ausführt (Klicks abgeschaltet, ein nicht unterstützter Befehl, ein goto über die letzte Folie hinaus), erhält ein Ergebnis, aber kein slide-Ereignis.
error | Wann |
|---|---|
auth_required | joinRoom mit api: true auf einer Verbindung ohne Session-Token |
not_api_client | Ein Befehl von einer Verbindung, die keinem Raum mit api: true beigetreten ist oder ihn bereits verlassen hat |
invalid_web_action | Unbekannte action oder fehlender bzw. ungültiger Wert für slide, on oder id |
invalid_timer_action | Unbekannte Timer-action oder fehlender bzw. ungültiger Wert für minutes oder id |
invalid_message | Eine Nachricht ohne text oder mit mehr als 1000 Zeichen |
invalid_presenter_mouse | Eine Zeigerposition außerhalb von 0 bis 1 |
rate_limited | Mehr als 10 Folien- und Timer-Befehle in einer Sekunde oder mehr als 5 Nachrichten in 10 Sekunden |
no_host | Mit dem Raum ist kein Host verbunden |
host_update_required | Jeder Host im Raum nutzt eine ältere Cliqer-Version, die keine Befehle von der API annehmen kann |
api_not_in_plan | Der Plan des Raumbesitzers enthält die Client API nicht |
Fehler enthalten eine lesbare message und, sofern der Befehl wohlgeformt war, dessen action und id:
{ "type": "error", "error": "invalid_web_action", "payload": { "message": "Unknown or incomplete webAction. Use next, prev, first, last, goto with slide, or blackout with on.", "action": "goto", "id": "a3" } }
Ereignisse eines Hosts enthalten dessen ID in hostId:
| Ereignis | Payload |
|---|---|
clicksEnabled | true, wenn der Host Ihre Befehle annimmt, false, wenn der Operator Ihre Klicks abgeschaltet hat |
controls | { app, nav, blackout, timer }: die Präsentations-App, die zusätzlichen Folienbefehle, die sie unterstützt (first, last, goto, blackout), ob der Bildschirm schwarz ist und ob der Host Timer-Befehle annimmt |
slide | { number, current, total, title, notes } nach jedem Befehl, den der Host ausgeführt hat (number ist "4/12", total ist null, wenn die App den Wert nicht meldet), oder null, wenn beim Host keine Präsentation geöffnet ist |
countdown | Die Zeit, die der Timer des Hosts anzeigt, etwa "4:59" ("-0:12" bei Überziehung), jede Sekunde, solange er läuft, oder null, wenn er stoppt |
countdownPhase | { phase, blink }: phase ist idle, talk, warning (weniger als eine Minute übrig), overtime oder paused, und blink gibt an, ob der Timer des Hosts blinkt |
messageFromHost | Eine Antwort des Operators am Host, siehe Nachrichten an den Host |
Sie erhalten außerdem die Ereignisse des Raums selbst (clientJoined, clientLeft und weitere); ignorieren Sie die, die Sie nicht verwenden. Wenn der Operator Ihre Verbindung entfernt oder sperrt, erhalten Sie logout mit reason: "kicked".
Timer
Bedienen Sie den Timer in der Cliqer-App des Hosts mit denselben Befehlen, die ein Stream Deck oder Bitfocus Companion sendet.
{ "type": "timerAction", "payload": { "action": "set", "minutes": 25, "id": "t1" } }
action | Funktion |
|---|---|
start | Timer starten oder nach stop fortsetzen |
stop | Timer pausieren |
reset | Timer stoppen und auf die eingestellte Zeit zurücksetzen |
plus1, plus2, plus5 | 1, 2 oder 5 Minuten hinzufügen |
minus1, minus2, minus5 | 1, 2 oder 5 Minuten abziehen |
preset5, preset10, preset15, preset20 | Timer stoppen und 5, 10, 15 oder 20 Minuten einstellen |
set | Timer stoppen und minutes einstellen, eine ganze Zahl von 1 bis 999 |
- Jeder Befehl wird mit einem
timerActionResult({ action, id, hosts },hostswie bei Folien) oder einemerrorbeantwortet. Den Nachweis, dass er ausgeführt wurde, liefert das nächstecountdown- odercountdownPhase-Ereignis des Hosts. - Der Timer läuft nur auf einem Host, dessen letztes
controls-Ereignistimer: trueenthält. Hosts mit einer älteren Cliqer-Version melden dies nicht und ignorieren Timer-Befehle. - Für Timer-Befehle gelten dieselben Regeln wie für Folienbefehle: Der Host führt sie nur aus, solange er Klicks von Ihrer Verbindung zulässt, und sie teilen sich das Kontingent von 10 Befehlen pro Sekunde.
- Die Timer-Einstellungen des Hosts gelten unverändert: Ein Timer, der auf Hochzählen eingestellt ist, zählt hoch, und ein Timer, der einem verbundenen Hardware-Timer folgt, übernimmt seine Zeit von diesem Gerät.
Laserpointer
Zeigen Sie einen Laserpunkt auf dem geteilten Bildschirm des Hosts an, so wie es das Smartphone eines Vortragenden tut.
{ "type": "presenterMouse", "payload": { "mouse": { "x": 0.5, "y": 0.25 } } }
xundygeben die Position auf dem geteilten Bildschirm an, gemessen von der oberen linken Ecke, von0bis1.- Senden Sie
{ "pressed": false }als Payload, um den Punkt auszublenden. Andernfalls verschwindet er 30 bis 90 Sekunden nach Ihrer letzten Position. - Der Punkt erscheint nur, solange der Host einen Bildschirm teilt und Laserpointer eingeschaltet hat. Seine Farbe richtet sich nach Ihrem
presenterName. - Senden Sie höchstens 30 Positionen pro Sekunde. Überzählige Positionen werden ohne Fehler verworfen, und es gibt kein Ergebnisereignis.
Nachrichten an den Host
Senden Sie dem Operator am Host eine Nachricht. Sie erscheint im Vortragenden-Chat des Hosts, genau wie eine Nachricht vom Smartphone eines Vortragenden, und die Antworten des Operators kommen als messageFromHost zurück.
# Join as an API client, then send a message. Replies print as they arrive.
( sleep 2
echo '{"type":"joinRoom","payload":{"room":"ABC123","presenterName":"Show control","api":true}}'
sleep 3
echo '{"type":"messageToHosts","payload":{"text":"Two minutes to the break","id":"m1"}}'
sleep 30 ) | websocat "$WS_URL/ws?token=YOUR_SESSION_TOKEN"
// After joining (see Slide Navigation): send a message, print the replies.
send({ type: 'messageToHosts', payload: { text: 'Two minutes to the break', id: 'm1' } })
ws.addEventListener('message', (event) => {
if (event.data === 'pong') return
const msg = JSON.parse(event.data)
if (msg.type === 'messageToHostsResult') console.log(`Delivered to ${msg.payload.hosts} host(s)`)
if (msg.type === 'messageFromHost') console.log(`${msg.payload.fromPresenterName}: ${msg.payload.originalPayload.text}`)
})
# After joining (see Slide Navigation): send a message.
send(ws, {"type": "messageToHosts", "payload": {"text": "Two minutes to the break", "id": "m1"}})
# In on_message, print the replies:
# elif msg["type"] == "messageFromHost":
# print(f"{msg['payload']['fromPresenterName']}: {msg['payload']['originalPayload']['text']}")
// After joining (see Slide Navigation): send a message.
send(map[string]interface{}{
"type": "messageToHosts",
"payload": map[string]interface{}{"text": "Two minutes to the break", "id": "m1"},
})
// In the read loop, print the replies:
// case "messageFromHost":
// fmt.Printf("%s\n", msg.Payload)
// After joining (see Slide Navigation): send a message.
$client->text(json_encode([
'type' => 'messageToHosts',
'payload' => ['text' => 'Two minutes to the break', 'id' => 'm1']
]));
// In the read loop, print the replies:
// } elseif ($msg['type'] === 'messageFromHost') {
// echo $msg['payload']['originalPayload']['text'], PHP_EOL;
// After joining (see Slide Navigation): send a message.
let message = json!({"type": "messageToHosts", "payload": {"text": "Two minutes to the break", "id": "m1"}});
write.send(Message::Text(message.to_string().into())).await?;
// In the read loop, print the replies:
// Some("messageFromHost") => println!("{}", msg["payload"]["originalPayload"]["text"]),
Antwort des Hosts:
{
"type": "messageFromHost",
"payload": {
"fromClientId": "host_abc123",
"fromPresenterName": "Stage PC",
"originalPayload": { "text": "Thanks, got it" }
},
"hostId": "host_abc123"
}
textumfasst 1 bis 1000 Zeichen. Senden Sie höchstens 5 Nachrichten alle 10 Sekunden.- Jede Nachricht wird mit einem
messageToHostsResult({ id, hosts }) oder einemerrorbeantwortet. - Der Operator sieht die Nachricht unter Ihrem
presenterName, und der Host zeigt eine Benachrichtigung an, wenn seine Einstellung Benachrichtigungen aktiviert ist.
Ereignisreferenz
Ausgehende Ereignisse (Client → Server)
| Ereignis | Beschreibung |
|---|---|
joinRoom | Einem Raum als API-Client mit api: true beitreten (siehe Foliensteuerung) |
webAction | Foliensteuerung (siehe Foliensteuerung) |
timerAction | Timer-Steuerung (siehe Timer) |
presenterMouse | Position des Laserpointers (siehe Laserpointer) |
messageToHosts | Eine Nachricht an den Operator am Host senden (siehe Nachrichten an den Host) |
logout | Den Raum verlassen; die Verbindung bleibt offen und kann erneut beitreten |
Eingehende Ereignisse (Server → Client)
| Ereignis | Beschreibung |
|---|---|
connected | Wird beim Öffnen mit { clientId, userId, isAuthenticated } gesendet |
joinedRoom | Dem Raum erfolgreich beigetreten |
clientJoined | Jemand ist dem Raum beigetreten |
clientLeft | Jemand hat den Raum verlassen |
webActionResult | Eine webAction wurde an die Hosts des Raums zugestellt |
timerActionResult | Eine timerAction wurde an die Hosts des Raums zugestellt |
messageToHostsResult | Eine messageToHosts-Nachricht wurde an die Hosts des Raums zugestellt |
clicksEnabled | Ob ein Host Ihre Befehle annimmt |
controls | Was die Präsentations-App und der Timer eines Hosts unterstützen |
slide | Die aktuelle Folie eines Hosts nach einem Befehl |
countdown | Die Zeit auf dem Timer eines Hosts |
countdownPhase | Die Phase des Timers eines Hosts |
messageFromHost | Eine Nachricht vom Operator am Host |
logout | Der Operator hat Ihre Verbindung entfernt oder gesperrt (reason: "kicked") |
error | Fehlermeldung |