Getting Started
Install and configure the SDK in your React app.
Installation
Install the SDK and its peer dependencies:
npm install @cntrl-pw/sdk @tanstack/react-query
pnpm add @cntrl-pw/sdk @tanstack/react-query
yarn add @cntrl-pw/sdk @tanstack/react-query
bun add @cntrl-pw/sdk @tanstack/react-query
Setup Providers
Wrap your app with the required providers:
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { BridgesProvider } from "@cntrl-pw/sdk";
const queryClient = new QueryClient();
function App() {
return (
<QueryClientProvider client={queryClient}>
<BridgesProvider>{/* Your app */}</BridgesProvider>
</QueryClientProvider>
);
}
Provider Options
import { BridgesProvider, type SdkError } from "@cntrl-pw/sdk";
import { toast } from "sonner"; // or your toast library
<BridgesProvider
autoConnect={true} // Auto-connect to all saved bridges on mount (default: true)
onError={(error: SdkError) => {
toast.error(error.message);
}}
>
By default, bridges are persisted to localStorage via Zustand. To use a different storage backend (IndexedDB, SQLite, REST API), see Custom Storage.
Adding Your First Bridge
import { useBridges } from "@cntrl-pw/sdk";
function AddBridgeForm() {
const { addBridge } = useBridges();
const [host, setHost] = useState("");
const handleSubmit = (e: FormEvent) => {
e.preventDefault();
const bridgeId = addBridge({
name: "My PC",
config: {
host,
port: 9990, // Default port
secure: false, // Use wss:// instead of ws://
apiKey: undefined, // Optional API key
},
});
console.log("Added bridge:", bridgeId);
};
return (
<form onSubmit={handleSubmit}>
<input
value={host}
onChange={(e) => setHost(e.target.value)}
placeholder="192.168.1.100"
/>
<button type="submit">Add Bridge</button>
</form>
);
}
Using Hooks
Once a bridge is added, use hooks to access its data:
import { useBridge, useSystemStats } from "@cntrl-pw/sdk";
function BridgeStatus({ bridgeId }: { bridgeId: string }) {
const bridge = useBridge(bridgeId);
const { data, isLoading } = useSystemStats(bridgeId);
if (!bridge) return <div>Bridge not found</div>;
if (bridge.status === "connecting") return <div>Connecting...</div>;
if (bridge.status === "error") return <div>Connection failed</div>;
if (bridge.status === "disconnected") return <div>Disconnected</div>;
if (isLoading) return <div>Loading stats...</div>;
return (
<div>
<h2>{bridge.name}</h2>
<p>CPU: {data?.cpu.current_load.toFixed(1)}%</p>
<p>RAM: {data?.memory.percent.toFixed(1)}%</p>
</div>
);
}
TypeScript
The SDK is fully typed. Import types as needed:
import type {
BridgeConfig,
StoredBridge,
BridgeConnection,
ConnectionStatus,
SystemStats,
MediaState,
Process,
} from "@cntrl-pw/sdk";
Error Types
The onError callback receives an SdkError — a discriminated union you can narrow with the source field:
import type { SdkError, BridgeWsError, PersistenceOperationError } from "@cntrl-pw/sdk";
BridgeWsError
Emitted when the bridge server sends an error over WebSocket.
| Field | Type | Description |
|---|---|---|
source |
"bridge" | Discriminator |
message |
string |
Human-readable error message |
code |
`string | undefined` |
bridgeId |
string |
Which bridge sent the error |
PersistenceOperationError
Emitted when a custom storage operation fails (add, remove, update, or refresh). Not emitted for the initial load() — use persistenceStatus and persistenceError from useBridges() for that.
| Field | Type | Description |
|---|---|---|
source |
"persistence" | Discriminator |
message |
string |
Error message from the storage layer |
operation |
"add" | "remove" |
Example
<BridgesProvider
onError={(error) => {
switch (error.source) {
case "bridge":
toast.error(`[${error.bridgeId}] ${error.code}: ${error.message}`);
break;
case "persistence":
toast.error(`Storage ${error.operation} failed: ${error.message}`);
break;
}
}}
>
Next Steps
- Managing Bridges - Add, remove, update bridges
- Hooks Reference - All available hooks
Introduction React SDK for building Cntrl Bridge dashboards. Managing Bridges Add, remove, and manage Bridge connections.