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 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} />;
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. null if you started without an sdkKey.
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 — 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

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 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”.