On a TV there is no keyboard and no screenshot key, so Everframe does not put a reporter on the TV at all. The app runs in companion mode: it shows a pairing code, and the reporter lives on a phone in your hand or in the dashboard on your desk. The TV still captures everything — screenshot, breadcrumbs, console, network, device context — it just does not ask anyone to type.
If your TV app is React, use
@everframe/react. That package ships the pairing card, the attach-PIN card, the name badge and auseCompanion()hook. The vanilla mount below deliberately ships none of them, and expects you to render the pairing UI yourself. Every TV platform we support in production — Tizen, webOS, Vega OS — is a React app.
Starting a session
import { companion } from '@everframe/web';
companion.start({ sdkKey: 'evf_live_…', deviceLabel: 'Lobby TV' });
// …later
companion.stop();
Unlike the React package, nothing is defaulted for you here — pass sdkKey
explicitly if you want this device listed in the dashboard’s Companion tab.
start() is idempotent while a session is live; call stop() first to restart
with different options.
What the vanilla mount does not render
The vanilla mount renders the FAB, the reporter dialog, the inbox and a toast — and nothing else. Three companion surfaces therefore do not exist here:
| Surface | On @everframe/web |
|---|---|
| Pairing QR / code | Not rendered. You draw it. |
| Attach-PIN challenge | Not rendered. attachPinUi: 'builtin' resolves to 'off'. |
| Name badge | Not rendered. companionBadge has no effect. |
The PIN behaviour is the one to understand. A dashboard member who tries to
attach to this device is shown a four-digit PIN that someone in the room must
read off the screen — and if nothing renders it, the request strands until the
challenge expires. So the SDK fails closed here: rather than claim a capability
it does not have, it resolves the default to 'off', and attach-by-dashboard
simply does not work.
To support it, render the challenge yourself and say so:
companion.start({ sdkKey: 'evf_live_…', attachPinUi: 'custom' });
Only set 'custom' if you really will render it. Setting it and rendering
nothing is the same stranded request, minus the honest default.
Reading session state
The singleton exposes start and stop. It does not expose a public
subscription for state, pairUrl or code — a host that needs to draw its
own pairing screen builds the companion with the factory API
(createCompanion, createRelayWSClient) and owns the wiring, or uses
@everframe/react.
Session states
state | Meaning |
|---|---|
unpaired | No phone attached. Show the pairing code. |
paired | A phone or dashboard user is attached. |
report_in_progress | A report is being captured and submitted. |
phone_disconnected | The far end dropped. The session is still live and can be re-paired. |
Device identity
Each device gets a stable id so it stays recognisable across restarts. The SDK derives one — Tizen DUID, then webOS LGUDID, then a persisted UUID — and hashes it on-device before it leaves. A host with its own identity passes it instead:
companion.start({ sdkKey: 'evf_live_…', companionDeviceId: () => mdm.getSerial() });
It is resolved once per page load, so a later stop()/start() with a
different id does not re-resolve — only a reload does.
Announcing is optional
The sdkKey is what makes this device appear in the dashboard’s Companion list.
Omit it and the device connects to the relay exactly as it did before that
feature existed: QR pairing and reporting work identically, there is just no
dashboard presence. Announce can never break reporting — no key means “not
listed”, never “no socket”.