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 the Tizen, webOS and Vega OS apps report.
Starting a session
import { companion, useCompanion } from '@everframe/react';
useEffect(() => {
companion.start();
return () => companion.stop();
}, []);
Inside a mounted <EverframeProvider>, start() needs no arguments — it takes
the SDK key and device label from the provider’s own config. Pass them
explicitly only to override:
companion.start({ sdkKey: 'evf_live_…', deviceLabel: 'Lobby TV' });
Explicit options win per field, so you can override one without discarding the other.
start() is idempotent while a session is live — call stop() first to restart
with different options.
Showing the code
const { state, pairUrl, code, running, attachedUserName } = 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. null if you started without an sdkKey. |
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 — see Attach from the dashboard. |
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.
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 relay pushes a challenge and 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 SDK renders that PIN for you. To render it yourself:
companion.start({ attachPinUi: 'custom' });
const { attachChallenge } = useCompanion();
// attachChallenge: { code, requestedByName, ttlMs } | null
attachPinUi accepts 'builtin' (the default), 'custom', or 'off'. If you
cannot commit to rendering the challenge, use 'off', not 'custom' —
'custom' suppresses the built-in UI, so a host that sets it and then renders
nothing leaves the PIN invisible and attaches impossible.
The mode is fixed by the first start() of a page load.
The name badge
While a session is attached, the SDK shows a small on-screen badge naming who is watching. It is on by default — zero-setup recognition is the point.
companion.start({ companionBadge: { enabled: false } });
companion.start({ companionBadge: { position: 'top-left' } });
position is one of 'bottom-right' (default), 'bottom-left', 'top-right'
or 'top-left'.
This badge is identification, not a privacy control. It tells a person in the room who is attached; it is not a capture indicator and must not be described as one.
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 (an MDM id, a provisioning serial) passes it instead:
companion.start({ 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”.