For developers of an app that launches Chrome with this extension loaded — Testeiya, for one — and wants the panel signed in without the tester pasting a token. Testers have nothing to do here; the panel just arrives connected.
The host writes handoff.json into the extension folder (next to
manifest.json) and opens the panel:
{
"app": "Testeiya",
"baseUrl": "https://app.testomat.io",
"projectId": "my-project",
"jwt": "eyJ…",
"projectToken": "tstmt_…",
"runUrl": "https://app.testomat.io/projects/my-project/runs/abcd1234",
"at": 1756160000000
}
baseUrl, projectId and jwt are required; a file missing any of them is
ignored, as is one whose baseUrl is not https:// — the same refusal Save
gives a typed address, since the credentials below travel over it. Two credentials, because the panel talks to two APIs: projectToken
is what /api/v2 takes, jwt is a web session for the routes v2 lacks.
runUrl is optional; with one, the panel opens that run. at is
milliseconds since the epoch and has to grow on every push. app is shown
to the tester as the name of whoever signed them in.
A file rather than a command line: --load-extension argv is readable by
every process on the machine, and these are credentials.
jwt and the projectToken are both held in memory and re-read from the
file, exactly like the session POST /api/login returns. PRIVACY.md says
project keys are memory-only, and a handed-over one is no exception.at as answered instead of deleting a file it
does not own. Push a newer at to offer the connection again.at, so reloading the panel keeps the tester’s
place.An offer is an overlay on the ordinary sign-in, never a replacement. A tester who had already connected that instance keeps their own credential and their preferences; the two live side by side and each request uses whichever fits:
| Request | Credential |
|---|---|
Web JSON:API (/api/…) |
the handed jwt, else the tester’s own session |
/api/v2 on the handed project |
projectToken when one was sent |
/api/v2 anywhere else |
that project’s key, read with whichever session is live |
projectToken is therefore optional — a host that already holds one saves
the panel a round trip, and nothing more. jwt is what the offer is really
made of.
Because a session reaches every project, the switcher stays open. It closes in one case: the host has closed its browser, taking the file — and with it both credentials — and the tester never signed in themselves. Nothing of the handoff is left to work with, and Settings says so.
Signing in over a handed-off connection replaces it outright, session included.
A panel that is already open takes a new push through
window.TestomatHandoff.apply(), which answers {ok, projectId, run} —
run says whether the run the file names is now on screen — or
{ok: false, reason: "no-offer"} when there is no file, it is incomplete,
or its at was already declined. A build without that global predates this
contract and needs updating.
With no host involved the panel logs one ERR_FILE_NOT_FOUND for
handoff.json at boot. That is the check for the file, not a fault.