@browsonic/vue
v1.5.4
Published
Vue 3 adapter for @browsonic/sdk — error boundary, composables, app plugin with errorHandler chaining. Apache-2.0.
Maintainers
Readme
@browsonic/vue
Vue 3 adapter for @browsonic/sdk — error boundary component, composables, an app.use() plugin that chains into app.config.errorHandler, Vue Router instrumentation (breadcrumbs + Atlas route-template page views), and a Pinia integration.
Status: 1.5.x surface — check npm or this package's
package.jsonfor the current version. Boundary, four composables (useBrowsonic/useUser/useCaptureError/useBreadcrumb), plugin install, Vue Router 4 instrumentation — navigation breadcrumbs (with optionalbeforeEachintent phase) plus Atlas route-template page views (routeTemplateFromLocation) and the screen-name channel (routeNameFromLocation), on by default under the SDK ≥ 3.14atlas: trueumbrella — and a Pinia plugin that stamps store action errors with context. Server-side rendering capture is intentionally out of scope (browser-runtime adapter only).
Why this adapter
Vue's reconciler captures render-time and lifecycle exceptions in descendant components and routes them through errorCaptured / app.config.errorHandler. The plain @browsonic/sdk install therefore never hears about them — only the global error / unhandledrejection events. This package closes that gap.
Install
npm install @browsonic/sdk @browsonic/vue@browsonic/sdk is a peer dependency. vue (3.3+) is a peer dependency.
Quickstart
import { createApp } from 'vue';
import { getBrowsonic } from '@browsonic/sdk';
import { browsonicPlugin, BrowsonicErrorBoundary } from '@browsonic/vue';
import App from './App.vue';
const sdk = getBrowsonic();
sdk.init({
apiEndpoint: 'https://your-ingest-endpoint.test/v1/events',
appKey: 'web',
apiKey: 'pk_live_...', // publishable key — safe to ship in the browser
});
const app = createApp(App);
app.use(browsonicPlugin, { sdk });
app.component('BrowsonicErrorBoundary', BrowsonicErrorBoundary);
app.mount('#app');Wrap the parts of your tree you want to isolate:
<BrowsonicErrorBoundary :fallback="ErrorScreen">
<Routes />
</BrowsonicErrorBoundary>API
browsonicPlugin
app.use(browsonicPlugin, {
sdk, // optional; falls back to window.Browsonic.getBrowsonic()
chainErrorHandler: true, // optional; default true — chains into app.config.errorHandler
});Wires provide(browsonicInjectionKey, sdk) so composables can resolve via inject, and chains into app.config.errorHandler so errors that aren't caught by a boundary still reach the SDK. The previously-installed handler is preserved and called after the report.
<BrowsonicErrorBoundary>
<BrowsonicErrorBoundary :fallback="(ctx) => h('div', 'Crashed: ' + ctx.error.message)">
<Inner />
</BrowsonicErrorBoundary>Props:
| Prop | Type | Required |
| ---------- | --------------------------------------------------- | -------- |
| fallback | Component \| ((ctx: { error, reset }) => unknown) | yes |
| sdk | Browsonic | no |
| onError | (error: Error, info: string) => void | no |
When a child throws during render or in a lifecycle hook, the boundary forwards the error to the SDK (truncating any component stack to 1024 characters as componentStack metadata) and then renders the fallback. Returning false from errorCaptured stops further propagation.
useBrowsonic() / useUser() / useCaptureError() / useBreadcrumb()
import { useBrowsonic, useUser, useCaptureError, useBreadcrumb } from '@browsonic/vue';
const sdk = useBrowsonic();
useUser({ id: 'u1', email: '[email protected]' });
const captureError = useCaptureError();
const addBreadcrumb = useBreadcrumb();
function onClick() {
addBreadcrumb({ category: 'ui.click', message: 'buy button' });
try {
risky();
} catch (err) {
captureError(err as Error);
}
}useUser accepts a plain value or a Ref (re-applies on change, immediate). All four composables are no-ops when the SDK is unreachable.
installRouterInstrumentation(router, options?)
Subscribes to a Vue Router 4 afterEach guard and emits a category: 'navigation' breadcrumb on every successful route change. Pass { includeIntent: true } to also subscribe to beforeEach and emit a phase: 'intent' breadcrumb before the navigation begins (the existing afterEach breadcrumb gets phase: 'completed'). Returns the unsubscribe handle from Vue Router for HMR-friendly teardown.
import { createRouter, createWebHistory } from 'vue-router';
import { installRouterInstrumentation } from '@browsonic/vue';
const router = createRouter({ history: createWebHistory(), routes: [...] });
const off = installRouterInstrumentation(router, { includeIntent: true });
// optional: off() during HMR teardownStructural RouterLike shape — no vue-router runtime dep. Intent breadcrumbs are the only record of an attempted route when an error fires mid-navigation, so includeIntent: true is recommended for production observability.
Atlas route-template page views (trackPageViews). On every completed navigation the instrumentation can also drive the SDK's page-view channel with trackPageView(template, { name }) — the parameterized route template (/users/:id) that Atlas's URL normalization can never recover for alphabetic slugs, plus the host-authored screen name (see below).
- Default (SDK ≥ 3.14): follows the core's
atlas: trueumbrella, feature-detected per event viaBrowsonic.isAtlasEnabled(). When the host initialized withatlas: true, template tracking turns on here with no second flag, and the core hands organic page views over on the first templated view — no double counting, nomanualPageViewspairing. On older cores the default reads asfalse. - Explicit
trackPageViews: truekeeps the legacy contract on older cores — pair it withmanualPageViews: truein the SDK init there, otherwise the organic history-driven page view double counts every navigation. Explicitfalsekeeps the channel off even underatlas: true. - The page-view channel deliberately ignores
skipInitial— that flag de-dupes breadcrumb noise, and the initial navigation is exactly the page view that maps the entry screen.
routeTemplateFromLocation(to) / routeNameFromLocation(to)
The extraction helpers the instrumentation uses, exported for hosts that drive trackPageView themselves:
routeTemplateFromLocation(to)— the parameterized route template from a resolved location: the deepestto.matchedrecord'spath(Vue Router stores each record's full absolute template, so no joining is needed). Returns''when nothing matched — sendundefinedthen, so Atlas falls back to URL normalization.routeNameFromLocation(to)— the host-authored screen name:to.meta.browsonicName(explicit) orto.meta.title(the common Vue Router convention), trimmed. Returns''when the route declares neither. Feeds the SDK's screen-name channel, so a route can name its screen from code (dashboard naming rules still override it server-side).
const routes = [
{ path: '/users/:id', component: UserProfile, meta: { browsonicName: 'User Profile' } },
];installPiniaIntegration(pinia, options?)
Pinia plugin that hooks every store's $onAction.onError so unhandled action errors stamp the SDK scope with setContext('pinia', { storeId, action, args, errorMessage, state? }) before bubbling. The Vue boundary or window error handler then captures an event that already knows which store + action caused the failure.
import { createPinia } from 'pinia';
import { installPiniaIntegration } from '@browsonic/vue';
const pinia = createPinia();
installPiniaIntegration(pinia, {
// Opt-in: include store state snapshot. Off by default — Pinia stores
// commonly hold auth tokens / PII; audit per app before enabling.
captureState: false,
// Skip specific stores by `$id` (auth / wallet stores benefit).
ignoreStores: ['auth'],
});
app.use(pinia);Structural PiniaLike / PiniaStoreLike — no pinia runtime dep. maxLength (default 4096) caps serialised args / state to keep event payload size bounded.
Defensive contract
Every public surface follows the same rule:
- The host app must never crash because reporting failed.
- SDK calls are wrapped in
try { ... } catch {}. If the boundary, plugin, or composable can't reach the SDK, it stays silent. - Composables and the boundary work without the plugin — they fall back to
window.Browsonic.getBrowsonic().
What this package does NOT do
- Server-side rendering capture. Out of scope. The SDK is a browser library; the adapter is a browser library.
- Vue 2 / Options-API-only consumers. Vue 3.3+ Composition API is the contract. Vue 2 has reached end-of-life; we are not back-porting. (Note: Options API components inside a Vue 3 app are supported — the boundary catches errors from both authoring styles. The parity test suite pins this contract.)
- Vuex state snapshot on error. Pinia is the supported store integration via
installPiniaIntegration. Vuex consumers can mirror the same shape withsdk.setContext('vuex', ...)from a custom plugin, but we don't ship one.
License
Apache-2.0. See the repo root LICENSE and the package NOTICE.
