7.1 KiB
Command Channel
Complete working code for custom command messaging between participants on Web.
Overview
The command channel enables custom data exchange between participants within the same session. Use cases:
- Application-specific signaling
- Session transfer / waiting room coordination
- Real-time collaboration data
- Custom control messages
+-------------------------------------------------------------------+
| COMMAND CHANNEL FLOW (Web) |
+-------------------------------------------------------------------+
| Setup: |
| client.join() -> client.getCommandClient() |
| |
| Sender: |
| cmdClient.send(message) [broadcast to all] |
| cmdClient.send(message, userId) [targeted] |
| |
| Receiver: |
| client.on('command-channel-message', callback) |
| |
| IMPORTANT: Command channel is SESSION-SCOPED. |
| It does NOT span across different sessions. |
+-------------------------------------------------------------------+
Key difference from native SDKs: Web uses getCommandClient() (NOT getCmdChannel() like Linux/Windows native SDKs).
Limitations
| Limit | Value |
|---|---|
| Max message rate | 60 messages/second |
| Max message size | ~1KB recommended |
| Reliability | Best effort (not guaranteed) |
| Scope | Same session only |
Note: Commands are not persisted - late joiners won't receive previous commands.
Setup Order (Critical)
You MUST call getCommandClient() and register listeners AFTER client.join(). The command channel subsystem is not active until after joining a session.
// CORRECT: Everything after join
await client.join(topic, jwt, userName);
const cmdClient = client.getCommandClient();
client.on('command-channel-status', (status) => { /* ... */ });
client.on('command-channel-message', (payload) => { /* ... */ });
// WRONG: Registering before join — command channel not active yet
await client.init('en-US', 'Global', { patchJsMedia: true });
client.on('command-channel-message', handler); // WILL NOT RECEIVE MESSAGES
await client.join(topic, jwt, userName);
Without calling getCommandClient() after join, the command channel may never connect and command-channel-status will never fire.
Complete Working Example
import ZoomVideo from '@zoom/videosdk';
const client = ZoomVideo.createClient();
async function joinAndSetupCommandChannel(topic, jwt, userName) {
// 1. Initialize
await client.init('en-US', 'Global', { patchJsMedia: true });
// 2. Join session
await client.join(topic, jwt, userName);
// 3. Activate command channel (MUST be after join)
const cmdClient = client.getCommandClient();
// 4. Listen for connection status
client.on('command-channel-status', (status) => {
console.log('Command channel status:', status);
if (status === true) {
console.log('Command channel connected - ready to send');
}
});
// 5. Listen for incoming commands
client.on('command-channel-message', (payload) => {
try {
const data = JSON.parse(payload.text);
console.log('Command received:', data);
handleCommand(data);
} catch (e) {
console.log('Raw command received:', payload.text);
}
});
return cmdClient;
}
// Send a command (broadcast to all participants in session)
function sendCommand(cmdClient, type, data) {
const message = JSON.stringify({ type, data });
cmdClient.send(message);
}
// Send to a specific participant
function sendCommandToUser(cmdClient, userId, type, data) {
const message = JSON.stringify({ type, data });
cmdClient.send(message, userId);
}
// Handle incoming commands
function handleCommand(data) {
switch (data.type) {
case 'ping':
console.log('Received ping');
break;
case 'transfer':
console.log('Transfer info:', data);
break;
default:
console.log('Unknown command type:', data.type);
}
}
Events Reference
command-channel-status
Fires when the command channel connection status changes.
client.on('command-channel-status', (status) => {
// status: boolean - true when connected, false when disconnected
if (status) {
// Safe to send commands now
}
});
command-channel-message
Fires when a command is received from another participant.
client.on('command-channel-message', (payload) => {
// payload.text: string - the command message content
// payload.senderId: number - user ID of the sender
const message = payload.text;
console.log('Received:', message);
});
Cross-Platform Compatibility
When sending commands between Web and Linux/Windows native SDKs:
| Property | Web SDK | Linux SDK | Windows SDK |
|---|---|---|---|
| Get channel | getCommandClient() |
getCmdChannel() |
getCmdChannel() |
| Send broadcast | cmdClient.send(msg) |
sendCommand(nullptr, msg) |
sendCommand(NULL, msg) |
| Send targeted | cmdClient.send(msg, userId) |
sendCommand(user, msg) |
sendCommand(user, msg) |
| String type | JavaScript string | const char* (UTF-8) |
const wchar_t* (wide) |
| Receive event | command-channel-message |
onCommandReceived callback |
onCommandReceived callback |
Use JSON strings as the message format for cross-platform compatibility.
Common Issues
Commands Not Received
Cause: getCommandClient() not called after join, or listeners registered before join
Fix: Ensure setup order is correct — join first, then getCommandClient(), then register listeners.
command-channel-status Never Fires
Cause: getCommandClient() was not called
Fix: You must explicitly call client.getCommandClient() after join to activate the channel.
Cross-SDK Messages Not Arriving
Cause: Version mismatch or timing issues between different SDK platforms
Fix: Add a server-side polling fallback:
const pollTimer = setInterval(async () => {
const res = await fetch(`/api/status/${sessionName}`);
const data = await res.json();
if (data.ready) {
clearInterval(pollTimer);
handleData(data);
}
}, 3000);
Related Documentation
- Session Join Pattern - Session setup
- Event Handling - Event listener patterns
- Linux Command Channel - Linux native equivalent
- Windows Command Channel - Windows native equivalent
- Authorization - JWT roleType for host/co-host