Soundorv0.13.0

Connecting to a running plugin

Sign in to a DevTools session, select an instance, and handle timeouts, cancellation, reloads and errors.

connect() signs in to one DevTools session and returns a Driver. A session is the one soundor dev runs; it ends with dev.

The session

soundor dev writes a protected session descriptor and prints its path:

DevTools: http://localhost:4545
DevTools session file: /tmp/soundor-devtools-a1b2c3/0d6e….json

Connect with it, in one of these ways:

import { connect } from '@soundor/driver';

// The descriptor, by path.
const driver = await connect({
  descriptorPath: '/tmp/soundor-devtools-…/….json',
});

// The same, from $SOUNDOR_DEVTOOLS_SESSION (as the plugin itself gets it).
const fromEnvironment = await connect();

// The endpoint and the descriptor's browserKey, for a client that cannot
// read the file: the key defaults to $SOUNDOR_DEVTOOLS_BROWSER_KEY.
const explicit = await connect({
  endpoint: 'http://127.0.0.1:4545',
  browserKey: process.env.SOUNDOR_DEVTOOLS_BROWSER_KEY,
});

The descriptor is the convenient way: it is private to your user, and the Driver reads only its endpoint and browserKey. The endpoint must be http://127.0.0.1:<port> (or localhost): DevTools never listens anywhere else. Sessions are never discovered by scanning ports.

The Driver signs in as a client, like a DevTools browser tab: it exchanges the key for the session's cookie and opens the dashboard's WebSocket with the session's Origin. It never registers as a runtime.

Targets

A target is one running instance of a plugin. targets() lists them:

for (const target of await driver.targets())
  console.log(target.id, target.name, target.generation, target.capabilities);

The Driver works with one target. With one instance running, that is it. With several, select one when connecting, by its ID or by a unique plugin name; otherwise commands fail with AMBIGUOUS_TARGET:

const driver = await connect({ targetId: '3f0c…' });
const byName = await connect({ targetName: 'Orbit' });

An unknown selection fails at once with TARGET_NOT_FOUND. target() returns the selected target; waitForTarget({ timeout }) waits for an instance to connect when none runs yet.

Once selected, a target is for good. When it disconnects (the plugin quit, or the session restarted), every call fails with TARGET_DISCONNECTED: the Driver never moves to another instance, even one with the same name. Connect again to work with it.

A reload (soundor dev reloading the UI) keeps the target and starts a new generation. Node references of the old generation fail with STALE_NODE; a command the reload interrupted fails with GENERATION_CHANGED. Locators find their node again by themselves.

Other clients

A target has no owner. DevTools tabs and other Drivers read, highlight and send input at the same time as this one, as several mice would: each client has its own synthetic pointer and held keys, and nobody waits for anybody. When a client disconnects, the instance releases only that client's input.

Timeouts and cancellation

Each command waits 10 seconds by default; set timeout (milliseconds) on connect() for all of them, or on a call. A call also takes an AbortSignal:

const controller = new AbortController();
const tree = await driver.getTree({
  timeout: 2000,
  signal: controller.signal,
});

A timed-out or cancelled call fails with TIMEOUT or CANCELLED, and the server tells the instance to drop the command. connectTimeout and signal on connect() limit signing in.

disconnect() closes the connection; it is idempotent. Pending calls fail with DISCONNECTED, and the instance releases whatever this client held (a pressed button, keys).

Errors

Every failure is a DriverError with a stable code and a message that says what to do. Messages never contain keys.

import { isDriverError } from '@soundor/driver';

try {
  await driver.getByDebugId('reset').getNode();
} catch (error) {
  if (isDriverError(error, 'ELEMENT_NOT_FOUND')) console.log('Not rendered');
  else throw error;
}
CodeMeaning
INVALID_OPTIONSInvalid options or arguments, or input the instance refused (releasing a button that is not down).
INVALID_DESCRIPTORThe descriptor is missing (the session ended), unprotected or not one.
AUTHENTICATION_FAILEDThe server rejected the key or the connection.
SERVER_UNAVAILABLENo DevTools server answers at the endpoint.
DISCONNECTEDThe connection closed.
NO_TARGETSNo instance runs in the session.
TARGET_NOT_FOUNDNo instance has the selected ID or name.
AMBIGUOUS_TARGETSeveral instances run (or share the name); select one.
TARGET_DISCONNECTEDThe selected instance disconnected.
GENERATION_CHANGEDThe instance reloaded during the command.
STALE_NODEThe node reference is from an earlier generation, or the node went.
BUSYThe instance has too much outstanding work, or too many clients; retry.
MISSING_CAPABILITYThe instance does not support the command.
SCREENSHOT_UNAVAILABLEThe instance cannot capture its view now, or the capture was lost.
ELEMENT_NOT_FOUNDNo node matches the locator.
AMBIGUOUS_ELEMENTSeveral nodes match the locator.
ELEMENT_NOT_ACTIONABLEThe node cannot take the input: hidden, disabled, covered, outside the view, not focusable.
TIMEOUTThe command did not finish in time.
CANCELLEDThe command was cancelled.
PROTOCOLMismatched Soundor packages, or an invalid message.
INTERNALThe instance failed while handling the command.

On this page