react-fathom 0.1.10 → 0.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.
Files changed (80) hide show
  1. package/README.md +941 -25
  2. package/dist/cjs/index.cjs +55 -9
  3. package/dist/cjs/index.cjs.map +1 -1
  4. package/dist/cjs/native/index.cjs +1079 -0
  5. package/dist/cjs/native/index.cjs.map +1 -0
  6. package/dist/cjs/next/index.cjs +89 -5
  7. package/dist/cjs/next/index.cjs.map +1 -1
  8. package/dist/es/index.js +55 -9
  9. package/dist/es/index.js.map +1 -1
  10. package/dist/es/native/index.js +1071 -0
  11. package/dist/es/native/index.js.map +1 -0
  12. package/dist/es/next/index.js +90 -6
  13. package/dist/es/next/index.js.map +1 -1
  14. package/dist/react-fathom.js +55 -9
  15. package/dist/react-fathom.js.map +1 -1
  16. package/dist/react-fathom.min.js +2 -2
  17. package/dist/react-fathom.min.js.map +1 -1
  18. package/package.json +28 -5
  19. package/src/FathomContext.tsx +30 -1
  20. package/src/FathomProvider.test.tsx +115 -15
  21. package/src/FathomProvider.tsx +10 -2
  22. package/src/components/TrackClick.test.tsx +7 -7
  23. package/src/components/TrackClick.tsx +1 -1
  24. package/src/components/TrackVisible.test.tsx +7 -7
  25. package/src/components/TrackVisible.tsx +1 -1
  26. package/src/hooks/useFathom.test.tsx +14 -3
  27. package/src/hooks/useTrackOnClick.test.tsx +4 -4
  28. package/src/hooks/useTrackOnClick.ts +1 -1
  29. package/src/hooks/useTrackOnVisible.test.tsx +4 -4
  30. package/src/hooks/useTrackOnVisible.ts +1 -1
  31. package/src/index.ts +1 -0
  32. package/src/native/FathomWebView.test.tsx +410 -0
  33. package/src/native/FathomWebView.tsx +297 -0
  34. package/src/native/NativeFathomProvider.test.tsx +372 -0
  35. package/src/native/NativeFathomProvider.tsx +113 -0
  36. package/src/native/createWebViewClient.test.ts +380 -0
  37. package/src/native/createWebViewClient.ts +271 -0
  38. package/src/native/index.ts +29 -0
  39. package/src/native/react-native.d.ts +74 -0
  40. package/src/native/types.ts +145 -0
  41. package/src/native/useAppStateTracking.test.ts +249 -0
  42. package/src/native/useAppStateTracking.ts +66 -0
  43. package/src/native/useNavigationTracking.test.ts +446 -0
  44. package/src/native/useNavigationTracking.ts +177 -0
  45. package/src/next/NextFathomProviderApp.client.tsx +5 -0
  46. package/src/next/NextFathomProviderApp.test.tsx +154 -0
  47. package/src/next/NextFathomProviderApp.tsx +62 -0
  48. package/src/next/index.ts +3 -0
  49. package/src/types.ts +36 -9
  50. package/types/FathomContext.d.ts +1 -1
  51. package/types/FathomContext.d.ts.map +1 -1
  52. package/types/FathomProvider.d.ts.map +1 -1
  53. package/types/components/TrackClick.d.ts +1 -1
  54. package/types/components/TrackVisible.d.ts +1 -1
  55. package/types/hooks/useTrackOnClick.d.ts +1 -1
  56. package/types/hooks/useTrackOnVisible.d.ts +1 -1
  57. package/types/index.d.ts +1 -0
  58. package/types/index.d.ts.map +1 -1
  59. package/types/native/FathomWebView.d.ts +59 -0
  60. package/types/native/FathomWebView.d.ts.map +1 -0
  61. package/types/native/NativeFathomProvider.d.ts +36 -0
  62. package/types/native/NativeFathomProvider.d.ts.map +1 -0
  63. package/types/native/createWebViewClient.d.ts +51 -0
  64. package/types/native/createWebViewClient.d.ts.map +1 -0
  65. package/types/native/index.d.ts +10 -0
  66. package/types/native/index.d.ts.map +1 -0
  67. package/types/native/types.d.ts +125 -0
  68. package/types/native/types.d.ts.map +1 -0
  69. package/types/native/useAppStateTracking.d.ts +25 -0
  70. package/types/native/useAppStateTracking.d.ts.map +1 -0
  71. package/types/native/useNavigationTracking.d.ts +30 -0
  72. package/types/native/useNavigationTracking.d.ts.map +1 -0
  73. package/types/next/NextFathomProviderApp.client.d.ts +3 -0
  74. package/types/next/NextFathomProviderApp.client.d.ts.map +1 -0
  75. package/types/next/NextFathomProviderApp.d.ts +38 -4
  76. package/types/next/NextFathomProviderApp.d.ts.map +1 -1
  77. package/types/next/index.d.ts +1 -0
  78. package/types/next/index.d.ts.map +1 -1
  79. package/types/types.d.ts +34 -9
  80. package/types/types.d.ts.map +1 -1
