WebSocket בזמן אמת

התחברו אל wss://cliqer.io/ws כדי לשלוט בחדר Cliqer מהתוכנה שלכם או ממערכת בקרת המופע שלכם, כמו בשלט למצגות מרחוק: החלפת שקפים, הפעלת הטיימר של המארח, הצבעה עם נקודת לייזר ושליחת הודעות למארח. האפליקציה שלכם מצטרפת לחדר כמציג; את החדר עצמו מארחת תמיד אפליקציית שולחן העבודה של Cliqer.
session_token (דפדפנים שולחים אותו אוטומטית), פרמטר השאילתה ?token= או הכותרת Authorization: Bearer. האסימון נבדק בעת ההתחברות ולא בכל פקודה, ולכן כשתוקפו פג יש לפתוח חיבור חדש עם אסימון חדש ולהצטרף שוב. ללא אסימון אפשר להתחבר, אך אי אפשר להשתמש ב-API. מפתחות API של הצוות (cliqer_...) מאמתים רק את נקודות הקצה של REST; ב-socket הזה מפתח אינו אסימון סשן.חיבור
# 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);
}
_ => {}
}
}
השרת שולח connected עם ה-clientId שלכם לאחר שהגדיר את החיבור. יש להמתין לו לפני שליחת כל דבר: הודעה שמגיעה לפני כן נדחית עם error והחיבור נסגר.
זרימת החיבור והחדר
ניווט בשקפים
שליטה בשקפים בחדר מהתוכנה שלכם, כמו בשלט למצגות מרחוק. האפליקציה שלכם מצטרפת לחדר כמציג, כך שהמארח רואה אותה ברשימת המציגים תחת ה-presenterName שלה ושולט בה כמו בכל מציג: המפעיל יכול לכבות את הלחיצות שלה, להסיר אותה או לחסום אותה.
- יש להתחבר עם אסימון סשן (
?token=, הכותרתAuthorization: Bearerאו קובץ ה-cookiesession_token). מפתחות API של הצוות פועלים רק בנקודות הקצה של REST, לא ב-socket הזה. - התוכנית של בעל החדר חייבת לכלול את ה-Client API: תוסף ה-API בתוכניות Presenter, Studio HD ו-Studio 4K, וכלול בתוכניות Studio 8K ו-Enterprise.
- פקודות שקפים וטיימר פועלות רק כל עוד המארח מאפשר לחיצות מהחיבור שלכם.
הצטרפות לחדר
{ "type": "joinRoom", "payload": { "room": "ABC123", "presenterName": "Show control", "api": true } }
- יש לשלוח אותה אחרי האירוע
connected: השרת דוחה כל דבר שמגיע לפניו. presenterNameהוא השם שהמארח רואה (ברירת המחדל:API). האפליקציה שלכם היא תמיד מציג בחדר, ואף פעם לא המארח שלו.- יש להמתין לאירוע
clicksEnabledעם payloadtrueלפני שליחת פקודות. המארח שולח אותו לאחר שהגדיר את החיבור שלכם, ושוב בכל פעם שהמפעיל מפעיל או מכבה את הלחיצות שלכם. - שמרו את ה-
presenterKeyמ-joinedRoomושלחו אותו ב-joinRoomבעת התחברות מחדש, כדי שהמארח ישמור על הרשאת הלחיצות שלכם. - שלחו את מסגרת הטקסט
pingכל 15 שניות כדי לשמור על החיבור פתוח. השרת עונה במסגרת הטקסטpong(לא JSON).
שליחת פקודות
{ "type": "webAction", "payload": { "action": "goto", "slide": 5, "id": "a3" } }
action | שדה נוסף | מה זה עושה |
|---|---|---|
next | השקף הבא (או שלב ההנפשה הבא) | |
prev | השקף הקודם (או שלב ההנפשה הקודם) | |
first | השקף הראשון | |
last | השקף האחרון | |
goto | slide: מספר שלם מ-1 עד 9999 | מעבר לשקף הזה |
blackout | on: true או false | השחרת המסך, או הצגתו שוב |
idהוא אופציונלי: 1 עד 64 אותיות, ספרות,_,.,:או-. הוא חוזר בתוצאה. השתמשו ב-idחדש לכל פקודה: המארח מתעלם מפקודה שאת ה-idשלה כבר קיבל מהחיבור שלכם ב-5 השניות האחרונות, כך שניסיון חוזר של אותה פקודה מתבצע פעם אחת בלבד.first,last,gotoו-blackoutפועלות רק במארח שאירוע ה-controlsהאחרון שלו מפרט אותן ב-nav. מארח שאינו שולח אירועcontrolsתומך ב-nextוב-prevבלבד. כשיש כמה מארחים בחדר, כל מארח מבצע את הפקודות שהוא תומך בהן.nextאוprevבזמן שהמסך מושחר מציגות אותו שוב בלי לזוז.- המערכת מתעלמת מ-
gotoאל מעבר לשקף האחרון. - שלחו לכל היותר 10 פקודות בשנייה, פקודות שקפים וטיימר יחד. המארח גם מתעלם מחזרה על אותה פקודה בתוך 50 מילישניות.
מה כל אפליקציית מצגות תומכת בו כיום:
| אפליקציית מצגות | next, prev | first, last | goto | blackout |
|---|---|---|---|---|
| PowerPoint ב-Windows | כן | כן | כן | כן |
| PowerPoint ב-Mac | כן | כן | לא | לא |
| Keynote | כן | לא | לא | כן |
| Google Slides (עם תוסף הדפדפן מחובר) | כן | כן | לא | לא |
| vMix | כן | לא | לא | לא |
| חלון ממוקד | כן | לא | לא | לא |
אירוע ה-controls הוא התשובה העדכנית עבור המארח שבו אתם שולטים: יש לקרוא את nav ממנו ולא מהטבלה הזו.
# 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),
_ => {}
}
}
}
}
תוצאות ואירועים
כל פקודה נענית ב-webActionResult או ב-error.
{ "type": "webActionResult", "payload": { "action": "next", "id": "a1", "hosts": 1 } }
hosts הוא מספר המארחים שהפקודה נמסרה אליהם. ההוכחה שהיא בוצעה היא אירוע ה-slide הבא של המארח; blackout משנה גם את blackout באירוע ה-controls הבא של המארח. פקודה שהמארח אינו מבצע (לחיצות כבויות, פקודה שהוא אינו תומך בה, goto אל מעבר לשקף האחרון) מקבלת תוצאה, אך לא אירוע slide.
error | מתי |
|---|---|
auth_required | joinRoom עם api: true בחיבור ללא אסימון סשן |
not_api_client | פקודה מחיבור שלא הצטרף לחדר עם api: true, או אחרי שעזב אותו |
invalid_web_action | action לא מוכר, או slide, on או id חסר או לא תקין |
invalid_timer_action | action טיימר לא מוכר, או minutes או id חסר או לא תקין |
invalid_message | הודעה ללא text, או ארוכה מ-1000 תווים |
invalid_presenter_mouse | מיקום סמן מחוץ לטווח 0 עד 1 |
rate_limited | יותר מ-10 פקודות שקפים וטיימר בשנייה אחת, או יותר מ-5 הודעות ב-10 שניות |
no_host | אין מארח מחובר לחדר |
host_update_required | כל המארחים בחדר מריצים גרסה ישנה של Cliqer שאינה יכולה לקבל פקודות מה-API |
api_not_in_plan | התוכנית של בעל החדר אינה כוללת את ה-Client API |
שגיאות כוללות message קריא, וכשהפקודה הייתה בנויה כהלכה, גם את ה-action וה-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" } }
אירועים ממארח נושאים את המזהה של אותו מארח ב-hostId:
| אירוע | תוכן |
|---|---|
clicksEnabled | true כשהמארח מקבל את הפקודות שלכם, false כשהמפעיל כיבה את הלחיצות שלכם |
controls | { app, nav, blackout, timer }: אפליקציית המצגות, פקודות השקפים הנוספות שהיא תומכת בהן (first, last, goto, blackout), האם המסך מושחר, והאם המארח מקבל פקודות טיימר |
slide | { number, current, total, title, notes } אחרי כל פקודה שהמארח ביצע (number הוא "4/12", ו-total הוא null כשהאפליקציה אינה מדווחת עליו), או null כשאין למארח מצגת פתוחה |
countdown | הזמן שהטיימר של המארח מציג, למשל "4:59" ("-0:12" בחריגה מהזמן), בכל שנייה בזמן שהוא פועל, או null כשהוא נעצר |
countdownPhase | { phase, blink }: phase הוא idle, talk, warning (נותרה פחות מדקה), overtime או paused, ו-blink מציין אם הטיימר של המארח מהבהב |
messageFromHost | תשובה ממפעיל המארח, ראו הודעות למארח |
תקבלו גם את האירועים של החדר עצמו (clientJoined, clientLeft ואחרים); אפשר להתעלם מאלה שאינכם משתמשים בהם. אם המפעיל מסיר או חוסם את החיבור שלכם, תקבלו logout עם reason: "kicked".
טיימר
הפעלת הטיימר באפליקציית Cliqer של המארח באמצעות אותן פקודות ש-Stream Deck או Bitfocus Companion שולחים.
{ "type": "timerAction", "payload": { "action": "set", "minutes": 25, "id": "t1" } }
action | מה זה עושה |
|---|---|
start | הפעלת הטיימר, או המשך אחרי stop |
stop | השהיית הטיימר |
reset | עצירת הטיימר וחזרה לזמן שהוגדר בו |
plus1, plus2, plus5 | הוספת 1, 2 או 5 דקות |
minus1, minus2, minus5 | הפחתת 1, 2 או 5 דקות |
preset5, preset10, preset15, preset20 | עצירת הטיימר והגדרת 5, 10, 15 או 20 דקות |
set | עצירת הטיימר והגדרת minutes, מספר שלם מ-1 עד 999 |
- כל פקודה נענית ב-
timerActionResult({ action, id, hosts }, ו-hostsכמו בשקפים) או ב-error. ההוכחה שהיא בוצעה היא אירוע ה-countdownאו ה-countdownPhaseהבא של המארח. - הטיימר פועל רק במארח שאירוע ה-
controlsהאחרון שלו כוללtimer: true. מארחים בגרסה ישנה של Cliqer אינם מכריזים על כך ומתעלמים מפקודות טיימר. - פקודות טיימר כפופות לאותם כללים כמו פקודות שקפים: המארח מבצע אותן רק כל עוד הוא מאפשר לחיצות מהחיבור שלכם, והן חולקות את המכסה של 10 פקודות בשנייה.
- הגדרות הטיימר של המארח חלות כפי שהן: טיימר שמוגדר לספירה עולה סופר כלפי מעלה, וטיימר שעוקב אחר טיימר חומרה מחובר לוקח את הזמן שלו מאותו התקן.
סמן לייזר
הצגת נקודת לייזר על המסך המשותף של המארח, בדיוק כמו מהטלפון של מציג.
{ "type": "presenterMouse", "payload": { "mouse": { "x": 0.5, "y": 0.25 } } }
xו-yהם המיקום על המסך המשותף ביחס לפינה השמאלית העליונה שלו, מ-0עד1.- שלחו
{ "pressed": false }כ-payload כדי להסתיר את הנקודה. אחרת היא נעלמת 30 עד 90 שניות אחרי המיקום האחרון ששלחתם. - הנקודה מוצגת רק כשהמארח משתף מסך והאפשרות סמן לייזר מופעלת אצלו. הצבע שלה נקבע לפי ה-
presenterNameשלכם. - שלחו לכל היותר 30 מיקומים בשנייה. מיקומים עודפים נזרקים ללא שגיאה, ואין אירוע תוצאה.
הודעות למארח
שליחת הודעה למפעיל המארח. היא מופיעה בצ'אט המציגים של המארח, בדיוק כמו הודעה מהטלפון של מציג, ותשובות המפעיל חוזרות כ-messageFromHost.
# 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"]),
תשובה מהמארח:
{
"type": "messageFromHost",
"payload": {
"fromClientId": "host_abc123",
"fromPresenterName": "Stage PC",
"originalPayload": { "text": "Thanks, got it" }
},
"hostId": "host_abc123"
}
- אורך
textהוא 1 עד 1000 תווים. שלחו לכל היותר 5 הודעות בכל 10 שניות. - כל הודעה נענית ב-
messageToHostsResult({ id, hosts }) או ב-error. - המפעיל רואה את ההודעה תחת ה-
presenterNameשלכם, והמארח מציג התראה כשההגדרה התראות שלו מופעלת.
מדריך אירועים
אירועים יוצאים (לקוח → שרת)
| אירוע | תיאור |
|---|---|
joinRoom | הצטרפות לחדר כלקוח API עם api: true (ראו ניווט בשקפים) |
webAction | שליטה בשקפים (ראו ניווט בשקפים) |
timerAction | שליטה בטיימר (ראו טיימר) |
presenterMouse | מיקום סמן הלייזר (ראו סמן לייזר) |
messageToHosts | שליחת הודעה למפעיל המארח (ראו הודעות למארח) |
logout | יציאה מהחדר; החיבור נשאר פתוח ואפשר להצטרף שוב |
אירועים נכנסים (שרת → לקוח)
| אירוע | תיאור |
|---|---|
connected | נשלח עם פתיחת החיבור, עם { clientId, userId, isAuthenticated } |
joinedRoom | ההצטרפות לחדר הצליחה |
clientJoined | מישהו הצטרף לחדר |
clientLeft | מישהו עזב את החדר |
webActionResult | webAction נמסר למארחי החדר |
timerActionResult | timerAction נמסר למארחי החדר |
messageToHostsResult | messageToHosts נמסר למארחי החדר |
clicksEnabled | האם מארח מקבל את הפקודות שלכם |
controls | במה תומכים אפליקציית המצגות והטיימר של מארח |
slide | השקף הנוכחי של מארח אחרי פקודה |
countdown | הזמן בטיימר של מארח |
countdownPhase | השלב של הטיימר של מארח |
messageFromHost | הודעה ממפעיל המארח |
logout | המפעיל הסיר או חסם את החיבור שלכם (reason: "kicked") |
error | הודעת שגיאה |