# Getting Started

Install and configure the SDK in your React app.

## [Installation](/content/docs/sdk/getting-started#installation/index.html)

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](/content/docs/sdk/getting-started#setup-providers/index.html)

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](/content/docs/sdk/getting-started#provider-options/index.html)

```
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](/content/docs/sdk/custom-persistence/index.html).

## [Adding Your First Bridge](/content/docs/sdk/getting-started#adding-your-first-bridge/index.html)

```
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](/content/docs/sdk/getting-started#using-hooks/index.html)

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](/content/docs/sdk/getting-started#typescript/index.html)

The SDK is fully typed. Import types as needed:

```
import type {
  BridgeConfig,
  StoredBridge,
  BridgeConnection,
  ConnectionStatus,
  SystemStats,
  MediaState,
  Process,
} from "@cntrl-pw/sdk";
```

## [Error Types](/content/docs/sdk/getting-started#error-types/index.html)

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`](/content/docs/sdk/getting-started#bridgewserror/index.html)

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` | Error code (e.g., "PARSE_ERROR") |
| `bridgeId` | `string` | Which bridge sent the error |

### [`PersistenceOperationError`](/content/docs/sdk/getting-started#persistenceoperationerror/index.html)

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" | "update" | "refresh" | Which operation failed |

### [Example](/content/docs/sdk/getting-started#example/index.html)

```
<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](/content/docs/sdk/getting-started#next-steps/index.html)

- [Managing Bridges](/content/docs/sdk/bridges/index.html) - Add, remove, update bridges
- [Hooks Reference](/content/docs/sdk/hooks/index.html) - All available hooks

[Introduction  React SDK for building Cntrl Bridge dashboards.](/content/docs/sdk/index.html) [Managing Bridges  Add, remove, and manage Bridge connections.](/content/docs/sdk/bridges/index.html)
