VSTDOCSv0.0.17

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

TSX
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

C++
// 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 sendNativeEvent from workers or the audio thread.
  • Debounce chatty inputs with useDebounced before they become native calls: const q = useDebounced(text, 250) then call in an effect keyed on q.
StashTrack drives its whole download pipeline this way: a download:start call, then download:progress / download:done events rendering a live progress arc.