/** * Angular Router instrumentation. Subscribes to a `Router.events` * Observable, filters for the `NavigationEnd` event, and emits a * `category: 'navigation'` breadcrumb with the resolved URL. * * Why a structural router type instead of `import { Router } from '@angular/router'`: * the adapter must not pull `@angular/router` (or `@angular/core`) * into its runtime graph — both stay peer-only type imports. The * `RouterLike` shape below covers Angular Router 14+ and any * Subscribable/Observable that emits the same payload (incl. test * doubles). NavigationEnd is detected by structural discriminator * (`'urlAfterRedirects' in event`) so we don't need * `instanceof NavigationEnd` either. * * Defensive contract: * - The breadcrumb call is wrapped in try/catch — a thrown * `addBreadcrumb` cannot break a navigation observer. * - The returned unsubscribe forwards to the upstream subscription's * `unsubscribe()` (RxJS-compatible) so HMR / tear-down hosts can * clean up without leaking listeners. * * @copyright 2024-2026 Browsonic * @license Apache-2.0 */ import type { Browsonic } from '@browsonic/sdk'; /** * Minimal RxJS-compatible subscription. RxJS `Subscription`, * `Observer`, and the value returned by `Observable.subscribe` all * conform. */ export interface SubscriptionLike { unsubscribe(): void; } /** * Minimal observable shape. Any object with a single-callback * `subscribe()` works — RxJS Observables, simple event streams, and * test doubles. */ export interface ObservableLike { subscribe(observer: (value: T) => void): SubscriptionLike; } /** * Discriminator-friendly subset of Angular's NavigationEnd. Captures * only the fields we read; `urlAfterRedirects` is the structural * discriminator that separates NavigationEnd from NavigationStart / * Cancel / Error. */ export interface RouterEventLike { url?: string; urlAfterRedirects?: string; navigationTrigger?: 'imperative' | 'popstate' | 'hashchange'; } /** * Structural subset of Angular's ActivatedRouteSnapshot — just enough to * walk the matched route tree and read each level's `routeConfig.path` * (the AUTHORITATIVE parameterized template, e.g. `users/:id`). */ export interface ActivatedRouteSnapshotLike { routeConfig?: { path?: string; } | null; firstChild?: ActivatedRouteSnapshotLike | null; children?: ActivatedRouteSnapshotLike[]; /** * Route `data` bag — read for the SDK 3.13 screen-name channel * (`browsonicName`/`title`) and the SDK 3.17.0 Atlas screen context * (`browsonicScreen`). */ data?: Record | null; } /** * Structural shape of a route's `data.browsonicScreen` declaration — the * SDK 3.17.0 Atlas screen context. Mirrors the SDK's * `PageViewScreenContext`; typed locally as plain strings so route data * (untyped by Angular) passes through without casts. The SDK normalizes * (trim + lowercase) and validates each field independently — invalid * values are dropped field-wise on the client, never breaking the pv. */ export interface RouteScreenContextLike { /** Stable machine screen id, lower-kebab/dot, ≤ 64 (e.g. `'billing.invoice-detail'`). */ id?: string; /** Screen archetype: list|detail|form|wizard|dashboard|modal|search|auth|other. */ kind?: string; /** Zone/module id, lower-kebab/dot, ≤ 40 (e.g. `'billing'`). */ zone?: string; } /** * Minimal Router shape — `events` is required; `routerState` is only read * when {@link InstallRouterInstrumentationOptions.trackPageViews} is on. */ export interface RouterLike { events: ObservableLike; routerState?: { snapshot?: { root?: ActivatedRouteSnapshotLike; }; }; } /** * Join the matched route tree's `routeConfig.path` segments into the * parameterized route TEMPLATE (`/users/:id/orders`) — the thing the regex * normalizer fundamentally cannot recover for alphabetic slugs, and exactly * what the Atlas backend prefers over the normalized url (SDK >= 3.11 pv * `route` field). Empty-path levels (componentless/lazy wrappers) are * skipped. Returns '' when nothing matched (the caller then omits the field * and Atlas falls back to url normalization — never worse than before). */ export declare function routeTemplateFromSnapshot(root: ActivatedRouteSnapshotLike | undefined): string; /** * SDK 3.13 screen-name channel: the DEEPEST matched route's * `data['browsonicName']` (explicit) or `data['title']` (the common Angular * convention) — a host-authored human name for the screen, or '' when the * route tree declares none. Walks the same primary-outlet chain (and depth * cap) as {@link routeTemplateFromSnapshot}. */ export declare function routeNameFromSnapshot(root: ActivatedRouteSnapshotLike | undefined): string; /** * SDK 3.17.0 Atlas screen-context channel: the DEEPEST matched route's * `data['browsonicScreen']` (`{ id, kind, zone }`), or undefined when no * level declares one. Deepest wins WHOLESALE (no per-field merging across * levels — a child's declaration replaces the parent's, mirroring Angular's * own route-data inheritance intuition). Walks the same primary-outlet * chain (and depth cap) as {@link routeTemplateFromSnapshot}. Only string * fields are forwarded; the SDK normalizes + validates them field-wise. */ export declare function routeScreenFromSnapshot(root: ActivatedRouteSnapshotLike | undefined): RouteScreenContextLike | undefined; export interface InstallRouterInstrumentationOptions { /** SDK override; defaults to `resolveSdk()` window-singleton lookup. */ sdk?: Browsonic; /** Breadcrumb category. Defaults to `'navigation'`. */ category?: string; /** * Drive Atlas page views from the Router with the AUTHORITATIVE route * template: on every NavigationEnd this calls * `Browsonic.trackPageView(template)` with the matched route tree's * parameterized path (`/users/:id`). * * Default (SDK ≥ 3.14): follows the core's `atlas: true` umbrella — when * the host initialized with it, template tracking turns on here with no * second flag, and the core hands organic page views over on the first * template (no double counting, no `manualPageViews` pairing). On older * cores the default stays `false`; setting `true` explicitly keeps the * legacy contract (pair it with `manualPageViews: true` in init there). */ trackPageViews?: boolean; } /** * Wire an Angular Router (or any compatible RouterLike) into the * Browsonic SDK so navigation breadcrumbs land alongside captured * errors. Returns an `unsubscribe()` callable. * * @example * ```ts * import { Router } from '@angular/router'; * import { installRouterInstrumentation } from '@browsonic/angular'; * * @Component({ ... }) * export class AppComponent implements OnInit, OnDestroy { * private off?: () => void; * constructor(private router: Router) {} * ngOnInit() { this.off = installRouterInstrumentation(this.router); } * ngOnDestroy() { this.off?.(); } * } * ``` */ export declare function installRouterInstrumentation(router: RouterLike, options?: InstallRouterInstrumentationOptions): () => void; //# sourceMappingURL=router.d.ts.map