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.
This is how Apple TV, Android TV and Fire TV apps report.
Starting a session
import { companion, useCompanion } from '@everframe/react-native';
useEffect(() => {
companion.start();
return () => companion.stop();
}, []);
start() takes no arguments. Everything it needs — the SDK key, the device
label, the attach-PIN mode, the badge settings — comes from the native config
you already passed when the SDK was configured. The ingest URL is not a
JavaScript concern either; the native SDKs bake it at build time.
Showing the code
const { state, pairUrl, code, running } = useCompanion();
if (!running) return null;
if (state === 'unpaired') return <QR url={pairUrl} caption={code} />;
| Field | What it is |
|---|---|
state | What the relay is doing — see the table below. |
pairUrl | The URL to render as a QR code. null until the socket is up. |
code | Short display code, shown beside the QR so a teammate can find this device in the dashboard’s Companion list. |
running | Whether you asked for a session — true between start() and stop(). |
attachedUserName | The dashboard user’s display name once someone attaches. |
resolvedName | The server-resolved name for this device. |
attachChallenge | A live attach-PIN request — { code, requestedByName, ttlMs }. |
running is not state. state reports what the relay is doing; running
reports what you asked for. A stopped session and an unpaired one are both
'unpaired', so rendering your pairing UI off state alone puts a QR code on
screen after you called stop(). Use running to decide whether to show
anything at all, and state to decide what.
resolvedName and attachChallenge are seeded from a cache, so a value that
arrived before your component mounted is visible on the first render rather
than one effect later. A challenge seeded this way reports the time actually
remaining, not the full TTL it started with.
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. |
Attach from the dashboard
A teammate can attach from the dashboard’s Companion tab instead of scanning. When they do, the TV shows a four-digit PIN that the teammate types to confirm — so attaching to a screen always requires someone who can see it.
The native SDK renders that PIN for you by default. To render it yourself, set
the attach-PIN mode to custom in your native config and drive it from
useCompanion().attachChallenge. If you cannot commit to rendering it, set
off rather than custom — custom suppresses the built-in UI, so a host that
sets it and renders nothing leaves every attach request stranded until the
challenge expires.
Subscribing outside React
Every field also has a plain subscription, for code that is not a component:
const unsubscribe = companion.onState((state) => { … });
onState, onPairUrl, onCode, onAttachedUserName, onResolvedName,
onAttachChallenge and onRunning all return their own unsubscribe function.