@@ -1,4 +1,4 @@
1
- import React from 'react'
1
+ import React, { useRef } from 'react'
2
2
 
3
3
  import { beforeEach, describe, expect, it, vi } from 'vitest'
4
4
 
@@ -6,6 +6,7 @@ import { renderHook, waitFor } from '@testing-library/react'
6
6
 
7
7
  import { FathomProvider } from './FathomProvider'
8
8
  import { useFathom } from './hooks/useFathom'
9
+ import type { FathomClient } from './types'
9
10
 
10
11
  // Mock fathom-client
11
12
  vi.mock('fathom-client', () => {
@@ -139,10 +140,10 @@ describe('FathomProvider', () => {
139
140
 
140
141
  const { result } = renderHook(() => useFathom(), { wrapper })
141
142
 
142
- result.current.trackEvent?.('test-event', { id: 'test-id' })
143
+ result.current.trackEvent?.('test-event', { _site_id: 'test-id' })
143
144
 
144
145
  expect(mockClient.trackEvent).toHaveBeenCalledWith('test-event', {
145
- id: 'test-id',
146
+ _site_id: 'test-id',
146
147
  })
147
148
  })
148
149
 
@@ -161,7 +162,7 @@ describe('FathomProvider', () => {
161
162
  const wrapper = ({ children }: { children: React.ReactNode }) => (
162
163
  <FathomProvider
163
164
  client={mockClient}
164
- defaultEventOptions={{ id: 'default-id' }}
165
+ defaultEventOptions={{ _site_id: 'default-id' }}
165
166
  >
166
167
  {children}
167
168
  </FathomProvider>
@@ -169,11 +170,11 @@ describe('FathomProvider', () => {
169
170
 
170
171
  const { result } = renderHook(() => useFathom(), { wrapper })
171
172
 
172
- result.current.trackEvent?.('test-event', { value: 100 })
173
+ result.current.trackEvent?.('test-event', { _value: 100 })
173
174
 
174
175
  expect(mockClient.trackEvent).toHaveBeenCalledWith('test-event', {
175
- id: 'default-id',
176
- value: 100,
176
+ _site_id: 'default-id',
177
+ _value: 100,
177
178
  })
178
179
  })
179
180
 
@@ -192,7 +193,7 @@ describe('FathomProvider', () => {
192
193
  const wrapper = ({ children }: { children: React.ReactNode }) => (
193
194
  <FathomProvider
194
195
  client={mockClient}
195
- defaultEventOptions={{ id: 'default-id' }}
196
+ defaultEventOptions={{ _site_id: 'default-id' }}
196
197
  >
197
198
  {children}
198
199
  </FathomProvider>
@@ -200,10 +201,10 @@ describe('FathomProvider', () => {
200
201
 
201
202
  const { result } = renderHook(() => useFathom(), { wrapper })
202
203
 
203
- result.current.trackEvent?.('test-event', { id: 'override-id' })
204
+ result.current.trackEvent?.('test-event', { _site_id: 'override-id' })
204
205
 
205
206
  expect(mockClient.trackEvent).toHaveBeenCalledWith('test-event', {
206
- id: 'override-id',
207
+ _site_id: 'override-id',
207
208
  })
208
209
  })
209
210
 
@@ -479,7 +480,7 @@ describe('FathomProvider', () => {
479
480
  const wrapper = ({ children }: { children: React.ReactNode }) => (
480
481
  <FathomProvider
481
482
  client={mockClient}
482
- defaultEventOptions={{ id: 'parent-id' }}
483
+ defaultEventOptions={{ _site_id: 'parent-id' }}
483
484
  >
484
485
  <FathomProvider>{children}</FathomProvider>
485
486
  </FathomProvider>
@@ -490,7 +491,7 @@ describe('FathomProvider', () => {
490
491
  result.current.trackEvent?.('test-event')
491
492
 
492
493
  expect(mockClient.trackEvent).toHaveBeenCalledWith('test-event', {
493
- id: 'parent-id',
494
+ _site_id: 'parent-id',
494
495
  })
495
496
  })
496
497
 
@@ -509,9 +510,9 @@ describe('FathomProvider', () => {
509
510
  const wrapper = ({ children }: { children: React.ReactNode }) => (
510
511
  <FathomProvider
511
512
  client={mockClient}
512
- defaultEventOptions={{ id: 'parent-id' }}
513
+ defaultEventOptions={{ _site_id: 'parent-id' }}
513
514
  >
514
- <FathomProvider defaultEventOptions={{ id: 'child-id' }}>
515
+ <FathomProvider defaultEventOptions={{ _site_id: 'child-id' }}>
515
516
  {children}
516
517
  </FathomProvider>
517
518
  </FathomProvider>
@@ -522,11 +523,110 @@ describe('FathomProvider', () => {
522
523
  result.current.trackEvent?.('test-event')
523
524
 
524
525
  expect(mockClient.trackEvent).toHaveBeenCalledWith('test-event', {
525
- id: 'child-id',
526
+ _site_id: 'child-id',
526
527
  })
527
528
  })
528
529
 
529
530
  it('should have displayName', () => {
530
531
  expect(FathomProvider.displayName).toBe('FathomProvider')
531
532
  })
533
+
534
+ describe('clientRef', () => {
535
+ it('should populate clientRef with the resolved client', () => {
536
+ const mockClient = {
537
+ trackEvent: vi.fn(),
538
+ trackPageview: vi.fn(),
539
+ trackGoal: vi.fn(),
540
+ load: vi.fn(),
541
+ setSite: vi.fn(),
542
+ blockTrackingForMe: vi.fn(),
543
+ enableTrackingForMe: vi.fn(),
544
+ isTrackingEnabled: vi.fn(() => true),
545
+ }
546
+
547
+ let clientRefValue: FathomClient | null = null
548
+
549
+ const TestWrapper = ({ children }: { children: React.ReactNode }) => {
550
+ const clientRef = useRef<FathomClient>(null)
551
+ // Capture the ref value after render
552
+ React.useEffect(() => {
553
+ clientRefValue = clientRef.current
554
+ })
555
+ return (
556
+ <FathomProvider client={mockClient} clientRef={clientRef}>
557
+ {children}
558
+ </FathomProvider>
559
+ )
560
+ }
561
+
562
+ renderHook(() => useFathom(), { wrapper: TestWrapper })
563
+
564
+ expect(clientRefValue).toBe(mockClient)
565
+ })
566
+
567
+ it('should allow parent to call client methods via clientRef', () => {
568
+ const mockClient = {
569
+ trackEvent: vi.fn(),
570
+ trackPageview: vi.fn(),
571
+ trackGoal: vi.fn(),
572
+ load: vi.fn(),
573
+ setSite: vi.fn(),
574
+ blockTrackingForMe: vi.fn(),
575
+ enableTrackingForMe: vi.fn(),
576
+ isTrackingEnabled: vi.fn(() => true),
577
+ }
578
+
579
+ const clientRef = React.createRef<FathomClient>() as React.MutableRefObject<FathomClient | null>
580
+ clientRef.current = null
581
+
582
+ const wrapper = ({ children }: { children: React.ReactNode }) => (
583
+ <FathomProvider client={mockClient} clientRef={clientRef}>
584
+ {children}
585
+ </FathomProvider>
586
+ )
587
+
588
+ renderHook(() => useFathom(), { wrapper })
589
+
590
+ // Parent can now use the client directly
591
+ clientRef.current?.trackEvent('parent-event', { _value: 50 })
592
+
593
+ expect(mockClient.trackEvent).toHaveBeenCalledWith('parent-event', {
594
+ _value: 50,
595
+ })
596
+ })
597
+
598
+ it('should populate clientRef with inherited parent client', () => {
599
+ const parentClient = {
600
+ trackEvent: vi.fn(),
601
+ trackPageview: vi.fn(),
602
+ trackGoal: vi.fn(),
603
+ load: vi.fn(),
604
+ setSite: vi.fn(),
605
+ blockTrackingForMe: vi.fn(),
606
+ enableTrackingForMe: vi.fn(),
607
+ isTrackingEnabled: vi.fn(() => true),
608
+ }
609
+
610
+ let clientRefValue: FathomClient | null = null
611
+
612
+ const TestWrapper = ({ children }: { children: React.ReactNode }) => {
613
+ const clientRef = useRef<FathomClient>(null)
614
+ React.useEffect(() => {
615
+ clientRefValue = clientRef.current
616
+ })
617
+ return (
618
+ <FathomProvider client={parentClient}>
619
+ <FathomProvider clientRef={clientRef}>
620
+ {children}
621
+ </FathomProvider>
622
+ </FathomProvider>
623
+ )
624
+ }
625
+
626
+ renderHook(() => useFathom(), { wrapper: TestWrapper })
627
+
628
+ // Should inherit the parent client
629
+ expect(clientRefValue).toBe(parentClient)
630
+ })
631
+ })
532
632
  })
@@ -11,6 +11,7 @@ import type { FathomProviderProps } from './types'
11
11
  const FathomProvider: React.FC<FathomProviderProps> = ({
12
12
  children,
13
13
  client: providedClient,
14
+ clientRef,
14
15
  clientOptions,
15
16
  siteId,
16
17
  defaultPageviewOptions: providedDefaultPageviewOptions,
@@ -65,8 +66,8 @@ const FathomProvider: React.FC<FathomProviderProps> = ({
65
66
  )
66
67
 
67
68
  const trackEvent = useCallback(
68
- (category: string, options?: EventOptions) => {
69
- client.trackEvent(category, {
69
+ (eventName: string, options?: EventOptions) => {
70
+ client.trackEvent(eventName, {
70
71
  ...defaultEventOptions,
71
72
  ...options,
72
73
  })
@@ -97,6 +98,13 @@ const FathomProvider: React.FC<FathomProviderProps> = ({
97
98
  }
98
99
  }, [clientOptions, load, siteId])
99
100
 
101
+ // Populate the clientRef so the parent component can access the client
102
+ useEffect(() => {
103
+ if (clientRef) {
104
+ clientRef.current = client
105
+ }
106
+ }, [client, clientRef])
107
+
100
108
  return (
101
109
  <FathomContext.Provider
102
110
  value={{
@@ -55,7 +55,7 @@ describe('TrackClick', () => {
55
55
 
56
56
  it('should track event with options', () => {
57
57
  render(
58
- <TrackClick eventName="test-event" id="test-id" value={100}>
58
+ <TrackClick eventName="test-event" _site_id="test-site" _value={100}>
59
59
  <button>Click me</button>
60
60
  </TrackClick>,
61
61
  { wrapper },
@@ -64,8 +64,8 @@ describe('TrackClick', () => {
64
64
  fireEvent.click(screen.getByText('Click me'))
65
65
 
66
66
  expect(mockTrackEvent).toHaveBeenCalledWith('test-event', {
67
- id: 'test-id',
68
- value: 100,
67
+ _site_id: 'test-site',
68
+ _value: 100,
69
69
  })
70
70
  })
71
71
 
@@ -168,14 +168,14 @@ describe('TrackClick', () => {
168
168
  const customWrapper = ({ children }: { children: React.ReactNode }) => (
169
169
  <FathomProvider
170
170
  client={mockClient}
171
- defaultEventOptions={{ id: 'default-id' }}
171
+ defaultEventOptions={{ _site_id: 'default-site' }}
172
172
  >
173
173
  {children}
174
174
  </FathomProvider>
175
175
  )
176
176
 
177
177
  render(
178
- <TrackClick eventName="test-event" value={100}>
178
+ <TrackClick eventName="test-event" _value={100}>
179
179
  <button>Click me</button>
180
180
  </TrackClick>,
181
181
  { wrapper: customWrapper },
@@ -184,8 +184,8 @@ describe('TrackClick', () => {
184
184
  fireEvent.click(screen.getByText('Click me'))
185
185
 
186
186
  expect(mockTrackEvent).toHaveBeenCalledWith('test-event', {
187
- id: 'default-id',
188
- value: 100,
187
+ _site_id: 'default-site',
188
+ _value: 100,
189
189
  })
190
190
  })
191
191
  })
@@ -35,7 +35,7 @@ export interface TrackClickProps extends EventOptions {
35
35
  *
36
36
  * @example
37
37
  * ```tsx
38
- * <TrackClick eventName="cta-clicked" id="hero-cta">
38
+ * <TrackClick eventName="cta-clicked" _value={100}>
39
39
  * <button>Get Started</button>
40
40
  * </TrackClick>
41
41
  * ```
@@ -99,7 +99,7 @@ describe('TrackVisible', () => {
99
99
 
100
100
  it('should track event with options', async () => {
101
101
  render(
102
- <TrackVisible eventName="test-event" id="test-id" value={100}>
102
+ <TrackVisible eventName="test-event" _site_id="test-site" _value={100}>
103
103
  <div>Content</div>
104
104
  </TrackVisible>,
105
105
  { wrapper },
@@ -120,8 +120,8 @@ describe('TrackVisible', () => {
120
120
 
121
121
  await waitFor(() => {
122
122
  expect(mockTrackEvent).toHaveBeenCalledWith('test-event', {
123
- id: 'test-id',
124
- value: 100,
123
+ _site_id: 'test-site',
124
+ _value: 100,
125
125
  })
126
126
  })
127
127
  })
@@ -275,14 +275,14 @@ describe('TrackVisible', () => {
275
275
  const customWrapper = ({ children }: { children: React.ReactNode }) => (
276
276
  <FathomProvider
277
277
  client={mockClient}
278
- defaultEventOptions={{ id: 'default-id' }}
278
+ defaultEventOptions={{ _site_id: 'default-site' }}
279
279
  >
280
280
  {children}
281
281
  </FathomProvider>
282
282
  )
283
283
 
284
284
  render(
285
- <TrackVisible eventName="test-event" value={100}>
285
+ <TrackVisible eventName="test-event" _value={100}>
286
286
  <div>Content</div>
287
287
  </TrackVisible>,
288
288
  { wrapper: customWrapper },
@@ -303,8 +303,8 @@ describe('TrackVisible', () => {
303
303
 
304
304
  await waitFor(() => {
305
305
  expect(mockTrackEvent).toHaveBeenCalledWith('test-event', {
306
- id: 'default-id',
307
- value: 100,
306
+ _site_id: 'default-site',
307
+ _value: 100,
308
308
  })
309
309
  })
310
310
  })
@@ -35,7 +35,7 @@ export interface TrackVisibleProps extends EventOptions {
35
35
  *
36
36
  * @example
37
37
  * ```tsx
38
- * <TrackVisible eventName="section-viewed" section="hero">
38
+ * <TrackVisible eventName="section-viewed" _value={1}>
39
39
  * <HeroSection />
40
40
  * </TrackVisible>
41
41
  * ```
@@ -37,12 +37,23 @@ describe('useFathom', () => {
37
37
  expect(result.current.isTrackingEnabled).toBeDefined()
38
38
  })
39
39
 
40
- it('should return empty context when used outside FathomProvider', () => {
40
+ it('should return stub methods when used outside FathomProvider', () => {
41
+ const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
42
+
41
43
  const { result } = renderHook(() => useFathom())
42
44
 
45
+ // Client should be undefined, but methods should be stub functions
43
46
  expect(result.current.client).toBeUndefined()
44
- expect(result.current.trackEvent).toBeUndefined()
45
- expect(result.current.trackPageview).toBeUndefined()
47
+ expect(typeof result.current.trackEvent).toBe('function')
48
+ expect(typeof result.current.trackPageview).toBe('function')
49
+
50
+ // Calling stub methods should warn in development
51
+ result.current.trackEvent('test')
52
+ expect(consoleSpy).toHaveBeenCalledWith(
53
+ '[react-fathom] trackEvent() called without a FathomProvider. Wrap your app with <FathomProvider> to enable analytics tracking.',
54
+ )
55
+
56
+ consoleSpy.mockRestore()
46
57
  })
47
58
 
48
59
  it('should have displayName', () => {
@@ -54,8 +54,8 @@ describe('useTrackOnClick', () => {
54
54
  () =>
55
55
  useTrackOnClick({
56
56
  eventName: 'test-event',
57
- id: 'test-id',
58
- value: 100,
57
+ _site_id: 'test-site',
58
+ _value: 100,
59
59
  }),
60
60
  { wrapper },
61
61
  )
@@ -63,8 +63,8 @@ describe('useTrackOnClick', () => {
63
63
  result.current()
64
64
 
65
65
  expect(mockTrackEvent).toHaveBeenCalledWith('test-event', {
66
- id: 'test-id',
67
- value: 100,
66
+ _site_id: 'test-site',
67
+ _value: 100,
68
68
  })
69
69
  })
70
70
 
@@ -30,7 +30,7 @@ export interface UseTrackOnClickOptions extends EventOptions {
30
30
  * function Button() {
31
31
  * const handleClick = useTrackOnClick({
32
32
  * eventName: 'button-click',
33
- * id: 'signup-button',
33
+ * _value: 100, // Optional: value in cents
34
34
  * callback: (e) => {
35
35
  * console.log('Button clicked!')
36
36
  * // Your custom logic here
@@ -110,8 +110,8 @@ describe('useTrackOnVisible', () => {
110
110
  const TestComponent = () => {
111
111
  const ref = useTrackOnVisible({
112
112
  eventName: 'test-event',
113
- id: 'test-id',
114
- value: 100,
113
+ _site_id: 'test-site',
114
+ _value: 100,
115
115
  })
116
116
  return <div ref={ref}>Test</div>
117
117
  }
@@ -137,8 +137,8 @@ describe('useTrackOnVisible', () => {
137
137
 
138
138
  await waitFor(() => {
139
139
  expect(mockTrackEvent).toHaveBeenCalledWith('test-event', {
140
- id: 'test-id',
141
- value: 100,
140
+ _site_id: 'test-site',
141
+ _value: 100,
142
142
  })
143
143
  })
144
144
  })
@@ -34,7 +34,7 @@ export interface UseTrackOnVisibleOptions extends EventOptions {
34
34
  * function Section() {
35
35
  * const ref = useTrackOnVisible({
36
36
  * eventName: 'section-viewed',
37
- * section: 'hero',
37
+ * _value: 1, // Optional: value in cents
38
38
  * callback: (entry) => {
39
39
  * console.log('Section is visible!', entry.isIntersecting)
40
40
  * // Your custom logic here
package/src/index.ts CHANGED
@@ -2,3 +2,4 @@ export * from './FathomContext'
2
2
  export * from './FathomProvider'
3
3
  export * from './hooks'
4
4
  export * from './components'
5
+ export * from './types'