Best Practices
Error Handling
All SDKs throw StarfishError with a code, message, and optional details. Handle errors based on the code:
import { StarfishError } from "@starfish/client";
try {
await client.connect();
await client.join("my-session");
} catch (err) {
if (err instanceof StarfishError) {
switch (err.code) {
case "CONNECTION_FAILED":
console.error("Could not reach server");
break;
case "NO_WEBSOCKET":
console.error("Provide a WebSocket factory for Node.js");
break;
default:
console.error(`Starfish error [${err.code}]: ${err.message}`);
}
}
}from starfish import StarfishError
try:
await client.connect()
await client.join("my-session")
except StarfishError as err:
if err.code == "CONNECTION_FAILED":
print("Could not reach server")
else:
print(f"Starfish error [{err.code}]: {err}")import StarfishClient
do {
try await client.connect()
try await client.join(session: "my-session")
} catch let error as StarfishError {
switch error.code {
case "CONNECTION_FAILED":
print("Could not reach server")
default:
print("Starfish error [\(error.code)]: \(error.message)")
}
}Reconnection Strategies
The default reconnection settings work well for most cases, but you may want to tune them:
Interactive applications (live performances, real-time collaboration):
{
reconnect: {
enabled: true,
maxRetries: Infinity,
baseDelayMs: 500, // start retrying faster
maxDelayMs: 10_000, // cap at 10 seconds
},
}Background connections (monitoring, logging):
{
reconnect: {
enabled: true,
maxRetries: 20,
baseDelayMs: 2000,
maxDelayMs: 60_000,
},
}No reconnection (one-shot connections):
{
reconnect: { enabled: false },
}Monitor connection state to update your UI:
client.connectionState$.subscribe((state) => {
switch (state) {
case "connected":
showStatus("Connected");
break;
case "reconnecting":
showStatus("Reconnecting...");
break;
case "disconnected":
showStatus("Disconnected");
break;
}
});client.connection_state.subscribe(lambda state: show_status(state))Task {
for await state in client.connectionState {
showStatus(state.rawValue)
}
}Delivery Option Tradeoffs
Choose the right delivery mode for your use case:
| Use Case | Reliability | Why |
|---|---|---|
| Chat messages | reliable (default) | Every message must arrive |
| Cursor positions | unreliable | Dropped messages are fine — the next update replaces it |
| Slider values | latest | Only the most recent value matters |
| Game state snapshots | reliable | State must be consistent |
| Audio/video metadata | unreliable | Low latency matters more than completeness |
| Sensor readings | unreliable | High frequency, latest value is what counts |
When WebRTC is enabled, unreliable and latest messages prefer the RTC transport for lower latency. reliable messages use WebSocket by default, ensuring server-side persistence and ordering.
Resource Cleanup
Always clean up when you're done to avoid memory leaks and stale connections.
Unsubscribe from Observables
Every .subscribe() call returns an unsubscribe function. Call it when you no longer need updates:
const unsub = client.topics.topic$("cursor").subscribe((frame) => {
renderCursor(frame.payload);
});
// Later, when cleaning up:
unsub();Unsubscribe from Topics
Unsubscribing from a topic tells the server to stop sending you messages:
await client.topics.unsubscribe("cursor");Leave and Disconnect
When your client is done, leave the session and disconnect:
await client.leave();
await client.disconnect();disconnect() automatically stops the heartbeat, clears presence, closes RTC connections, and tears down the WebSocket.
Full Cleanup Example
// Store all unsubscribe functions
const cleanups: (() => void)[] = [];
cleanups.push(client.topic$("cursor").subscribe(handleCursor));
cleanups.push(client.presence$.subscribe(handlePresence));
cleanups.push(client.connectionState$.subscribe(handleState));
// On shutdown
function cleanup() {
cleanups.forEach((fn) => fn());
client.disconnect();
}Size Limits
Be aware of payload size limits to avoid PAYLOAD_TOO_LARGE errors:
| Limit | Size |
|---|---|
| WebSocket message | 64 KB |
| RTC control channel message | 64 KB |
| RTC stream channel message | 16 KB |
| Presence data (per client) | 8 KB |
| Data value (per save) | 256 KB |
| Topic name length | 128 characters |
| Client meta | 16 KB |
If you need to send larger data, consider splitting it into chunks or using an external storage service and sharing references via Starfish.