Message Protocol
Understanding Socket.IO events, payloads, and the request/response pattern
Message Protocol
SkySpy uses Socket.IO's event-based protocol for bidirectional communication. Learn about events, payloads, batching, and the request/response pattern.
Event-Based API
Socket.IO uses events instead of raw messages. Each event has:
- Name (string): The event type (e.g.,
subscribe,aircraft:update) - Payload (usually an object): The data associated with the event
ConventionSkySpy uses colon-separated namespacing for events (e.g.,
aircraft:update,safety:event). This makes it easy to identify the domain and action.
Client → Server Events
These are the events your client can emit to the server.
| Event | Payload | Description | Example |
|---|---|---|---|
subscribe | { topics: string[] } | Subscribe to topics (e.g., ['aircraft','safety']; use 'all' for all topics) | { topics: ['aircraft'] } |
unsubscribe | { topics: string[] } | Unsubscribe from topics | { topics: ['stats'] } |
request | { type, request_id, params? } | On-demand query; server replies with response or error | See request/response section |
ping | optional data | Custom keepalive; server replies with pong | null or { timestamp } |
Example: Subscribe
// Subscribe to multiple topics
socket.emit('subscribe', {
topics: ['aircraft', 'safety', 'alerts']
});
// Subscribe to all topics
socket.emit('subscribe', {
topics: ['all']
});# Subscribe to multiple topics
sio.emit('subscribe', {
'topics': ['aircraft', 'safety', 'alerts']
})
# Subscribe to all topics
sio.emit('subscribe', {
'topics': ['all']
})Server → Client Events
These are the events the server emits to your client.
Control Events
| Event | When | Payload | Description |
|---|---|---|---|
subscribed | After subscribe | { topics, joined?, denied? } | Confirms subscription; lists joined and denied topics |
unsubscribed | After unsubscribe | { topics, remaining } | Confirms unsubscription; lists remaining subscriptions |
response | Reply to request | { type, request_id, request_type, data } | Successful response to a request event |
error | Request failed or generic error | { type?, request_id?, message } | Error message with optional request context |
pong | Reply to ping | { timestamp } | Keepalive response |
Data Events
| Event | Topic | Payload | Description |
|---|---|---|---|
aircraft:snapshot | aircraft | { aircraft[], count, timestamp } | Initial snapshot on connect or request |
aircraft:update | aircraft | aircraft list or delta | Periodic position updates (rate-limited) |
aircraft:new | aircraft | single aircraft object | New aircraft detected in range |
aircraft:remove | aircraft | { hex, reason? } | Aircraft left range or timeout |
aircraft:delta | aircraft | delta object | Only changed fields (bandwidth optimization) |
aircraft:heartbeat | aircraft | { count, timestamp } | Periodic keepalive with aircraft count |
safety:snapshot | safety | { events[], count, timestamp } | Initial safety event snapshot |
safety:event | safety | event object | New safety event (TCAS, emergency, etc.) |
alert:triggered | alerts | alert payload | Custom alert rule fired |
acars:message | acars | message object | New ACARS datalink message |
stats:update | stats | stats object | Live statistics update |
batch | all | { messages[], count?, timestamp? } | Batched messages (high-frequency optimization) |
Payload CompatibilityPayloads match the formats described in the REST API documentation. Only the transport mechanism is different (Socket.IO events vs. HTTP responses).
Batch Messages
To optimize bandwidth, high-frequency updates may be batched into a single batch event.
{
"messages": [
{
"type": "aircraft:update",
"data": {
"hex": "A1B2C3",
"lat": 37.7749,
"lon": -122.4194,
"alt_baro": 35000
}
},
{
"type": "aircraft:update",
"data": {
"hex": "D4E5F6",
"lat": 37.8044,
"lon": -122.2712,
"alt_baro": 28000
}
}
],
"count": 2,
"timestamp": "2024-01-15T10:30:00.000Z"
}Handling Batch Events
socket.on('batch', (data) => {
data.messages.forEach(msg => {
// Dispatch to individual handlers
switch (msg.type) {
case 'aircraft:update':
handleAircraftUpdate(msg.data);
break;
case 'safety:event':
handleSafetyEvent(msg.data);
break;
default:
console.log('Unknown message type:', msg.type);
}
});
});@sio.event
def batch(data):
for msg in data.get('messages', []):
msg_type = msg.get('type', '')
msg_data = msg.get('data', msg)
if msg_type == 'aircraft:update':
handle_aircraft_update(msg_data)
elif msg_type == 'safety:event':
handle_safety_event(msg_data)
else:
print(f'Unknown message type: {msg_type}')
Critical Events Bypass BatchingCritical event types (alert, safety, emergency) bypass batching and are emitted immediately to ensure low latency for important events.
Batch Configuration
| Parameter | Default Value | Description |
|---|---|---|
| Batch Window | ~200 ms | Time to collect messages before sending batch |
| Max Batch Size | ~50 messages | Maximum messages per batch |
| Max Batch Bytes | ~1 MB | Maximum payload size per batch |
| Bypass Types | alert, safety, emergency | Event types that skip batching |
Request/Response Pattern
For on-demand queries (historical data, aircraft info, etc.), use the request event with a unique request_id.
Request Flow
sequenceDiagram
participant C as Client
participant S as Server
C->>S: request { type, request_id, params }
alt Success
S->>C: response { request_id, data }
else Error
S->>C: error { request_id, message }
end
Request Format
{
"type": "aircraft-info",
"request_id": "req_abc123",
"params": {
"icao": "A1B2C3"
}
}| Field | Required | Description |
|---|---|---|
type | Yes | Request type (e.g., aircraft-info, sightings, safety-events) |
request_id | Yes | Unique identifier to match response (client-generated) |
params | No | Parameters specific to the request type |
Response Format
Success:
{
"type": "response",
"request_id": "req_abc123",
"request_type": "aircraft-info",
"data": {
"icao_hex": "A1B2C3",
"registration": "N12345",
"type_code": "B738",
"operator": "Southwest Airlines",
"manufactured": "2015",
"model": "Boeing 737-800"
}
}Error:
{
"type": "error",
"request_id": "req_abc123",
"message": "Aircraft not found"
}Request/Response Helper
Here's a helper function to handle request/response with timeouts.
function request(socket, type, params = {}, timeoutMs = 10000) {
return new Promise((resolve, reject) => {
const requestId = `req_${Date.now()}_${Math.random().toString(36).slice(2)}`;
const timeout = setTimeout(() => {
cleanup();
reject(new Error(`Request timeout: ${type}`));
}, timeoutMs);
const onResponse = (data) => {
if (data.request_id !== requestId) return;
cleanup();
resolve(data.data ?? data);
};
const onError = (data) => {
if (data.request_id !== requestId) return;
cleanup();
reject(new Error(data.message || 'Request failed'));
};
const cleanup = () => {
clearTimeout(timeout);
socket.off('response', onResponse);
socket.off('error', onError);
};
socket.on('response', onResponse);
socket.on('error', onError);
socket.emit('request', { type, request_id: requestId, params });
});
}
// Usage
try {
const info = await request(socket, 'aircraft-info', { icao: 'A1B2C3' });
console.log('Aircraft:', info);
} catch (error) {
console.error('Request failed:', error.message);
}import uuid
import asyncio
from typing import Any, Dict, Optional
class RequestHelper:
def __init__(self, sio):
self.sio = sio
self.pending = {}
@sio.event
def response(data):
request_id = data.get('request_id')
if request_id in self.pending:
future = self.pending.pop(request_id)
future.set_result(data.get('data', data))
@sio.event
def error(data):
request_id = data.get('request_id')
if request_id in self.pending:
future = self.pending.pop(request_id)
future.set_exception(Exception(data.get('message', 'Request failed')))
async def request(self, req_type: str, params: Optional[Dict] = None, timeout: float = 10.0) -> Any:
request_id = f"req_{uuid.uuid4().hex}"
future = asyncio.Future()
self.pending[request_id] = future
self.sio.emit('request', {
'type': req_type,
'request_id': request_id,
'params': params or {}
})
try:
return await asyncio.wait_for(future, timeout=timeout)
except asyncio.TimeoutError:
self.pending.pop(request_id, None)
raise TimeoutError(f'Request timeout: {req_type}')
# Usage
helper = RequestHelper(sio)
try:
info = await helper.request('aircraft-info', {'icao': 'A1B2C3'})
print('Aircraft:', info)
except Exception as e:
print('Request failed:', e)Rate Limits
Different topics have different rate limits to optimize bandwidth and prevent overwhelming clients.
| Topic / Event | Max Rate | Min Interval | Notes |
|---|---|---|---|
aircraft:update | ~10 Hz | 100 ms | Full position updates |
aircraft:delta | ~10 Hz | 100 ms | Delta updates (changed fields only) |
stats:update | ~0.5 Hz | 2 s | Live statistics |
| Default | ~5 Hz | 200 ms | Other event types |
Client-Side ThrottlingIf your application can't keep up with the update rate, implement client-side throttling or request lower-frequency updates. Critical events (alerts, safety) are never rate-limited.
Next Steps
Ready to stream data?Learn about the main namespace to start receiving aircraft positions, safety events, and alerts. Or explore specialized namespaces for audio and mobile features.
Updated 27 days ago