> ## Documentation Index
> Fetch the complete documentation index at: https://tbd-6fc993ce-hypeship-docs-live-view-connection-failed.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Live View

Humans-in-the-loop can access the live view of Kernel browsers in real-time to resolve errors or take unscripted actions.

To access the live view, visit the `browser_live_view_url` provided when you create a Kernel browser:

<CodeGroup>
  ```typescript Typescript/Javascript theme={null}
  import Kernel from '@onkernel/sdk';

  const kernel = new Kernel();

  const browser = await kernel.browsers.create();
  console.log(browser.browser_live_view_url);
  ```

  ```python Python theme={null}
  from kernel import Kernel

  kernel = Kernel()

  browser = kernel.browsers.create()
  print(browser.browser_live_view_url)
  ```

  ```go Go theme={null}
  package main

  import (
  	"context"
  	"fmt"

  	"github.com/kernel/kernel-go-sdk"
  )

  func main() {
  	ctx := context.Background()
  	client := kernel.NewClient()

  	browser, err := client.Browsers.New(ctx, kernel.BrowserNewParams{})
  	if err != nil {
  		panic(err)
  	}

  	fmt.Println(browser.BrowserLiveViewURL)
  }
  ```
</CodeGroup>

## Query parameters

The `browser_live_view_url` supports additional query parameters to customize the live view:

* `readOnly` (bool): when set to `true`, the view will be non-interactive.

Example:

```
https://api.onkernel.com/browser/live/<TOKEN>?readOnly=true
```

## Embedding in an iframe

The live view URL can be embedded in an iframe to integrate the browser view into your own application or dashboard.

