routeflow-browser 0.1.4 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Unified event tracking for all RouteFlow browser events.
3
+ */
4
+ import { Environment } from "./types";
5
+ interface EventTrackerConfig {
6
+ environment: Environment;
7
+ allowlist: string[];
8
+ ingestKey?: string;
9
+ maxQueueSize: number;
10
+ flushIntervalMs: number;
11
+ sampleRate: number;
12
+ trackExternal: boolean;
13
+ trackBackend: boolean;
14
+ trackPageViews: boolean;
15
+ }
16
+ interface EventStats {
17
+ queued: number;
18
+ sent: number;
19
+ dropped: number;
20
+ failed: number;
21
+ }
22
+ /**
23
+ * Initialize event tracking.
24
+ */
25
+ export declare function initEventTracking(config: EventTrackerConfig): void;
26
+ /**
27
+ * Track a page view.
28
+ */
29
+ export declare function trackPageView(route: string, referrer?: string): void;
30
+ /**
31
+ * Track a backend request.
32
+ */
33
+ export declare function trackBackendRequest(url: string, init: RequestInit | undefined, startTime: number, endTime: number, response: Response | null, error: Error | null, traceId: string): void;
34
+ /**
35
+ * Track an external request.
36
+ */
37
+ export declare function trackExternalRequest(url: string, init: RequestInit | undefined, startTime: number, endTime: number, response: Response | null, error: Error | null): void;
38
+ /**
39
+ * Get event stats.
40
+ */
41
+ export declare function getEventStats(): EventStats;
42
+ /**
43
+ * Get queue size.
44
+ */
45
+ export declare function getEventQueueSize(): number;
46
+ /**
47
+ * Stop event tracking.
48
+ */
49
+ export declare function stopEventTracking(): void;
50
+ export {};
@@ -0,0 +1,344 @@
1
+ /**
2
+ * Unified event tracking for all RouteFlow browser events.
3
+ */
4
+ import { extractHostname, isHostAllowed, validateTargetHost, } from "./validate";
5
+ import { getCurrentFrontendRoute } from "./route";
6
+ import { getOrCreateSessionId, generateTraceId } from "./trace";
7
+ import { sendEvents } from "./client";
8
+ class EventTracker {
9
+ constructor(config) {
10
+ this.queue = [];
11
+ this.stats = {
12
+ queued: 0,
13
+ sent: 0,
14
+ dropped: 0,
15
+ failed: 0,
16
+ };
17
+ this.flushTimer = null;
18
+ this.SDK_NAME = "@routeflow/browser";
19
+ this.SDK_VERSION = "0.1.5";
20
+ this.lastPageView = null;
21
+ this.config = config;
22
+ this.startFlushTimer();
23
+ }
24
+ /**
25
+ * Track a page view event.
26
+ */
27
+ trackPageView(route, referrer) {
28
+ if (!this.config.trackPageViews) {
29
+ return;
30
+ }
31
+ // Don't track duplicate page views
32
+ if (this.lastPageView === route) {
33
+ return;
34
+ }
35
+ this.lastPageView = route;
36
+ try {
37
+ // Sample rate check
38
+ if (Math.random() > this.config.sampleRate) {
39
+ return;
40
+ }
41
+ const session_id = getOrCreateSessionId();
42
+ const event = {
43
+ type: "frontend_page_view",
44
+ timestamp: new Date().toISOString(),
45
+ session_id,
46
+ environment: this.config.environment,
47
+ frontend_route: route,
48
+ sdk: {
49
+ name: this.SDK_NAME,
50
+ version: this.SDK_VERSION,
51
+ },
52
+ };
53
+ if (referrer) {
54
+ event.referrer = referrer;
55
+ }
56
+ // Add code version if available
57
+ if (typeof window !== "undefined" && window.__ROUTEFLOW_CODE_VERSION__) {
58
+ event.code_version = window.__ROUTEFLOW_CODE_VERSION__;
59
+ }
60
+ this.queueEvent(event);
61
+ }
62
+ catch {
63
+ // Never throw
64
+ }
65
+ }
66
+ /**
67
+ * Track a frontend-to-backend request.
68
+ */
69
+ trackBackendRequest(url, init, startTime, endTime, response, error, traceId) {
70
+ if (!this.config.trackBackend) {
71
+ return;
72
+ }
73
+ try {
74
+ // Sample rate check
75
+ if (Math.random() > this.config.sampleRate) {
76
+ return;
77
+ }
78
+ const method = (init?.method || "GET").toUpperCase();
79
+ const duration_ms = Math.round(endTime - startTime);
80
+ let outcome = "unknown";
81
+ let status_code;
82
+ if (error) {
83
+ outcome = "failure";
84
+ }
85
+ else if (response) {
86
+ status_code = response.status;
87
+ outcome = response.ok ? "success" : "failure";
88
+ }
89
+ const session_id = getOrCreateSessionId();
90
+ const frontend_route = getCurrentFrontendRoute();
91
+ const event = {
92
+ type: "frontend_backend_request",
93
+ timestamp: new Date(startTime).toISOString(),
94
+ trace_id: traceId,
95
+ session_id,
96
+ environment: this.config.environment,
97
+ frontend_route,
98
+ method,
99
+ backend_url: url,
100
+ duration_ms,
101
+ outcome,
102
+ sdk: {
103
+ name: this.SDK_NAME,
104
+ version: this.SDK_VERSION,
105
+ },
106
+ };
107
+ if (status_code !== undefined) {
108
+ event.status_code = status_code;
109
+ }
110
+ // Add code version if available
111
+ if (typeof window !== "undefined" && window.__ROUTEFLOW_CODE_VERSION__) {
112
+ event.code_version = window.__ROUTEFLOW_CODE_VERSION__;
113
+ }
114
+ this.queueEvent(event);
115
+ }
116
+ catch {
117
+ // Never throw
118
+ }
119
+ }
120
+ /**
121
+ * Track an external dependency request.
122
+ */
123
+ trackExternalRequest(url, init, startTime, endTime, response, error) {
124
+ if (!this.config.trackExternal) {
125
+ return;
126
+ }
127
+ try {
128
+ // Don't track requests to the Routeflow ingest endpoint (prevents feedback loop)
129
+ if (url.includes('/v1/ingest')) {
130
+ return;
131
+ }
132
+ // Sample rate check
133
+ if (Math.random() > this.config.sampleRate) {
134
+ return;
135
+ }
136
+ // Extract hostname
137
+ const hostname = extractHostname(url);
138
+ if (!hostname) {
139
+ return;
140
+ }
141
+ // Check allowlist
142
+ if (!isHostAllowed(hostname, this.config.allowlist)) {
143
+ return;
144
+ }
145
+ // Validate hostname (no scheme, path, query)
146
+ if (!validateTargetHost(hostname)) {
147
+ return;
148
+ }
149
+ const method = (init?.method || "GET").toUpperCase();
150
+ const duration_ms = Math.round(endTime - startTime);
151
+ let outcome = "unknown";
152
+ let status_code;
153
+ if (error) {
154
+ outcome = "failure";
155
+ }
156
+ else if (response) {
157
+ status_code = response.status;
158
+ outcome = response.ok ? "success" : "failure";
159
+ }
160
+ // Extract trace_id from request headers (same as sent to backend)
161
+ let trace_id;
162
+ const session_id = getOrCreateSessionId();
163
+ if (init && init.headers) {
164
+ const headers = new Headers(init.headers);
165
+ trace_id = headers.get("x-routeflow-trace-id") || undefined;
166
+ }
167
+ // Fallback: generate new trace_id if not found
168
+ if (!trace_id) {
169
+ trace_id = generateTraceId();
170
+ }
171
+ const frontend_route = getCurrentFrontendRoute();
172
+ const event = {
173
+ type: "frontend_external_request_observed",
174
+ timestamp: new Date(startTime).toISOString(),
175
+ trace_id,
176
+ session_id,
177
+ environment: this.config.environment,
178
+ frontend_route,
179
+ target_host: hostname,
180
+ method,
181
+ duration_ms,
182
+ outcome,
183
+ sdk: {
184
+ name: this.SDK_NAME,
185
+ version: this.SDK_VERSION,
186
+ },
187
+ };
188
+ if (status_code !== undefined) {
189
+ event.status_code = status_code;
190
+ }
191
+ // Add code version if available
192
+ if (typeof window !== "undefined" && window.__ROUTEFLOW_CODE_VERSION__) {
193
+ event.code_version = window.__ROUTEFLOW_CODE_VERSION__;
194
+ }
195
+ this.queueEvent(event);
196
+ }
197
+ catch {
198
+ // Never throw
199
+ }
200
+ }
201
+ /**
202
+ * Queue an event for sending.
203
+ */
204
+ queueEvent(event) {
205
+ if (this.queue.length >= this.config.maxQueueSize) {
206
+ this.stats.dropped++;
207
+ return;
208
+ }
209
+ this.queue.push(event);
210
+ this.stats.queued++;
211
+ // Flush if queue is getting large
212
+ if (this.queue.length >= 50) {
213
+ this.flush();
214
+ }
215
+ }
216
+ /**
217
+ * Flush queued events to backend.
218
+ */
219
+ async flush() {
220
+ if (this.queue.length === 0) {
221
+ return;
222
+ }
223
+ // Check for ingest key
224
+ if (!this.config.ingestKey) {
225
+ // Drop events silently
226
+ this.stats.dropped += this.queue.length;
227
+ this.queue = [];
228
+ return;
229
+ }
230
+ // Take events from queue
231
+ const events = this.queue.splice(0, this.queue.length);
232
+ // Build payload
233
+ const payload = {
234
+ sent_at: new Date().toISOString(),
235
+ events,
236
+ };
237
+ // Send to backend
238
+ const success = await sendEvents(payload, this.config.ingestKey);
239
+ if (success) {
240
+ this.stats.sent += events.length;
241
+ }
242
+ else {
243
+ this.stats.failed += events.length;
244
+ }
245
+ }
246
+ /**
247
+ * Start flush timer.
248
+ */
249
+ startFlushTimer() {
250
+ if (this.flushTimer !== null) {
251
+ return;
252
+ }
253
+ this.flushTimer = window.setInterval(() => {
254
+ this.flush();
255
+ }, this.config.flushIntervalMs);
256
+ }
257
+ /**
258
+ * Stop flush timer.
259
+ */
260
+ stop() {
261
+ if (this.flushTimer !== null) {
262
+ clearInterval(this.flushTimer);
263
+ this.flushTimer = null;
264
+ }
265
+ // Final flush
266
+ this.flush();
267
+ }
268
+ /**
269
+ * Get current stats.
270
+ */
271
+ getStats() {
272
+ return { ...this.stats };
273
+ }
274
+ /**
275
+ * Get queue size.
276
+ */
277
+ getQueueSize() {
278
+ return this.queue.length;
279
+ }
280
+ }
281
+ let tracker = null;
282
+ /**
283
+ * Initialize event tracking.
284
+ */
285
+ export function initEventTracking(config) {
286
+ if (tracker) {
287
+ tracker.stop();
288
+ }
289
+ tracker = new EventTracker(config);
290
+ }
291
+ /**
292
+ * Track a page view.
293
+ */
294
+ export function trackPageView(route, referrer) {
295
+ if (!tracker) {
296
+ return;
297
+ }
298
+ tracker.trackPageView(route, referrer);
299
+ }
300
+ /**
301
+ * Track a backend request.
302
+ */
303
+ export function trackBackendRequest(url, init, startTime, endTime, response, error, traceId) {
304
+ if (!tracker) {
305
+ return;
306
+ }
307
+ tracker.trackBackendRequest(url, init, startTime, endTime, response, error, traceId);
308
+ }
309
+ /**
310
+ * Track an external request.
311
+ */
312
+ export function trackExternalRequest(url, init, startTime, endTime, response, error) {
313
+ if (!tracker) {
314
+ return;
315
+ }
316
+ tracker.trackExternalRequest(url, init, startTime, endTime, response, error);
317
+ }
318
+ /**
319
+ * Get event stats.
320
+ */
321
+ export function getEventStats() {
322
+ if (!tracker) {
323
+ return { queued: 0, sent: 0, dropped: 0, failed: 0 };
324
+ }
325
+ return tracker.getStats();
326
+ }
327
+ /**
328
+ * Get queue size.
329
+ */
330
+ export function getEventQueueSize() {
331
+ if (!tracker) {
332
+ return 0;
333
+ }
334
+ return tracker.getQueueSize();
335
+ }
336
+ /**
337
+ * Stop event tracking.
338
+ */
339
+ export function stopEventTracking() {
340
+ if (tracker) {
341
+ tracker.stop();
342
+ tracker = null;
343
+ }
344
+ }
@@ -1,14 +1,15 @@
1
1
  /**
2
- * Fetch patching for correlation headers and external span tracking.
2
+ * Fetch patching for correlation headers and event tracking.
3
3
  *
4
4
  * This SDK patches fetch() to:
5
5
  * 1. Add correlation headers to requests to user's app backend
6
- * 2. Track external requests and send telemetry to RouteFlow backend
6
+ * 2. Track backend requests and send telemetry to RouteFlow
7
+ * 3. Track external requests and send telemetry to RouteFlow
7
8
  */
