Skip to content

probe-web / @probe-web/client

@probe-web/client

The headless probe-rs SDK for browsers (and Node). List probes, attach to a chip, flash firmware, stream RTT, defmt and semihosting output, run embedded-test suites and debug — with one API over two transports.

sh
npm install @probe-web/client

Transports

webusbwebsocket
Where probe-rs runsIn this tab: probe-rs compiled to wasm, in a Web WorkerA native probe-rs serve, on this machine or another
BrowsersChromium onlyAny browser, and Node
InstallNothingprobe-rs on the host
DisassemblyNo (probe-rs's disassembler is C)Yes
ts
import { Client } from '@probe-web/client';

// probe-rs running in this tab, over WebUSB …
const client = await Client.connect({ kind: 'webusb' });
// … or a native `probe-rs serve`
const remote = await Client.connect({ kind: 'websocket', url: 'ws://127.0.0.1:3000', token: 'secret' });

On WebUSB the probe must be granted to the page on the main thread first (for example with requestProbe from @probe-web/devices); the worker only sees granted probes. The worker option of the webusb transport replaces the default worker: pass createLocalWorker({ log: 'debug' }) to raise probe-rs's log level, or the fake-probe worker to run without hardware (see below).

More: Transports.

Flash and monitor

ts
import { Client, elfHasRtt } from '@probe-web/client';

const [probe] = await client.listProbes();
const session = await client.attach({ probe, chip: 'MCXA153', protocol: 'Swd' });

const bootInfo = await session.flash({ image: elf, name: 'app.elf', format: 'elf' }, (e) => console.log(e));

if (elfHasRtt(elf)) {
  await session.createRttClient({ elf, defaults: { dataFormat: 'Defmt' } });
  session.setDefmtElf(elf);
}
const exit = await session.monitor(bootInfo, (ev) => {
  if (ev.kind === 'defmt') ev.lines.forEach((l) => console.log(l.level, l.message));
  if (ev.kind === 'semihosting') console.log(ev.data);
});

openSession({ transport, probe, chip }) does the connect, pick-a-probe and attach steps in one call.

More: Getting started, Flashing, RTT and defmt, Testing.

elfSymbol and elfHasRtt are also available from @probe-web/client/elf, which is plain JavaScript and loads no wasm. Import them from there when a symbol lookup is all you need — the main entry pulls in the wasm module and, with it, the probe-rs worker.

Debug

session.debugger() returns a Debugger for one core: run control, breakpoints, stepping, stack traces, variables, registers and memory. It works over both transports (disassembly needs probe-rs serve).

ts
const dbg = session.debugger();
dbg.addEventListener('stopped', async () => {
  const [frame] = await dbg.stackTrace();
  console.log(`stopped in ${frame.functionName} at ${frame.source?.path}:${frame.source?.line}`);
});
dbg.start(); // poll the core, so `stopped` fires on its own

await dbg.loadDebugInfo(elf, 'app.elf');
await dbg.setSourceBreakpoints('src/main.rs', [{ line: 42 }]);
await dbg.continue();

DirectorySourceProvider and UrlSourceProvider map the build-time paths in the debug info to source text in the browser.

More: Debugging.

Subpath exports

ImportWhat
@probe-web/clientClient, Session, Core, Debugger, openSession, source providers, config import.
@probe-web/client/wireThe probe-rs RPC types (Wire.*), generated from probe-rs's schema by tools/wire-gen. Values cross the wasm boundary as described at the top of src/wire.ts: externally tagged enums, bigint for 64-bit integers, null for None.
@probe-web/client/targetsReading CMSIS packs and .FLM flash algorithms into target YAML for client.loadChipFamily. It has its own wasm module, so import it dynamically.
@probe-web/client/testingFakeDebugger, a scripted Debugger for UI and adapter tests.
@probe-web/client/testing/workercreateFakeLocalWorker(), a worker with a fake probe and a mocked core, for driving the WebUSB transport without hardware. It pulls in a second wasm module, so keep it out of production bundles.
ts
const { createFakeLocalWorker } = await import('@probe-web/client/testing/worker');
const client = await Client.connect({ kind: 'webusb', worker: createFakeLocalWorker() });

Modules