Skip to content
Everframe Docs
Documentation

Companion mode

For screens with no keyboard. The TV captures; a phone or the dashboard writes the report.

Updated

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} />;
FieldWhat it is
stateWhat the relay is doing — see the table below.
pairUrlThe URL to render as a QR code. null until the socket is up.
codeShort display code, shown beside the QR so a teammate can find this device in the dashboard’s Companion list.
runningWhether you asked for a session — true between start() and stop().
attachedUserNameThe dashboard user’s display name once someone attaches.
resolvedNameThe server-resolved name for this device.
attachChallengeA 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

stateMeaning
unpairedNo phone attached. Show the pairing code.
pairedA phone or dashboard user is attached.
report_in_progressA report is being captured and submitted.
phone_disconnectedThe 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.