8
9
  /**
9
- * Patch window.fetch to add correlation headers and track external requests.
10
+ * Patch window.fetch to add correlation headers and track requests.
10
11
  */
11
- export declare function patchFetch(enableExternalTracking: boolean, backendUrl?: string): void;
12
+ export declare function patchFetch(enableExternalTracking: boolean, enableBackendTracking: boolean, backendUrl?: string): void;
12
13
  /**
13
14
  * Restore original fetch.
14
15
  */
@@ -1,20 +1,22 @@
1
1
  /**
2
- * Fetch patching for correlation headers and external span tracking.
2
+ * Fetch patching for correlation headers and event tracking.
3
3
  *
4
4
  * This SDK patches fetch() to:
5
5
  * 1. Add correlation headers to requests to user's app backend
6
- * 2. Track external requests and send telemetry to RouteFlow backend
6
+ * 2. Track backend requests and send telemetry to RouteFlow
7
+ * 3. Track external requests and send telemetry to RouteFlow
7
8
  */
8
9
  import { isSameOrigin } from "./validate";
9
10
  import { getOrCreateSessionId, generateTraceId } from "./trace";
10
11
  import { getCurrentFrontendRoute } from "./route";
11
- import { trackExternalFetch } from "./external_spans";
12
+ import { trackBackendRequest, trackExternalRequest } from "./event_tracker";
12
13
  const TRACE_ID_HEADER = "x-routeflow-trace-id";
