@lavarage/telemetry 1.1.0 → 1.2.0

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.
package/README.md CHANGED
@@ -263,6 +263,22 @@ Track a system event (not tied to any wallet address). System events are display
263
263
  ```typescript
264
264
  // Track app startup
265
265
  telemetry.trackSystemEvent('app_start', 'Application initialized', 'info');
266
+ ```
267
+
268
+ #### `trackStateChange(stateName: string, previousValue?: any, newValue?: any, action?: string, metadata?: object)`
269
+
270
+ Track a React state change. Useful for debugging and understanding user interactions.
271
+
272
+ ```typescript
273
+ // Track a state change manually
274
+ telemetry.trackStateChange(
275
+ 'userPreferences',
276
+ { theme: 'light' },
277
+ { theme: 'dark' },
278
+ 'TOGGLE_THEME',
279
+ { source: 'settings-panel' }
280
+ );
281
+ ```
266
282
 
267
283
  // Track feature usage
268
284
  telemetry.trackSystemEvent('feature_used', 'User enabled dark mode', 'info', {
@@ -333,6 +349,177 @@ Clean up the telemetry instance, restore original functions, and flush remaining
333
349
  telemetry.destroy();
334
350
  ```
335
351
 
352
+ ## React State Tracking
353
+
354
+ The SDK provides React hooks for automatically tracking state changes in your React components.
355
+
356
+ ### Installation
357
+
358
+ ```bash
359
+ # Install the SDK
360
+ npm install @lavarage/telemetry
361
+
362
+ # If you want to use React hooks, also install React (peer dependency)
363
+ npm install react
364
+ ```
365
+
366
+ ### Quick Start
367
+
368
+ ```typescript
369
+ import { LavarageTelemetry } from '@lavarage/telemetry';
370
+ // Import React hooks from the separate entry point
371
+ import { useTrackedState } from '@lavarage/telemetry/react';
372
+
373
+ const telemetry = new LavarageTelemetry({
374
+ apiEndpoint: 'https://telemetry.lavarage.com',
375
+ platform: 'lavarage-web',
376
+ });
377
+
378
+ function MyComponent() {
379
+ // Automatically tracks all state changes
380
+ const [count, setCount] = useTrackedState(0, {
381
+ stateName: 'counter',
382
+ telemetry: telemetry,
383
+ action: 'SET_COUNT'
384
+ });
385
+
386
+ return (
387
+ <button onClick={() => setCount(count + 1)}>
388
+ Count: {count}
389
+ </button>
390
+ );
391
+ }
392
+ ```
393
+
394
+ ### Available Hooks
395
+
396
+ #### `useTrackedState(initialState, config)`
397
+
398
+ Wraps React's `useState` and automatically tracks state changes.
399
+
400
+ ```typescript
401
+ const [state, setState] = useTrackedState(initialValue, {
402
+ stateName: 'userPreferences', // Required: name for this state
403
+ telemetry: telemetryInstance, // Required: telemetry instance
404
+ trackInitial: false, // Optional: track initial state (default: false)
405
+ action: 'UPDATE_PREFERENCES', // Optional: action name
406
+ metadata: { source: 'settings' }, // Optional: additional metadata
407
+ shouldTrack: (prev, next) => { // Optional: filter which changes to track
408
+ return prev !== next; // Only track if values actually changed
409
+ }
410
+ });
411
+ ```
412
+
413
+ #### `useTrackedReducer(reducer, initialState, config)`
414
+
415
+ Wraps React's `useReducer` and automatically tracks state changes with action information.
416
+
417
+ ```typescript
418
+ const [state, dispatch] = useTrackedReducer(reducer, initialState, {
419
+ stateName: 'cart',
420
+ telemetry: telemetryInstance,
421
+ trackInitial: false,
422
+ metadata: { userId: '123' }
423
+ });
424
+
425
+ // Dispatch actions normally - they'll be tracked automatically
426
+ dispatch({ type: 'ADD_ITEM', payload: { id: 1, name: 'Product' } });
427
+ ```
428
+
429
+ #### `useStateTracker(telemetry)`
430
+
431
+ Returns a function to manually track state changes with more control.
432
+
433
+ ```typescript
434
+ const trackState = useStateTracker(telemetry);
435
+
436
+ const handleChange = (newValue) => {
437
+ trackState('theme', currentValue, newValue, 'SET_THEME', {
438
+ source: 'user-action'
439
+ });
440
+ setCurrentValue(newValue);
441
+ };
442
+ ```
443
+
444
+ ### Configuration Options
445
+
446
+ - **stateName** (required): A unique identifier for this state (e.g., 'cart', 'userPreferences', 'theme')
447
+ - **telemetry** (required): Your telemetry instance
448
+ - **trackInitial** (optional): Whether to track the initial state value (default: false)
449
+ - **action** (optional): Action name to use when tracking (default: 'STATE_CHANGE')
450
+ - **metadata** (optional): Additional context to include with each state change
451
+ - **shouldTrack** (optional): Function to filter which changes to track - useful for avoiding noise
452
+
453
+ ### Example: Shopping Cart
454
+
455
+ ```typescript
456
+ import { useTrackedState } from '@lavarage/telemetry/react';
457
+
458
+ function ShoppingCart() {
459
+ const [items, setItems] = useTrackedState([], {
460
+ stateName: 'cartItems',
461
+ telemetry: telemetry,
462
+ action: 'CART_UPDATE',
463
+ shouldTrack: (prev, next) => {
464
+ // Only track if cart actually changed (not just reference)
465
+ return JSON.stringify(prev) !== JSON.stringify(next);
466
+ }
467
+ });
468
+
469
+ const addItem = (item) => {
470
+ setItems([...items, item]);
471
+ // State change is automatically tracked!
472
+ };
473
+
474
+ return (
475
+ <div>
476
+ {items.map(item => <div key={item.id}>{item.name}</div>)}
477
+ <button onClick={() => addItem({ id: 1, name: 'Product' })}>
478
+ Add Item
479
+ </button>
480
+ </div>
481
+ );
482
+ }
483
+ ```
484
+
485
+ ### Example: User Preferences with useReducer
486
+
487
+ ```typescript
488
+ import { useTrackedReducer } from '@lavarage/telemetry/react';
489
+
490
+ function preferencesReducer(state, action) {
491
+ switch (action.type) {
492
+ case 'SET_THEME':
493
+ return { ...state, theme: action.payload };
494
+ case 'SET_LANGUAGE':
495
+ return { ...state, language: action.payload };
496
+ default:
497
+ return state;
498
+ }
499
+ }
500
+
501
+ function UserPreferences() {
502
+ const [prefs, dispatch] = useTrackedReducer(
503
+ preferencesReducer,
504
+ { theme: 'light', language: 'en' },
505
+ {
506
+ stateName: 'userPreferences',
507
+ telemetry: telemetry,
508
+ metadata: { userId: currentUser.id }
509
+ }
510
+ );
511
+
512
+ return (
513
+ <div>
514
+ <button onClick={() => dispatch({ type: 'SET_THEME', payload: 'dark' })}>
515
+ Dark Mode
516
+ </button>
517
+ {/* State changes are automatically tracked with action type! */}
518
+ </div>
519
+ );
520
+ }
521
+ ```
522
+
336
523
  ## Event Types
337
524
 
338
525
  The SDK tracks the following event types:
@@ -343,6 +530,7 @@ The SDK tracks the following event types:
343
530
  - **request**: Network requests (fetch/Axios)
344
531
  - **system_event**: System-level events not tied to any wallet address (displayed in separate dashboard panel)
345
532
  - **log**: Custom log events (via `logWalletEvent()` or `sendLog()`)
533
+ - **state_change**: React state changes (via `trackStateChange()` or React hooks)
346
534
 
347
535
  ## Batching
348
536
 
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { TelemetryConfig, TelemetryEvent, ErrorEvent, RequestEvent, HostFilterInput, HostFilterConfig, ErrorFilterConfig, SystemEvent } from './types';
2
2
  export type { TelemetryConfig, TelemetryEvent, ErrorEvent, RequestEvent, HostFilterInput, HostFilterConfig, ErrorFilterConfig, SystemEvent, };
3
+ export type { StateChangeEvent } from './types';
3
4
  export declare class LavarageTelemetry {
4
5
  private apiEndpoint;
5
6
  private platform;
@@ -64,5 +65,15 @@ export declare class LavarageTelemetry {
64
65
  */
65
66
  sendLog(walletAddress: string, eventType: string, message: string, metadata?: object): Promise<void>;
66
67
  updateHostFilter(captureHosts: HostFilterInput): void;
68
+ /**
69
+ * Track a React state change
70
+ * Use this method to manually log state changes, or use the useTrackedState/useTrackedReducer hooks for automatic tracking
71
+ * @param stateName - Name/identifier for the state (e.g., 'userPreferences', 'cartItems', 'theme')
72
+ * @param previousValue - Previous state value (optional, will be sanitized)
73
+ * @param newValue - New state value (optional, will be sanitized)
74
+ * @param action - Action that caused the change (e.g., 'SET_USER', 'ADD_TO_CART', 'TOGGLE_THEME')
75
+ * @param metadata - Additional context about the state change
76
+ */
77
+ trackStateChange(stateName: string, previousValue?: any, newValue?: any, action?: string, metadata?: object): void;
67
78
  destroy(): void;
68
79
  }
package/dist/index.js CHANGED
@@ -684,6 +684,36 @@ class LavarageTelemetry {
684
684
  // Silently fail
685
685
  }
686
686
  }
687
+ /**
688
+ * Track a React state change
689
+ * Use this method to manually log state changes, or use the useTrackedState/useTrackedReducer hooks for automatic tracking
690
+ * @param stateName - Name/identifier for the state (e.g., 'userPreferences', 'cartItems', 'theme')
691
+ * @param previousValue - Previous state value (optional, will be sanitized)
692
+ * @param newValue - New state value (optional, will be sanitized)
693
+ * @param action - Action that caused the change (e.g., 'SET_USER', 'ADD_TO_CART', 'TOGGLE_THEME')
694
+ * @param metadata - Additional context about the state change
695
+ */
696
+ trackStateChange(stateName, previousValue, newValue, action, metadata) {
697
+ try {
698
+ const event = {
699
+ type: 'state_change',
700
+ wallet: this.wallet,
701
+ platform: this.platform,
702
+ stateName,
703
+ previousValue: previousValue !== undefined ? this.sanitizePayload(previousValue) : undefined,
704
+ newValue: newValue !== undefined ? this.sanitizePayload(newValue) : undefined,
705
+ action: action || 'STATE_CHANGE',
706
+ metadata: metadata ? this.sanitizePayload(metadata) : undefined,
707
+ timestamp: Date.now(),
708
+ sessionId: this.sessionId,
709
+ url: typeof window !== 'undefined' ? window.location.href : '',
710
+ };
711
+ this.enqueue(event);
712
+ }
713
+ catch (error) {
714
+ // Silently fail
715
+ }
716
+ }
687
717
  destroy() {
688
718
  // Restore original functions
689
719
  if (this.originalFetch && typeof window !== 'undefined') {
@@ -0,0 +1,73 @@
1
+ /**
2
+ * React hooks for automatic state change tracking
3
+ *
4
+ * These hooks wrap React's useState and useReducer to automatically
5
+ * track state changes to the telemetry system.
6
+ */
7
+ import { Dispatch, SetStateAction, Reducer } from 'react';
8
+ import { LavarageTelemetry } from './index';
9
+ /**
10
+ * Configuration for tracked state
11
+ */
12
+ export interface TrackedStateConfig {
13
+ /** Name/identifier for this state (e.g., 'userPreferences', 'cartItems') */
14
+ stateName: string;
15
+ /** Telemetry instance to use for tracking */
16
+ telemetry: LavarageTelemetry;
17
+ /** Whether to track the initial state value (default: false) */
18
+ trackInitial?: boolean;
19
+ /** Action name to use when tracking (default: 'STATE_CHANGE') */
20
+ action?: string;
21
+ /** Additional metadata to include with each state change */
22
+ metadata?: object;
23
+ /** Function to determine if a state change should be tracked (useful for filtering) */
24
+ shouldTrack?: (previousValue: any, newValue: any) => boolean;
25
+ }
26
+ /**
27
+ * Hook that wraps useState and automatically tracks state changes
28
+ *
29
+ * @example
30
+ * ```tsx
31
+ * const [count, setCount] = useTrackedState(0, {
32
+ * stateName: 'counter',
33
+ * telemetry: telemetryInstance,
34
+ * action: 'SET_COUNT'
35
+ * });
36
+ * ```
37
+ */
38
+ export declare function useTrackedState<T>(initialState: T | (() => T), config: TrackedStateConfig): [T, Dispatch<SetStateAction<T>>];
39
+ /**
40
+ * Action type for tracked reducer
41
+ */
42
+ export interface TrackedReducerAction {
43
+ type: string;
44
+ payload?: any;
45
+ [key: string]: any;
46
+ }
47
+ /**
48
+ * Hook that wraps useReducer and automatically tracks state changes
49
+ *
50
+ * @example
51
+ * ```tsx
52
+ * const [state, dispatch] = useTrackedReducer(reducer, initialState, {
53
+ * stateName: 'cart',
54
+ * telemetry: telemetryInstance
55
+ * });
56
+ * ```
57
+ */
58
+ export declare function useTrackedReducer<TState, TAction extends TrackedReducerAction>(reducer: Reducer<TState, TAction>, initialState: TState, config: TrackedStateConfig): [TState, Dispatch<TAction>];
59
+ /**
60
+ * Hook to manually track state changes with more control
61
+ * Useful when you want to track state changes but don't want to use the tracked hooks
62
+ *
63
+ * @example
64
+ * ```tsx
65
+ * const trackState = useStateTracker(telemetryInstance);
66
+ *
67
+ * const handleChange = (newValue) => {
68
+ * trackState('userPreferences', currentValue, newValue, 'UPDATE_PREFERENCES');
69
+ * setCurrentValue(newValue);
70
+ * };
71
+ * ```
72
+ */
73
+ export declare function useStateTracker(telemetry: LavarageTelemetry): (stateName: string, previousValue?: any, newValue?: any, action?: string, metadata?: object) => void;
package/dist/react.js ADDED
@@ -0,0 +1,149 @@
1
+ "use strict";
2
+ /**
3
+ * React hooks for automatic state change tracking
4
+ *
5
+ * These hooks wrap React's useState and useReducer to automatically
6
+ * track state changes to the telemetry system.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.useTrackedState = useTrackedState;
10
+ exports.useTrackedReducer = useTrackedReducer;
11
+ exports.useStateTracker = useStateTracker;
12
+ const react_1 = require("react");
13
+ /**
14
+ * Hook that wraps useState and automatically tracks state changes
15
+ *
16
+ * @example
17
+ * ```tsx
18
+ * const [count, setCount] = useTrackedState(0, {
19
+ * stateName: 'counter',
20
+ * telemetry: telemetryInstance,
21
+ * action: 'SET_COUNT'
22
+ * });
23
+ * ```
24
+ */
25
+ function useTrackedState(initialState, config) {
26
+ const { stateName, telemetry, trackInitial = false, action, metadata, shouldTrack } = config;
27
+ const [state, setState] = (0, react_1.useState)(initialState);
28
+ const previousStateRef = (0, react_1.useRef)(state);
29
+ const isInitialMount = (0, react_1.useRef)(true);
30
+ // Track initial state if configured
31
+ (0, react_1.useEffect)(() => {
32
+ if (trackInitial && isInitialMount.current) {
33
+ isInitialMount.current = false;
34
+ telemetry.trackStateChange(stateName, undefined, state, action || 'INITIAL_STATE', metadata);
35
+ }
36
+ }, []);
37
+ // Track state changes
38
+ (0, react_1.useEffect)(() => {
39
+ // Skip tracking on initial mount unless trackInitial is true
40
+ if (isInitialMount.current && !trackInitial) {
41
+ isInitialMount.current = false;
42
+ previousStateRef.current = state;
43
+ return;
44
+ }
45
+ if (isInitialMount.current) {
46
+ isInitialMount.current = false;
47
+ previousStateRef.current = state;
48
+ return;
49
+ }
50
+ const previousValue = previousStateRef.current;
51
+ // Check if we should track this change
52
+ if (shouldTrack && !shouldTrack(previousValue, state)) {
53
+ previousStateRef.current = state;
54
+ return;
55
+ }
56
+ // Track the state change
57
+ telemetry.trackStateChange(stateName, previousValue, state, action, metadata);
58
+ previousStateRef.current = state;
59
+ }, [state, stateName, telemetry, action, metadata, shouldTrack]);
60
+ // Wrapper for setState that ensures tracking
61
+ const trackedSetState = (value) => {
62
+ setState(value);
63
+ };
64
+ return [state, trackedSetState];
65
+ }
66
+ /**
67
+ * Hook that wraps useReducer and automatically tracks state changes
68
+ *
69
+ * @example
70
+ * ```tsx
71
+ * const [state, dispatch] = useTrackedReducer(reducer, initialState, {
72
+ * stateName: 'cart',
73
+ * telemetry: telemetryInstance
74
+ * });
75
+ * ```
76
+ */
77
+ function useTrackedReducer(reducer, initialState, config) {
78
+ const { stateName, telemetry, trackInitial = false, metadata, shouldTrack } = config;
79
+ const [state, dispatch] = (0, react_1.useReducer)(reducer, initialState);
80
+ const previousStateRef = (0, react_1.useRef)(state);
81
+ const isInitialMount = (0, react_1.useRef)(true);
82
+ // Track initial state if configured
83
+ (0, react_1.useEffect)(() => {
84
+ if (trackInitial && isInitialMount.current) {
85
+ isInitialMount.current = false;
86
+ telemetry.trackStateChange(stateName, undefined, state, 'INITIAL_STATE', metadata);
87
+ }
88
+ }, []);
89
+ // Track state changes
90
+ (0, react_1.useEffect)(() => {
91
+ // Skip tracking on initial mount unless trackInitial is true
92
+ if (isInitialMount.current && !trackInitial) {
93
+ isInitialMount.current = false;
94
+ previousStateRef.current = state;
95
+ return;
96
+ }
97
+ if (isInitialMount.current) {
98
+ isInitialMount.current = false;
99
+ previousStateRef.current = state;
100
+ return;
101
+ }
102
+ const previousValue = previousStateRef.current;
103
+ // Check if we should track this change
104
+ if (shouldTrack && !shouldTrack(previousValue, state)) {
105
+ previousStateRef.current = state;
106
+ return;
107
+ }
108
+ // Track the state change (action type will be included in the reducer action)
109
+ telemetry.trackStateChange(stateName, previousValue, state, undefined, // Action will be determined by the reducer
110
+ metadata);
111
+ previousStateRef.current = state;
112
+ }, [state, stateName, telemetry, metadata, shouldTrack]);
113
+ // Wrapper for dispatch that tracks the action
114
+ const trackedDispatch = (action) => {
115
+ // Track before dispatching
116
+ const previousValue = previousStateRef.current;
117
+ dispatch(action);
118
+ // Note: The actual state change will be tracked in the useEffect above
119
+ // But we can also track the action here for immediate feedback
120
+ if (!shouldTrack || shouldTrack(previousValue, state)) {
121
+ telemetry.trackStateChange(stateName, previousValue, state, // This will be the old state, useEffect will track the new one
122
+ action.type || 'REDUCER_ACTION', {
123
+ ...metadata,
124
+ actionPayload: action.payload,
125
+ ...action
126
+ });
127
+ }
128
+ };
129
+ return [state, trackedDispatch];
130
+ }
131
+ /**
132
+ * Hook to manually track state changes with more control
133
+ * Useful when you want to track state changes but don't want to use the tracked hooks
134
+ *
135
+ * @example
136
+ * ```tsx
137
+ * const trackState = useStateTracker(telemetryInstance);
138
+ *
139
+ * const handleChange = (newValue) => {
140
+ * trackState('userPreferences', currentValue, newValue, 'UPDATE_PREFERENCES');
141
+ * setCurrentValue(newValue);
142
+ * };
143
+ * ```
144
+ */
145
+ function useStateTracker(telemetry) {
146
+ return (stateName, previousValue, newValue, action, metadata) => {
147
+ telemetry.trackStateChange(stateName, previousValue, newValue, action, metadata);
148
+ };
149
+ }
package/dist/types.d.ts CHANGED
@@ -17,7 +17,7 @@ export interface ErrorFilterConfig {
17
17
  exclude?: string[];
18
18
  }
19
19
  export interface TelemetryEvent {
20
- type: 'login' | 'pair_view' | 'error' | 'request' | 'system_event';
20
+ type: 'login' | 'pair_view' | 'error' | 'request' | 'system_event' | 'state_change';
21
21
  wallet: string | null;
22
22
  platform: string;
23
23
  timestamp: number;
@@ -53,3 +53,10 @@ export interface RequestEvent {
53
53
  export interface BatchIngestRequest {
54
54
  events: TelemetryEvent[];
55
55
  }
56
+ export interface StateChangeEvent {
57
+ stateName: string;
58
+ previousValue?: any;
59
+ newValue?: any;
60
+ action?: string;
61
+ metadata?: object;
62
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lavarage/telemetry",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "Production telemetry SDK for Lavarage and partner applications",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",
@@ -10,29 +10,56 @@
10
10
  "node-fetch": false
11
11
  },
12
12
  "scripts": {
13
- "build": "tsc",
13
+ "build": "tsc && tsc --project tsconfig.react.json",
14
+ "build:main": "tsc",
15
+ "build:react": "tsc --project tsconfig.react.json",
14
16
  "dev": "tsc --watch",
15
17
  "prepublishOnly": "npm run build",
16
18
  "test": "jest"
17
19
  },
18
- "keywords": ["telemetry", "analytics", "monitoring", "lavarage"],
20
+ "keywords": [
21
+ "telemetry",
22
+ "analytics",
23
+ "monitoring",
24
+ "lavarage"
25
+ ],
19
26
  "author": "Lavarage",
20
27
  "license": "MIT",
21
28
  "peerDependencies": {
22
- "node-fetch": "^2.6.0 || ^3.0.0"
29
+ "node-fetch": "^2.6.0 || ^3.0.0",
30
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
23
31
  },
24
32
  "peerDependenciesMeta": {
25
33
  "node-fetch": {
26
34
  "optional": true
35
+ },
36
+ "react": {
37
+ "optional": true
38
+ }
39
+ },
40
+ "exports": {
41
+ ".": {
42
+ "import": "./dist/index.js",
43
+ "require": "./dist/index.js",
44
+ "types": "./dist/index.d.ts"
45
+ },
46
+ "./react": {
47
+ "import": "./dist/react.js",
48
+ "require": "./dist/react.js",
49
+ "types": "./dist/react.d.ts"
27
50
  }
28
51
  },
29
52
  "devDependencies": {
53
+ "@types/jest": "^29.0.0",
30
54
  "@types/node": "^20.0.0",
31
- "typescript": "^5.0.0",
55
+ "@types/react": "^19.2.8",
56
+ "@types/react-dom": "^19.2.3",
32
57
  "jest": "^29.0.0",
33
- "@types/jest": "^29.0.0",
34
- "ts-jest": "^29.0.0"
58
+ "ts-jest": "^29.0.0",
59
+ "typescript": "^5.0.0"
35
60
  },
36
- "files": ["dist", "README.md"]
61
+ "files": [
62
+ "dist",
63
+ "README.md"
64
+ ]
37
65
  }
38
-