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.
- package/README.md +941 -25
- package/dist/cjs/index.cjs +55 -9
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/native/index.cjs +1079 -0
- package/dist/cjs/native/index.cjs.map +1 -0
- package/dist/cjs/next/index.cjs +89 -5
- package/dist/cjs/next/index.cjs.map +1 -1
- package/dist/es/index.js +55 -9
- package/dist/es/index.js.map +1 -1
- package/dist/es/native/index.js +1071 -0
- package/dist/es/native/index.js.map +1 -0
- package/dist/es/next/index.js +90 -6
- package/dist/es/next/index.js.map +1 -1
- package/dist/react-fathom.js +55 -9
- package/dist/react-fathom.js.map +1 -1
- package/dist/react-fathom.min.js +2 -2
- package/dist/react-fathom.min.js.map +1 -1
- package/package.json +28 -5
- package/src/FathomContext.tsx +30 -1
- package/src/FathomProvider.test.tsx +115 -15
- package/src/FathomProvider.tsx +10 -2
- package/src/components/TrackClick.test.tsx +7 -7
- package/src/components/TrackClick.tsx +1 -1
- package/src/components/TrackVisible.test.tsx +7 -7
- package/src/components/TrackVisible.tsx +1 -1
- package/src/hooks/useFathom.test.tsx +14 -3
- package/src/hooks/useTrackOnClick.test.tsx +4 -4
- package/src/hooks/useTrackOnClick.ts +1 -1
- package/src/hooks/useTrackOnVisible.test.tsx +4 -4
- package/src/hooks/useTrackOnVisible.ts +1 -1
- package/src/index.ts +1 -0
- package/src/native/FathomWebView.test.tsx +410 -0
- package/src/native/FathomWebView.tsx +297 -0
- package/src/native/NativeFathomProvider.test.tsx +372 -0
- package/src/native/NativeFathomProvider.tsx +113 -0
- package/src/native/createWebViewClient.test.ts +380 -0
- package/src/native/createWebViewClient.ts +271 -0
- package/src/native/index.ts +29 -0
- package/src/native/react-native.d.ts +74 -0
- package/src/native/types.ts +145 -0
- package/src/native/useAppStateTracking.test.ts +249 -0
- package/src/native/useAppStateTracking.ts +66 -0
- package/src/native/useNavigationTracking.test.ts +446 -0
- package/src/native/useNavigationTracking.ts +177 -0
- package/src/next/NextFathomProviderApp.client.tsx +5 -0
- package/src/next/NextFathomProviderApp.test.tsx +154 -0
- package/src/next/NextFathomProviderApp.tsx +62 -0
- package/src/next/index.ts +3 -0
- package/src/types.ts +36 -9
- package/types/FathomContext.d.ts +1 -1
- package/types/FathomContext.d.ts.map +1 -1
- package/types/FathomProvider.d.ts.map +1 -1
- package/types/components/TrackClick.d.ts +1 -1
- package/types/components/TrackVisible.d.ts +1 -1
- package/types/hooks/useTrackOnClick.d.ts +1 -1
- package/types/hooks/useTrackOnVisible.d.ts +1 -1
- package/types/index.d.ts +1 -0
- package/types/index.d.ts.map +1 -1
- package/types/native/FathomWebView.d.ts +59 -0
- package/types/native/FathomWebView.d.ts.map +1 -0
- package/types/native/NativeFathomProvider.d.ts +36 -0
- package/types/native/NativeFathomProvider.d.ts.map +1 -0
- package/types/native/createWebViewClient.d.ts +51 -0
- package/types/native/createWebViewClient.d.ts.map +1 -0
- package/types/native/index.d.ts +10 -0
- package/types/native/index.d.ts.map +1 -0
- package/types/native/types.d.ts +125 -0
- package/types/native/types.d.ts.map +1 -0
- package/types/native/useAppStateTracking.d.ts +25 -0
- package/types/native/useAppStateTracking.d.ts.map +1 -0
- package/types/native/useNavigationTracking.d.ts +30 -0
- package/types/native/useNavigationTracking.d.ts.map +1 -0
- package/types/next/NextFathomProviderApp.client.d.ts +3 -0
- package/types/next/NextFathomProviderApp.client.d.ts.map +1 -0
- package/types/next/NextFathomProviderApp.d.ts +38 -4
- package/types/next/NextFathomProviderApp.d.ts.map +1 -1
- package/types/next/index.d.ts +1 -0
- package/types/next/index.d.ts.map +1 -1
- package/types/types.d.ts +34 -9
- package/types/types.d.ts.map +1 -1
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
import React, { useRef } from 'react'
|
|
2
|
+
|
|
3
|
+
import { describe, expect, it, vi, beforeEach } from 'vitest'
|
|
4
|
+
import { render, screen, waitFor } from '@testing-library/react'
|
|
5
|
+
|
|
6
|
+
import { useFathom } from '../hooks/useFathom'
|
|
7
|
+
import { NativeFathomProvider } from './NativeFathomProvider'
|
|
8
|
+
import type { WebViewFathomClient } from './createWebViewClient'
|
|
9
|
+
|
|
10
|
+
// Mock the FathomWebView component
|
|
11
|
+
vi.mock('./FathomWebView', () => ({
|
|
12
|
+
FathomWebView: vi.fn(({ onReady, onError, siteId, debug }) => {
|
|
13
|
+
// Store the callbacks for testing
|
|
14
|
+
;(global as any).__fathomWebViewProps = { onReady, onError, siteId, debug }
|
|
15
|
+
return null
|
|
16
|
+
}),
|
|
17
|
+
}))
|
|
18
|
+
|
|
19
|
+
// Mock react-native AppState for useAppStateTracking
|
|
20
|
+
vi.mock('react-native', () => ({
|
|
21
|
+
AppState: {
|
|
22
|
+
currentState: 'active',
|
|
23
|
+
addEventListener: vi.fn(() => ({ remove: vi.fn() })),
|
|
24
|
+
},
|
|
25
|
+
StyleSheet: {
|
|
26
|
+
create: vi.fn((styles) => styles),
|
|
27
|
+
},
|
|
28
|
+
View: vi.fn(({ children }) => children),
|
|
29
|
+
}))
|
|
30
|
+
|
|
31
|
+
describe('NativeFathomProvider', () => {
|
|
32
|
+
beforeEach(() => {
|
|
33
|
+
vi.clearAllMocks()
|
|
34
|
+
;(global as any).__fathomWebViewProps = null
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
it('should render children', () => {
|
|
38
|
+
render(
|
|
39
|
+
<NativeFathomProvider siteId="TEST_SITE">
|
|
40
|
+
<div data-testid="child">Child content</div>
|
|
41
|
+
</NativeFathomProvider>,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
expect(screen.getByTestId('child')).toBeDefined()
|
|
45
|
+
expect(screen.getByText('Child content')).toBeDefined()
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
it('should pass siteId to FathomWebView', async () => {
|
|
49
|
+
const { FathomWebView } = await import('./FathomWebView')
|
|
50
|
+
|
|
51
|
+
render(
|
|
52
|
+
<NativeFathomProvider siteId="MY_SITE_ID">
|
|
53
|
+
<div>Test</div>
|
|
54
|
+
</NativeFathomProvider>,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
expect(FathomWebView).toHaveBeenCalled()
|
|
58
|
+
const callArgs = (FathomWebView as any).mock.calls[0][0]
|
|
59
|
+
expect(callArgs.siteId).toBe('MY_SITE_ID')
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
it('should pass debug prop to FathomWebView', async () => {
|
|
63
|
+
const { FathomWebView } = await import('./FathomWebView')
|
|
64
|
+
|
|
65
|
+
render(
|
|
66
|
+
<NativeFathomProvider siteId="TEST_SITE" debug={true}>
|
|
67
|
+
<div>Test</div>
|
|
68
|
+
</NativeFathomProvider>,
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
expect(FathomWebView).toHaveBeenCalled()
|
|
72
|
+
const callArgs = (FathomWebView as any).mock.calls[0][0]
|
|
73
|
+
expect(callArgs.debug).toBe(true)
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
it('should pass scriptDomain to FathomWebView', async () => {
|
|
77
|
+
const { FathomWebView } = await import('./FathomWebView')
|
|
78
|
+
|
|
79
|
+
render(
|
|
80
|
+
<NativeFathomProvider siteId="TEST_SITE" scriptDomain="custom.domain.com">
|
|
81
|
+
<div>Test</div>
|
|
82
|
+
</NativeFathomProvider>,
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
expect(FathomWebView).toHaveBeenCalled()
|
|
86
|
+
const callArgs = (FathomWebView as any).mock.calls[0][0]
|
|
87
|
+
expect(callArgs.scriptDomain).toBe('custom.domain.com')
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
it('should pass loadOptions to FathomWebView', async () => {
|
|
91
|
+
const { FathomWebView } = await import('./FathomWebView')
|
|
92
|
+
const loadOptions = { honorDNT: true, auto: false }
|
|
93
|
+
|
|
94
|
+
render(
|
|
95
|
+
<NativeFathomProvider siteId="TEST_SITE" loadOptions={loadOptions}>
|
|
96
|
+
<div>Test</div>
|
|
97
|
+
</NativeFathomProvider>,
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
expect(FathomWebView).toHaveBeenCalled()
|
|
101
|
+
const callArgs = (FathomWebView as any).mock.calls[0][0]
|
|
102
|
+
expect(callArgs.loadOptions).toEqual(loadOptions)
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
it('should call onReady when FathomWebView is ready', async () => {
|
|
106
|
+
const onReady = vi.fn()
|
|
107
|
+
|
|
108
|
+
render(
|
|
109
|
+
<NativeFathomProvider siteId="TEST_SITE" onReady={onReady}>
|
|
110
|
+
<div>Test</div>
|
|
111
|
+
</NativeFathomProvider>,
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
// Simulate WebView ready
|
|
115
|
+
const props = (global as any).__fathomWebViewProps
|
|
116
|
+
props?.onReady?.()
|
|
117
|
+
|
|
118
|
+
expect(onReady).toHaveBeenCalled()
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
it('should call onError when FathomWebView has an error', async () => {
|
|
122
|
+
const onError = vi.fn()
|
|
123
|
+
|
|
124
|
+
render(
|
|
125
|
+
<NativeFathomProvider siteId="TEST_SITE" onError={onError}>
|
|
126
|
+
<div>Test</div>
|
|
127
|
+
</NativeFathomProvider>,
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
// Simulate WebView error
|
|
131
|
+
const props = (global as any).__fathomWebViewProps
|
|
132
|
+
props?.onError?.('Test error')
|
|
133
|
+
|
|
134
|
+
expect(onError).toHaveBeenCalledWith('Test error')
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
it('should provide Fathom context to children', () => {
|
|
138
|
+
const TestChild = () => {
|
|
139
|
+
const fathom = useFathom()
|
|
140
|
+
return (
|
|
141
|
+
<div data-testid="has-context">
|
|
142
|
+
{fathom.trackEvent ? 'has trackEvent' : 'no trackEvent'}
|
|
143
|
+
</div>
|
|
144
|
+
)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
render(
|
|
148
|
+
<NativeFathomProvider siteId="TEST_SITE">
|
|
149
|
+
<TestChild />
|
|
150
|
+
</NativeFathomProvider>,
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
expect(screen.getByText('has trackEvent')).toBeDefined()
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
it('should pass defaultPageviewOptions to FathomProvider', () => {
|
|
157
|
+
const TestChild = () => {
|
|
158
|
+
const { defaultPageviewOptions } = useFathom()
|
|
159
|
+
return (
|
|
160
|
+
<div data-testid="default-options">
|
|
161
|
+
{defaultPageviewOptions?.referrer || 'no referrer'}
|
|
162
|
+
</div>
|
|
163
|
+
)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
render(
|
|
167
|
+
<NativeFathomProvider
|
|
168
|
+
siteId="TEST_SITE"
|
|
169
|
+
defaultPageviewOptions={{ referrer: 'https://example.com' }}
|
|
170
|
+
>
|
|
171
|
+
<TestChild />
|
|
172
|
+
</NativeFathomProvider>,
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
expect(screen.getByText('https://example.com')).toBeDefined()
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
it('should pass defaultEventOptions to FathomProvider', () => {
|
|
179
|
+
const TestChild = () => {
|
|
180
|
+
const { defaultEventOptions } = useFathom()
|
|
181
|
+
return (
|
|
182
|
+
<div data-testid="default-options">
|
|
183
|
+
{(defaultEventOptions as any)?._site_id || 'no site id'}
|
|
184
|
+
</div>
|
|
185
|
+
)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
render(
|
|
189
|
+
<NativeFathomProvider
|
|
190
|
+
siteId="TEST_SITE"
|
|
191
|
+
defaultEventOptions={{ _site_id: 'my-app' } as any}
|
|
192
|
+
>
|
|
193
|
+
<TestChild />
|
|
194
|
+
</NativeFathomProvider>,
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
expect(screen.getByText('my-app')).toBeDefined()
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
describe('trackAppState prop', () => {
|
|
201
|
+
it('should not render AppStateTracker when trackAppState is false', async () => {
|
|
202
|
+
const reactNative = await import('react-native')
|
|
203
|
+
|
|
204
|
+
render(
|
|
205
|
+
<NativeFathomProvider siteId="TEST_SITE" trackAppState={false}>
|
|
206
|
+
<div>Test</div>
|
|
207
|
+
</NativeFathomProvider>,
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
// AppState.addEventListener should not be called for app state tracking
|
|
211
|
+
// (It might be called once for other reasons, so we just verify it works)
|
|
212
|
+
expect(screen.getByText('Test')).toBeDefined()
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
it('should render AppStateTracker when trackAppState is true', async () => {
|
|
216
|
+
const reactNative = await import('react-native')
|
|
217
|
+
|
|
218
|
+
render(
|
|
219
|
+
<NativeFathomProvider siteId="TEST_SITE" trackAppState={true}>
|
|
220
|
+
<div>Test</div>
|
|
221
|
+
</NativeFathomProvider>,
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
// AppState.addEventListener should be called for app state tracking
|
|
225
|
+
expect(reactNative.AppState.addEventListener).toHaveBeenCalledWith(
|
|
226
|
+
'change',
|
|
227
|
+
expect.any(Function),
|
|
228
|
+
)
|
|
229
|
+
})
|
|
230
|
+
})
|
|
231
|
+
|
|
232
|
+
describe('client methods', () => {
|
|
233
|
+
it('should provide a working trackEvent method', () => {
|
|
234
|
+
const trackEventCalls: any[] = []
|
|
235
|
+
|
|
236
|
+
const TestChild = () => {
|
|
237
|
+
const { trackEvent } = useFathom()
|
|
238
|
+
React.useEffect(() => {
|
|
239
|
+
trackEvent('test-event', { _value: 100 })
|
|
240
|
+
}, [trackEvent])
|
|
241
|
+
return null
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
render(
|
|
245
|
+
<NativeFathomProvider siteId="TEST_SITE">
|
|
246
|
+
<TestChild />
|
|
247
|
+
</NativeFathomProvider>,
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
// The event should be queued since WebView isn't ready
|
|
251
|
+
// We just verify no errors are thrown
|
|
252
|
+
})
|
|
253
|
+
|
|
254
|
+
it('should provide a working trackPageview method', () => {
|
|
255
|
+
const TestChild = () => {
|
|
256
|
+
const { trackPageview } = useFathom()
|
|
257
|
+
React.useEffect(() => {
|
|
258
|
+
trackPageview({ url: '/test-page' })
|
|
259
|
+
}, [trackPageview])
|
|
260
|
+
return null
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
render(
|
|
264
|
+
<NativeFathomProvider siteId="TEST_SITE">
|
|
265
|
+
<TestChild />
|
|
266
|
+
</NativeFathomProvider>,
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
// The pageview should be queued since WebView isn't ready
|
|
270
|
+
// We just verify no errors are thrown
|
|
271
|
+
})
|
|
272
|
+
|
|
273
|
+
it('should provide a working trackGoal method', () => {
|
|
274
|
+
const TestChild = () => {
|
|
275
|
+
const { trackGoal } = useFathom()
|
|
276
|
+
React.useEffect(() => {
|
|
277
|
+
trackGoal('PURCHASE', 2999)
|
|
278
|
+
}, [trackGoal])
|
|
279
|
+
return null
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
render(
|
|
283
|
+
<NativeFathomProvider siteId="TEST_SITE">
|
|
284
|
+
<TestChild />
|
|
285
|
+
</NativeFathomProvider>,
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
// The goal should be queued since WebView isn't ready
|
|
289
|
+
// We just verify no errors are thrown
|
|
290
|
+
})
|
|
291
|
+
})
|
|
292
|
+
|
|
293
|
+
describe('clientRef', () => {
|
|
294
|
+
it('should populate clientRef with the WebViewFathomClient', async () => {
|
|
295
|
+
let clientRefValue: WebViewFathomClient | null = null
|
|
296
|
+
|
|
297
|
+
const TestComponent = () => {
|
|
298
|
+
const clientRef = useRef<WebViewFathomClient>(null)
|
|
299
|
+
React.useEffect(() => {
|
|
300
|
+
clientRefValue = clientRef.current
|
|
301
|
+
})
|
|
302
|
+
return (
|
|
303
|
+
<NativeFathomProvider siteId="TEST_SITE" clientRef={clientRef}>
|
|
304
|
+
<div>Test</div>
|
|
305
|
+
</NativeFathomProvider>
|
|
306
|
+
)
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
render(<TestComponent />)
|
|
310
|
+
|
|
311
|
+
await waitFor(() => {
|
|
312
|
+
expect(clientRefValue).not.toBeNull()
|
|
313
|
+
expect(clientRefValue).toBeDefined()
|
|
314
|
+
})
|
|
315
|
+
|
|
316
|
+
// Verify it's a WebViewFathomClient with the extended methods
|
|
317
|
+
expect(typeof clientRefValue?.trackEvent).toBe('function')
|
|
318
|
+
expect(typeof clientRefValue?.trackPageview).toBe('function')
|
|
319
|
+
expect(typeof clientRefValue?.processQueue).toBe('function')
|
|
320
|
+
expect(typeof clientRefValue?.getQueueLength).toBe('function')
|
|
321
|
+
expect(typeof clientRefValue?.setWebViewReady).toBe('function')
|
|
322
|
+
})
|
|
323
|
+
|
|
324
|
+
it('should allow parent to call client methods via clientRef', async () => {
|
|
325
|
+
const clientRef = React.createRef<WebViewFathomClient>() as React.MutableRefObject<WebViewFathomClient | null>
|
|
326
|
+
clientRef.current = null
|
|
327
|
+
|
|
328
|
+
render(
|
|
329
|
+
<NativeFathomProvider siteId="TEST_SITE" clientRef={clientRef}>
|
|
330
|
+
<div>Test</div>
|
|
331
|
+
</NativeFathomProvider>,
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
await waitFor(() => {
|
|
335
|
+
expect(clientRef.current).not.toBeNull()
|
|
336
|
+
})
|
|
337
|
+
|
|
338
|
+
// Parent can use the client directly
|
|
339
|
+
// Events will be queued since WebView isn't ready
|
|
340
|
+
clientRef.current?.trackEvent('parent-event', { _value: 100 })
|
|
341
|
+
|
|
342
|
+
// Verify the event was queued
|
|
343
|
+
expect(clientRef.current?.getQueueLength()).toBeGreaterThan(0)
|
|
344
|
+
})
|
|
345
|
+
|
|
346
|
+
it('should allow parent to check queue status via clientRef', async () => {
|
|
347
|
+
const clientRef = React.createRef<WebViewFathomClient>() as React.MutableRefObject<WebViewFathomClient | null>
|
|
348
|
+
clientRef.current = null
|
|
349
|
+
|
|
350
|
+
render(
|
|
351
|
+
<NativeFathomProvider siteId="TEST_SITE" clientRef={clientRef}>
|
|
352
|
+
<div>Test</div>
|
|
353
|
+
</NativeFathomProvider>,
|
|
354
|
+
)
|
|
355
|
+
|
|
356
|
+
await waitFor(() => {
|
|
357
|
+
expect(clientRef.current).not.toBeNull()
|
|
358
|
+
})
|
|
359
|
+
|
|
360
|
+
// Initially, queue should be empty
|
|
361
|
+
expect(clientRef.current?.getQueueLength()).toBe(0)
|
|
362
|
+
|
|
363
|
+
// Track some events (they'll be queued)
|
|
364
|
+
clientRef.current?.trackEvent('event-1')
|
|
365
|
+
clientRef.current?.trackPageview({ url: '/page-1' })
|
|
366
|
+
clientRef.current?.trackGoal('GOAL', 100)
|
|
367
|
+
|
|
368
|
+
// Queue should now have 3 items
|
|
369
|
+
expect(clientRef.current?.getQueueLength()).toBe(3)
|
|
370
|
+
})
|
|
371
|
+
})
|
|
372
|
+
})
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import React, { useMemo, useRef, useCallback, useEffect } from 'react'
|
|
2
|
+
|
|
3
|
+
import { FathomProvider } from '../FathomProvider'
|
|
4
|
+
import { FathomWebView, type FathomWebViewRef } from './FathomWebView'
|
|
5
|
+
import { createWebViewClient, type WebViewFathomClient } from './createWebViewClient'
|
|
6
|
+
import { useAppStateTracking } from './useAppStateTracking'
|
|
7
|
+
import type { NativeFathomProviderProps } from './types'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Internal component that handles app state tracking
|
|
11
|
+
*/
|
|
12
|
+
const AppStateTracker: React.FC = () => {
|
|
13
|
+
useAppStateTracking({
|
|
14
|
+
foregroundEventName: 'app-foreground',
|
|
15
|
+
backgroundEventName: 'app-background',
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
return null
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* A convenience provider for React Native apps that uses a hidden WebView
|
|
23
|
+
* to load the official Fathom Analytics script.
|
|
24
|
+
*
|
|
25
|
+
* This approach ensures full compatibility with Fathom's tracking by using
|
|
26
|
+
* their official JavaScript client, while providing a native React API.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```tsx
|
|
30
|
+
* import { NativeFathomProvider } from 'react-fathom/native'
|
|
31
|
+
*
|
|
32
|
+
* function App() {
|
|
33
|
+
* return (
|
|
34
|
+
* <NativeFathomProvider
|
|
35
|
+
* siteId="YOUR_SITE_ID"
|
|
36
|
+
* debug={__DEV__}
|
|
37
|
+
* trackAppState
|
|
38
|
+
* >
|
|
39
|
+
* <YourApp />
|
|
40
|
+
* </NativeFathomProvider>
|
|
41
|
+
* )
|
|
42
|
+
* }
|
|
43
|
+
* ```
|
|
44
|
+
*
|
|
45
|
+
* @remarks
|
|
46
|
+
* This component renders a hidden WebView that loads Fathom's tracking script.
|
|
47
|
+
* Events are queued until the WebView is ready, then automatically flushed.
|
|
48
|
+
*
|
|
49
|
+
* The WebView approach is used because Fathom Analytics does not currently
|
|
50
|
+
* provide a public API for server-side or mobile event tracking. This ensures
|
|
51
|
+
* your analytics are recorded correctly using Fathom's official client.
|
|
52
|
+
*/
|
|
53
|
+
export const NativeFathomProvider: React.FC<NativeFathomProviderProps> = ({
|
|
54
|
+
siteId,
|
|
55
|
+
loadOptions,
|
|
56
|
+
scriptDomain,
|
|
57
|
+
defaultPageviewOptions,
|
|
58
|
+
defaultEventOptions,
|
|
59
|
+
trackAppState = false,
|
|
60
|
+
debug = false,
|
|
61
|
+
onReady,
|
|
62
|
+
onError,
|
|
63
|
+
clientRef,
|
|
64
|
+
children,
|
|
65
|
+
}) => {
|
|
66
|
+
const webViewRef = useRef<FathomWebViewRef>(null)
|
|
67
|
+
|
|
68
|
+
// Create the WebView-based client
|
|
69
|
+
const client = useMemo(
|
|
70
|
+
(): WebViewFathomClient =>
|
|
71
|
+
createWebViewClient(() => webViewRef.current, {
|
|
72
|
+
debug,
|
|
73
|
+
enableQueue: true,
|
|
74
|
+
maxQueueSize: 100,
|
|
75
|
+
}),
|
|
76
|
+
[debug],
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
// Populate the clientRef so the parent component can access the client
|
|
80
|
+
useEffect(() => {
|
|
81
|
+
if (clientRef) {
|
|
82
|
+
clientRef.current = client
|
|
83
|
+
}
|
|
84
|
+
}, [client, clientRef])
|
|
85
|
+
|
|
86
|
+
// Handle WebView ready event
|
|
87
|
+
const handleReady = useCallback(() => {
|
|
88
|
+
// Flush any queued commands
|
|
89
|
+
client.setWebViewReady()
|
|
90
|
+
onReady?.()
|
|
91
|
+
}, [client, onReady])
|
|
92
|
+
|
|
93
|
+
return (
|
|
94
|
+
<FathomProvider
|
|
95
|
+
client={client}
|
|
96
|
+
siteId={siteId}
|
|
97
|
+
defaultPageviewOptions={defaultPageviewOptions}
|
|
98
|
+
defaultEventOptions={defaultEventOptions}
|
|
99
|
+
>
|
|
100
|
+
<FathomWebView
|
|
101
|
+
ref={webViewRef}
|
|
102
|
+
siteId={siteId}
|
|
103
|
+
loadOptions={loadOptions}
|
|
104
|
+
scriptDomain={scriptDomain}
|
|
105
|
+
debug={debug}
|
|
106
|
+
onReady={handleReady}
|
|
107
|
+
onError={onError}
|
|
108
|
+
/>
|
|
109
|
+
{trackAppState && <AppStateTracker />}
|
|
110
|
+
{children}
|
|
111
|
+
</FathomProvider>
|
|
112
|
+
)
|
|
113
|
+
}
|