TypeScript SDK
Write a satellite in TypeScript with @nestor-home/satellite, in a browser tab or in Node.
@nestor-home/satellite handles pairing,
the token, the manifest, the WebRTC link and audio, in browsers and in Node 22 or later. It does not
depend on the Nestor server.
pnpm add @nestor-home/satellite
pnpm add @roamhq/wrtc # in Node only: WebRTC (libwebrtc, prebuilt for Linux, macOS and Windows x64)
A minimal driver
A tab on a wall tablet. Served over https: or localhost, from the server’s origin or through a
proxy:
import {
AUDIO_CAPTURE,
AUDIO_PLAYBACK,
browserWebRtc,
IDENTIFY,
LocalStorageTokenStore,
Manifest,
Satellite,
} from "@nestor-home/satellite";
const satellite = new Satellite({
core: location.origin,
manifest: Manifest.of(
{ id: "tablette-cuisine", model: "browser", room: "cuisine" },
AUDIO_CAPTURE,
AUDIO_PLAYBACK,
IDENTIFY,
),
tokens: new LocalStorageTokenStore(),
webrtc: browserWebRtc(),
pairing: {
// the device code to type in Nestor; `code` is null once it is no longer needed
display: (code, text) => {
document.querySelector("#code")!.textContent = code ?? "";
document.querySelector("#status")!.textContent = text;
},
},
body: {
behavior: (behavior) => (document.body.dataset.behavior = behavior),
identify: () => document.body.animate([{ opacity: 0.2 }, { opacity: 1 }], { duration: 500, iterations: 6 }),
},
});
button.onclick = () => satellite.run(); // a click: browsers only play sound after a gesture
In Node, headless
No screen: the device code is said on the speaker with the SDK’s recorded voice (offline, English or French) and logged, unless the pairing URI is in the environment.
import { AUDIO_CAPTURE, AUDIO_PLAYBACK, EXPRESSION, Manifest, Satellite } from "@nestor-home/satellite";
import {
CommandSink,
CommandSource,
FileTokenStore,
languagesFromEnv,
nodeWebRtc,
provisioningFromEnv,
} from "@nestor-home/satellite/node";
const provisioning = provisioningFromEnv(); // NESTOR_PAIRING_URI or NESTOR_PAIRING_CODE, if set
const satellite = new Satellite({
core: provisioning?.core ?? "http://nestor.local:7860",
manifest: Manifest.of(
{ id: "enceinte-salon", model: "raspberry-pi-4", room: "salon" },
AUDIO_CAPTURE,
AUDIO_PLAYBACK,
EXPRESSION,
),
tokens: new FileTokenStore("~/.config/nestor-satellite/enceinte-salon.json"), // mode 600
webrtc: nodeWebRtc({
source: new CommandSource("arecord -q -t raw -f S16_LE -c 1 -r 48000"),
sink: new CommandSink("aplay -q -t raw -f S16_LE -c 1 -r 48000"),
}),
pairing: { enrollment: provisioning?.enrollment },
locale: languagesFromEnv(), // LC_ALL, LC_MESSAGES, LANG
body: { expression: (expression) => console.log("expression:", expression) },
});
const stop = new AbortController();
process.once("SIGINT", () => stop.abort());
await satellite.run(stop.signal); // pairs the first time, then reconnects forever
Pairing options
The manifest’s pairing methods follow from the pairing options; you never list them by hand.
| Option | Declares |
|---|---|
| always | enrollment.provisioning |
display: (code, text) => … |
device_code.display |
speak: (sentence) => …, or a speaker (audio.playback) without display |
device_code.speech |
voice: true |
says the code with the bundled voice even with a screen |
accepts: ["enrollment.input"] |
enrollment.input |
scanner: cameraQrScanner({ wasmUrl }) (browsers) |
enrollment.qr |
enrollmenttakes a pairing URI or an enrollment code.- A code typed while the device waits:
await satellite.enroll(input.value). A refused code is never sent again, even after a restart: its SHA-256 is kept in the token store, never the code. - QR codes:
cameraQrScannercomes from@nestor-home/satellite/qrand needszxing-wasm(about 1 MB of WebAssembly, fetched from jsDelivr unlesswasmUrlsays where your page serves it). Node has no camera, so a Node satellite never declaresenrollment.qr. localesets the language it speaks to people (default: the platform’s).
The command
In Node, this machine’s microphone and speaker as a satellite (rec and play from SoX by
default):
NESTOR_PAIRING_URI='nestor+http://nestor.local:7860/?code=K7QF2M9X' npx nestor-satellite run --room salon
# or
npx nestor-satellite run --room salon --pairing-uri 'nestor+http://nestor.local:7860/?code=K7QF2M9X'
Run it from a project where both packages are installed. With a bare code, give the core with
--core (--pairing-code K7QF-2M9X). Without either, it says a device code to type in Nestor
(--output none: it only logs it). --input and --output take any command that records or plays
raw 48 kHz 16-bit mono PCM, or none. The token is kept in ~/.config/nestor-satellite/<id>.json
(mode 600). npx nestor-satellite --help lists the rest.
Going further
- The source, a browser example and the voice clips’ licence (CC BY 4.0): nestor-satellite-typescript.
- Check yours: Conformance.