DOCS / AUDIO & NATIVE
Native messaging
Beyond parameters, apps talk to the plugin through one call channel and one event channel. Payloads cross the bridge as JSON.
The JS side
import { native, useNativeEvent } from "@vsreact/core";
// synchronous request → C++ handler → JSON result
const version = native.call("app:version");
const ok = native.call("download:start", { url });
// subscribe for the component's lifetime — the handler stays fresh
// without resubscribing (no stale closures)
useNativeEvent("download:progress", (p) => setProgress(p.ratio));
// or manually: native.on returns an unsubscribe fn
useEffect(() => native.on("download:done", onDone), []);The C++ side
// handle calls (RootOptions::onNativeCall)
options.onNativeCall = [this] (const juce::String& name, const juce::var& args) -> juce::var
{
if (name == "app:version")
return juce::var (ProjectInfo::versionString);
if (name == "download:start")
{
startDownload (args["url"].toString()); // kick off async work
return juce::var (true);
}
return {};
};
// push events — from the message thread
juce::MessageManager::callAsync ([this, ratio]
{
auto* obj = new juce::DynamicObject();
obj->setProperty ("ratio", ratio);
root->sendNativeEvent ("download:progress", juce::var (obj));
});Patterns
- Calls are synchronous from the JS side — keep handlers fast. For long work, start it in the handler, return immediately, and report back with events.
- Namespace your messages (
download:*,history:*) —param:*is reserved by the ParameterBridge. - Marshal to the message thread before calling
sendNativeEventfrom workers or the audio thread. - Debounce chatty inputs with
useDebouncedbefore they become native calls:const q = useDebounced(text, 250)then call in an effect keyed onq.
StashTrack drives its whole download pipeline this way: a
download:start call, then download:progress / download:done events rendering a live progress arc.