If your environment restricts outbound traffic, allow the [Live View domains and ports](/info/network-access#required-destinations) before you embed it.

```html theme={null}
<iframe src={browser.browser_live_view_url}></iframe>
```

<Info>
  Embedded third-party iframes like live view must have focus to receive keyboard events. On Safari, focus requires a user-initiated event — calling `.focus()` on the iframe element within a user-initiated event handler is recommended.

  To enable clipboard sharing, add `allow="autoplay; clipboard-read; clipboard-write"` to the iframe element.

  If your application uses a **Content Security Policy (CSP)**, you must add the following directives to allow the live view iframe and its WebSocket connection. See [Network access](/info/network-access#content-security-policy) for the complete firewall and CSP requirements.

  ```
  frame-src https://*.onkernel.com:8443
            https://*.kernel.sh:8443;
  connect-src https://*.onkernel.com:8443
              wss://*.onkernel.com:8443
              https://*.kernel.sh:8443
              wss://*.kernel.sh:8443;
  ```
</Info>

## Parent frame events

When the live view is embedded in an iframe, the client posts messages to the parent window as the connection and playback state change, and accepts one message back. Use them to tell a working viewer apart from one that never starts.

### Sent to the parent

| Event                       | Payload                                                                           | Meaning                                                                                           |
| --------------------------- | --------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- |
| `KERNEL_CONNECTED`          | `connected`, `capabilities` (recent images only)                                  | Signaling is established. Frames are not necessarily rendering yet.                               |
| `KERNEL_PLAYING`            | `playing: true`                                                                   | Video frames are rendering.                                                                       |
| `KERNEL_PAUSED`             | `playing: false`                                                                  | Playback has stopped.                                                                             |
| `KERNEL_CONNECTION_TIMEOUT` | `reason`, `iceConnectionState`, `connectionState`, `signalingState`, `socketOpen` | A connect stage ran out its bound without completing. `reason` names the stage.                   |
| `KERNEL_CONNECTION_FAILED`  | `reason`, `iceConnectionState`, `connectionState`, `signalingState`, `socketOpen` | The connect failed outright. `reason` says why. Nothing further happens until the iframe reloads. |
| `KERNEL_READ_ONLY_CHANGED`  | `readOnly`, `requestId`                                                           | Acknowledges a `KERNEL_SET_READ_ONLY` request.                                                    |

Both terminal events carry the same `reason`, so branch on it instead of matching message text:

| `reason`      | Meaning                                                              |
| ------------- | -------------------------------------------------------------------- |
| `transport`   | the socket never opened inside its bound, or closed before signaling |
| `signaling`   | the socket opened but no offer arrived inside its bound              |
| `media`       | a peer exists but ICE never reached `checking` inside its bound      |
| `peer`        | peer construction or the remote offer threw                          |
| `unsupported` | the browser has no `RTCPeerConnection`                               |
| `server`      | the server closed the connect                                        |

### Accepted from the parent

| Event                  | Payload                                              | Effect                                                                                            |
| ---------------------- | ---------------------------------------------------- | ------------------------------------------------------------------------------------------------- |
| `KERNEL_SET_READ_ONLY` | `readOnly` (boolean), `requestId` (string, optional) | Toggles interactivity without reloading the iframe. Acknowledged with `KERNEL_READ_ONLY_CHANGED`. |

<Info>
  `KERNEL_CONNECTION_TIMEOUT`, `KERNEL_READ_ONLY_CHANGED` and `KERNEL_SET_READ_ONLY` require a recent browser image. `KERNEL_CONNECTED`, `KERNEL_PLAYING` and `KERNEL_PAUSED` are available on all current images, but `capabilities` is sent only by recent ones — older images post `{ type: 'KERNEL_CONNECTED', connected: true }`, so treat a missing `capabilities` as unknown rather than unsupported.

  `KERNEL_CONNECTION_FAILED` requires a browser image that includes the connect-failure fix. On images without it the client fails silently instead, so treat a missing `KERNEL_CONNECTION_FAILED` as unknown rather than as a successful start, and keep gating on `KERNEL_PLAYING`. On images before that fix, `KERNEL_CONNECTION_TIMEOUT` carries `reason: 'connection timeout'` rather than a stage name.

  Messages are exchanged with the parent origin derived from `document.referrer`. If the referrer is unavailable — for example under a restrictive `Referrer-Policy` — the client cannot resolve your origin and will reject `KERNEL_SET_READ_ONLY`.
</Info>

### Detecting a viewer that never starts

Gate on `KERNEL_PLAYING`. It fires only once frames actually arrive, so it is the signal that distinguishes a working viewer from one that is still connecting or has silently failed. The terminal events below tell you when the client has stopped trying. Start a timer when you mount the iframe and remount if `KERNEL_PLAYING` has not arrived:

```typescript Typescript/Javascript theme={null}
const iframe = document.querySelector('#kernel-live-view');
const src = iframe.src;
const liveViewOrigin = new URL(src).origin;

let attempts = 0;
let watchdog;

function arm() {
  clearTimeout(watchdog);
  watchdog = setTimeout(() => {
    if (attempts++ >= 2) return showFallback();
    iframe.src = 'about:blank';
    iframe.src = src;
    arm();
  }, 15000);
}

window.addEventListener('message', (event) => {
  if (event.origin !== liveViewOrigin) return;
  if (event.data?.type === 'KERNEL_PLAYING') clearTimeout(watchdog);
  if (event.data?.type === 'KERNEL_PAUSED') arm();
  if (event.data?.type === 'KERNEL_CONNECTION_FAILED') showFallback(event.data);
  if (event.data?.type === 'KERNEL_CONNECTION_TIMEOUT') showFallback(event.data);
});

arm();
```

Do not gate on `KERNEL_CONNECTION_TIMEOUT` alone. The watchdog behind it is cleared once negotiation begins, so a connection that stalls after that point never emits it. Log it alongside `KERNEL_PLAYING` to capture the connection state at the moment things stalled.

`KERNEL_CONNECTION_FAILED` and `KERNEL_CONNECTION_TIMEOUT` are both terminal, and a failed connect posts one of them, never both. Whichever arrives, nothing further happens until the iframe reloads. Surface a persistent message rather than remounting blindly: a `peer` or `unsupported` reason is a property of the browser environment, so a remount repeats it, while a `transport` reason is worth one retry.

The client does not retry a connect itself, so whatever `reason` you get is the first failure, and whether to try again is your call.

## Kiosk mode

Kiosk mode provides a fullscreen live view experience without browser UI elements like the address bar and tabs. You can enable kiosk mode when creating a browser by setting the `kiosk_mode` parameter to `true`.

<Info>
  Kiosk mode triggers a Chromium restart, which can take several seconds. Use [browser pools](/browsers/pools) to access kiosk mode browsers faster.
</Info>

<CodeGroup>
  ```typescript Typescript/Javascript theme={null}
  const browser = await kernel.browsers.create({
      kiosk_mode: true
  });
  ```

  ```python Python theme={null}
  kernel_browser = kernel.browsers.create(
      kiosk_mode=True
  )
  ```

  ```go Go theme={null}
  browser, err := client.Browsers.New(ctx, kernel.BrowserNewParams{
  	KioskMode: kernel.Bool(true),
  })
  if err != nil {
  	panic(err)
  }
  _ = browser
  ```
</CodeGroup>

## URL lifetime

`browser_live_view_url` becomes invalid once the browser is [deleted](/browsers/termination) manually or via timeout.
