DOCS / AUDIO & NATIVE
PostHog analytics
Know what your users actually do with your plugin — which knobs they touch, which panels they open, which presets they load. @vsreact/posthog captures from the React side and the native PostHogBridge delivers over HTTPS (QuickJS has no network; C++ owns delivery, off the message thread).
Install
bun add @vsreact/posthogC++ wiring
Construct a PostHogBridge with your project API key and chain it in onNativeCall, exactly like the ParameterBridge. The key lives in C++ — the JS bundle never sees it.
vsreact::PostHogBridge::Options analytics;
analytics.apiKey = "phc_..."; // your project key
analytics.host = "https://eu.i.posthog.com"; // or us / self-hosted
analytics.stateFile = appDataDir.getChildFile ("ph-id"); // persistent anonymous id
posthog = std::make_unique<vsreact::PostHogBridge> (analytics);
options.onNativeCall = [this] (const juce::String& name, const juce::var& args) -> juce::var
{
if (auto handled = posthog->handleNativeCall (name, args)) return *handled;
if (auto handled = bridge.handleNativeCall (name, args)) return *handled;
return {};
};- Batches post to
{host}/batch/on a background thread — never the audio or message thread. stateFilepersists the anonymous distinct id across sessions; leave it empty for a fresh id per instance.
Capture from React
import { render, GenericEditor } from "@vsreact/core";
import { posthog, usePostHogParameters, useCaptureOnMount } from "@vsreact/posthog";
posthog.init({ defaultProperties: { plugin_version: "1.2.0" } });
function App() {
usePostHogParameters(); // every knob tweak, debounced per parameter
useCaptureOnMount("plugin_opened");
return <GenericEditor />;
}
render(<App />);usePostHogParameters() is the flagship: it subscribes to every host parameter change and captures one parameter_changed event per parameter after the user settles (debounced, default 800ms) — usage analytics for your whole control surface in one line.
The client API
posthog.capture("preset_loaded", { preset: "Warm Tape" });
posthog.register({ daw: hostName }); // stamped on every event
posthog.registerOnce({ first_seen: today }); // only where unset
posthog.identify("user-123", { plan: "pro" }); // tie to a known user
posthog.alias("licence-XYZ"); // link another id
posthog.set({ favourite_mode: "TUBE" }); // person properties
posthog.setOnce({ first_version: "1.2.0" }); // only if unset
posthog.group("studio", "abbey-road"); // group analytics
posthog.time("preset_load"); // start a stopwatch…
posthog.timeEnd("preset_load"); // …capture { duration_ms }
posthog.debug(true); // log captures (dev builds)
posthog.flush(); // force-send now
posthog.reset(); // fresh anonymous identityposthog.init({
beforeSend: (event) => {
if (event.event === "internal_debug") return null; // veto
delete event.properties.project_path; // scrub
return event;
},
});- Events queue in JS and flush at 10 events or 10 seconds (tune with
init({flushAt, flushIntervalMs})); the queue is capped atmaxQueueSize(drop-oldest, default 1000). - Every event carries
distinct_id,$session_id, and lib metadata automatically. init({sampleRate})keeps a fraction of sessions and stamps$sample_rateon kept events so PostHog can weight counts.
Error tracking & sessions
import { PostHogErrorBoundary, useEditorSession } from "@vsreact/posthog";
function App() {
useEditorSession(); // editor_session_start / _end { duration_ms }
return <MainPanel />;
}
render(
<PostHogErrorBoundary fallback={<Text>Something broke — reopen the window.</Text>}>
<App />
</PostHogErrorBoundary>,
);posthog.captureException(error, props?)— a properly-shaped$exceptionevent for PostHog error tracking; the boundary calls it for every render crash and flushes immediately.useCaptureOnUnmount(event)— the closing bookend touseCaptureOnMount, stamped withduration_ms.useScreen(name)/posthog.screen(name)— panel views as$screenevents for PostHog screen analytics;posthog.shutdown()flushes and goes silent at editor teardown;init({propertyDenylist})strips sensitive keys mechanically.posthog.optOut()/optIn()/optedOut— the consent switch: opting out drops new events and discards the unsent queue;init({optOut})starts disabled.unregister(key)andgetSessionId()round out the client.
<ParamToggle> wired to a "share usage data" flag that drives posthog.optOut() / optIn() is the pattern.Full messaging model in Native messaging; the bridge type in the C++ API.