13
14
  const SESSION_ID_HEADER = "x-routeflow-session-id";
14
15
  const FRONTEND_ROUTE_HEADER = "x-routeflow-frontend-route";
15
16
  let originalFetch;
16
17
  let isPatched = false;
17
18
  let trackExternal = false;
19
+ let trackBackend = false;
18
20
  let userBackendUrl = ""; // User's app backend URL for header injection
19
21
  /**
20
22
  * Check if a URL should receive correlation headers.
@@ -39,9 +41,9 @@ function shouldInjectHeaders(url) {
39
41
  return isSameOrigin(url);
40
42
  }
41
43
  /**
42
- * Patch window.fetch to add correlation headers and track external requests.
44
+ * Patch window.fetch to add correlation headers and track requests.
43
45
  */
44
- export function patchFetch(enableExternalTracking, backendUrl) {
46
+ export function patchFetch(enableExternalTracking, enableBackendTracking, backendUrl) {
45
47
  if (isPatched) {
46
48
  return;
47
49
  }
@@ -50,6 +52,7 @@ export function patchFetch(enableExternalTracking, backendUrl) {
50
52
  }
51
53
  originalFetch = window.fetch;
52
54
  trackExternal = enableExternalTracking;
55
+ trackBackend = enableBackendTracking;
53
56
  userBackendUrl = backendUrl || "";
54
57
  window.fetch = async function patchedFetch(input, init) {
55
58
  // Convert input to URL string
@@ -61,30 +64,41 @@ export function patchFetch(enableExternalTracking, backendUrl) {
61
64
  const shouldInject = shouldInjectHeaders(url);
62
65
  const sameOrigin = isSameOrigin(url);
63
66
  // Add correlation headers to requests going to user's app backend
67
+ let traceId;
64
68
  if (shouldInject) {
65
- init = addCorrelationHeaders(input, init);
69
+ const result = addCorrelationHeaders(input, init);
70
+ init = result.init;
71
+ traceId = result.traceId;
66
72
  }
67
- // Track external spans if enabled and not same-origin
68
- // This sends telemetry to the RouteFlow backend
73
+ // Track backend requests (to user's app)
74
+ if (trackBackend && shouldInject && traceId) {
75
+ return trackAndFetchBackend(url, init, traceId);
76
+ }
77
+ // Track external requests (to third-party APIs)
69
78
  if (trackExternal && !sameOrigin) {
70
- return trackAndFetch(url, init);
79
+ return trackAndFetchExternal(url, init);
71
80
  }
72
- // Regular fetch
81
+ // Regular fetch (no tracking)
73
82
  return originalFetch(input, init);
74
83
  };
75
84
  isPatched = true;
76
85
  }
77
86
  /**
78
87
  * Add RouteFlow correlation headers to requests going to user's app backend.
88
+ * Returns the modified init and the trace ID that was added.
79
89
  */
80
90
  function addCorrelationHeaders(input, init) {
81
91
  // Get or create headers
82
92
  const headers = new Headers(init?.headers);
83
93
  // Add trace ID if not already present (unique per request)
94
+ let traceId;
84
95
  if (!headers.has(TRACE_ID_HEADER)) {
85
- const traceId = generateTraceId();
96
+ traceId = generateTraceId();
86
97
  headers.set(TRACE_ID_HEADER, traceId);
87
98
  }
99
+ else {
100
+ traceId = headers.get(TRACE_ID_HEADER);
101
+ }
88
102
  // Add session ID if not already present (persists across requests)
89
103
  if (!headers.has(SESSION_ID_HEADER)) {
90
104
  const sessionId = getOrCreateSessionId();
@@ -96,15 +110,38 @@ function addCorrelationHeaders(input, init) {
96
110
  headers.set(FRONTEND_ROUTE_HEADER, route);
97
111
  }
98
112
  return {
99
- ...init,
100
- headers,
113
+ init: {
114
+ ...init,
115
+ headers,
116
+ },
117
+ traceId,
101
118
  };
102
119
  }
103
120
  /**
104
- * Track external fetch call and send telemetry to RouteFlow backend.
105
- * This does NOT add correlation headers - those are only for same-origin requests.
121
+ * Track backend request and send telemetry to RouteFlow.
122
+ */
123
+ async function trackAndFetchBackend(url, init, traceId) {
124
+ const startTime = performance.now();
125
+ let response = null;
126
+ let error = null;
127
+ try {
128
+ response = await originalFetch(url, init);
129
+ return response;
130
+ }
131
+ catch (e) {
132
+ error = e;
133
+ throw e;
134
+ }
135
+ finally {
136
+ const endTime = performance.now();
137
+ // Send telemetry about this backend request
138
+ trackBackendRequest(url, init, startTime, endTime, response, error, traceId);
139
+ }
140
+ }
141
+ /**
142
+ * Track external request and send telemetry to RouteFlow.
106
143
  */
107
- async function trackAndFetch(url, init) {
144
+ async function trackAndFetchExternal(url, init) {
108
145
  const startTime = performance.now();
109
146
  let response = null;
110
147
  let error = null;
@@ -118,8 +155,8 @@ async function trackAndFetch(url, init) {
118
155
  }
119
156
  finally {
120
157
  const endTime = performance.now();
121
- // This sends telemetry data to RouteFlow backend
122
- trackExternalFetch(url, init, startTime, endTime, response, error);
158
+ // Send telemetry about this external request
159
+ trackExternalRequest(url, init, startTime, endTime, response, error);
123
160
  }
124
161
  }
125
162
  /**
package/dist/init.js CHANGED
@@ -1,10 +1,10 @@
1
1
  /**
2
2
  * Initialization and configuration for Routeflow Browser SDK.
3
3
  */
4
- import { initRouteTracking, getCurrentFrontendRoute } from "./route";
4
+ import { initRouteTracking, getCurrentFrontendRoute, setRouteChangeCallback } from "./route";
5
5
  import { getOrCreateSessionId } from "./trace";
6
6
  import { patchFetch, unpatchFetch } from "./fetch_patch";
7
- import { initExternalSpanTracking, getExternalSpanStats, getExternalSpanQueueSize, stopExternalSpanTracking, } from "./external_spans";
7
+ import { initEventTracking, getEventStats, getEventQueueSize, stopEventTracking, trackPageView, } from "./event_tracker";
8
8
  let isInitialized = false;
9
9
  let config = {
10
10
  ingestKey: "",
@@ -107,36 +107,47 @@ export function initRouteflow(opts) {
107
107
  }
108
108
  // Initialize route tracking
109
109
  initRouteTracking();
110
+ // Set up route change callback to track page views on navigation
111
+ setRouteChangeCallback((route) => {
112
+ trackPageView(route);
113
+ });
110
114
  // Initialize session ID (persists across page navigation within session)
111
115
  getOrCreateSessionId();
112
- // Patch fetch for correlation headers (pass user's backend URL)
113
- patchFetch(config.trackExternal, config.backendUrl);
114
- // Initialize external span tracking if enabled
115
- if (config.trackExternal) {
116
- initExternalSpanTracking({
117
- environment: config.environment,
118
- allowlist: config.externalAllowlist,
119
- ingestKey: config.ingestKey || undefined,
120
- maxQueueSize: config.maxQueueSize,
121
- flushIntervalMs: config.flushIntervalMs,
122
- sampleRate: config.sampleRate,
123
- });
124
- }
116
+ // Patch fetch for correlation headers and tracking
117
+ patchFetch(config.trackExternal, // Track external API calls
118
+ true, // Always track backend requests
119
+ config.backendUrl);
120
+ // Initialize unified event tracking
121
+ initEventTracking({
122
+ environment: config.environment,
123
+ allowlist: config.externalAllowlist,
124
+ ingestKey: config.ingestKey || undefined,
125
+ maxQueueSize: config.maxQueueSize,
126
+ flushIntervalMs: config.flushIntervalMs,
127
+ sampleRate: config.sampleRate,
128
+ trackExternal: config.trackExternal,
129
+ trackBackend: true, // Always track backend requests
130
+ trackPageViews: true, // Always track page views
131
+ });
132
+ // Track initial page view
133
+ const currentRoute = getCurrentFrontendRoute();
134
+ const referrer = typeof document !== 'undefined' ? document.referrer : undefined;
135
+ trackPageView(currentRoute, referrer);
125
136
  isInitialized = true;
126
137
  }
127
138
  /**
128
139
  * Get Routeflow stats.
129
140
  */
130
141
  export function getRouteflowStats() {
131
- const externalStats = getExternalSpanStats();
142
+ const eventStats = getEventStats();
132
143
  return {
133
144
  enabled: config.enabled,
134
145
  session_id: getOrCreateSessionId(),
135
146
  current_route: getCurrentFrontendRoute(),
136
- events_queued: getExternalSpanQueueSize(),
137
- events_sent: externalStats.sent,
138
- events_dropped: externalStats.dropped,
139
- events_failed: externalStats.failed,
147
+ events_queued: getEventQueueSize(),
148
+ events_sent: eventStats.sent,
149
+ events_dropped: eventStats.dropped,
150
+ events_failed: eventStats.failed,
140
151
  external_tracking_enabled: config.trackExternal,
141
152
  has_ingest_key: !!config.ingestKey,
142
153
  };
@@ -149,6 +160,6 @@ export function shutdownRouteflow() {
149
160
  return;
150
161
  }
151
162
  unpatchFetch();
152
- stopExternalSpanTracking();
163
+ stopEventTracking();
153
164
  isInitialized = false;
154
165
  }
package/dist/route.d.ts CHANGED
@@ -1,6 +1,10 @@
1
1
  /**
2
2
  * Frontend route tracking (framework-agnostic).
3
3
  */
4
+ /**
5
+ * Set route change callback.
6
+ */
7
+ export declare function setRouteChangeCallback(callback: (route: string) => void): void;
4
8
  /**
5
9
  * Initialize route tracking by patching history API.
6
10
  */
package/dist/route.js CHANGED
@@ -3,6 +3,13 @@
3
3
  */
4
4
  import { normalizeFrontendRoute } from "./validate";
5
5
  let currentRoute = "/";
6
+ let onRouteChange = null;
7
+ /**
8
+ * Set route change callback.
9
+ */
10
+ export function setRouteChangeCallback(callback) {
11
+ onRouteChange = callback;
12
+ }
6
13
  /**
7
14
  * Initialize route tracking by patching history API.
8
15
  */
@@ -32,7 +39,14 @@ export function initRouteTracking() {
32
39
  * Update the current route from location.pathname.
33
40
  */
34
41
  function updateCurrentRoute() {
35
- currentRoute = normalizeFrontendRoute(window.location.pathname);
42
+ const newRoute = normalizeFrontendRoute(window.location.pathname);
43
+ // Only trigger callback if route actually changed
44
+ if (newRoute !== currentRoute) {
45
+ currentRoute = newRoute;
46
+ if (onRouteChange) {
47
+ onRouteChange(newRoute);
48
+ }
49
+ }
36
50
  }
37
51
  /**
38
52
  * Get the current frontend route.
package/dist/types.d.ts CHANGED
@@ -40,9 +40,41 @@ export interface ExternalSpanEvent {
40
40
  };
41
41
  code_version?: string;
42
42
  }
43
+ export interface PageViewEvent {
44
+ type: "frontend_page_view";
45
+ timestamp: string;
46
+ session_id: string;
47
+ environment: Environment;
48
+ frontend_route: string;
49
+ referrer?: string;
50
+ sdk: {
51
+ name: string;
52
+ version: string;
53
+ };
54
+ code_version?: string;
55
+ }
56
+ export interface BackendRequestEvent {
57
+ type: "frontend_backend_request";
58
+ timestamp: string;
59
+ trace_id: string;
60
+ session_id: string;
61
+ environment: Environment;
62
+ frontend_route: string;
63
+ method: string;
64
+ backend_url: string;
65
+ duration_ms: number;
66
+ outcome: "success" | "failure" | "unknown";
67
+ status_code?: number;
68
+ sdk: {
69
+ name: string;
70
+ version: string;
71
+ };
72
+ code_version?: string;
73
+ }
74
+ export type RouteflowEvent = ExternalSpanEvent | PageViewEvent | BackendRequestEvent;
43
75
  export interface IngestPayload {
44
76
  sent_at: string;
45
- events: ExternalSpanEvent[];
77
+ events: RouteflowEvent[];
46
78
  }
47
79
  export interface RouteflowStats {
48
80
  enabled: boolean;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "routeflow-browser",
3
- "version": "0.1.4",
4
- "description": "Browser SDK for Routeflow - Frontend to backend correlation and external dependency tracking",
3
+ "version": "0.1.5",
4
+ "description": "Browser SDK for Routeflow - Frontend telemetry, correlation headers, and dependency tracking",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "scripts": {