@teardown/react-native 2.0.4 → 2.0.10

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.
@@ -1,563 +0,0 @@
1
- # Advanced Usage
2
-
3
- Advanced patterns and techniques for the Teardown SDK.
4
-
5
- ## Custom Storage Adapters
6
-
7
- Create platform-specific or specialized storage adapters.
8
-
9
- ### In-Memory Storage (Testing)
10
-
11
- ```typescript
12
- function createInMemoryStorageFactory(): SupportedStorageFactory {
13
- return (storageKey: string) => {
14
- const store = new Map<string, string>();
15
-
16
- return {
17
- preload: () => {},
18
- getItem: (key: string) => {
19
- return store.get(`${storageKey}:${key}`) ?? null;
20
- },
21
- setItem: (key: string, value: string) => {
22
- store.set(`${storageKey}:${key}`, value);
23
- },
24
- removeItem: (key: string) => {
25
- store.delete(`${storageKey}:${key}`);
26
- },
27
- clear: () => {
28
- store.clear();
29
- },
30
- keys: () => {
31
- return Array.from(store.keys());
32
- },
33
- };
34
- };
35
- }
36
-
37
- // Use in tests
38
- const teardown = new TeardownCore({
39
- storageFactory: createInMemoryStorageFactory(),
40
- // ...
41
- });
42
- ```
43
-
44
- ### AsyncStorage Adapter
45
-
46
- ```typescript
47
- import AsyncStorage from '@react-native-async-storage/async-storage';
48
-
49
- function createAsyncStorageFactory(): SupportedStorageFactory {
50
- return (storageKey: string) => {
51
- const cache = new Map<string, string>();
52
-
53
- return {
54
- preload: () => {
55
- // Optionally preload keys
56
- AsyncStorage.getAllKeys().then((keys) => {
57
- const relevantKeys = keys.filter(k => k.startsWith(storageKey));
58
- AsyncStorage.multiGet(relevantKeys).then((entries) => {
59
- entries.forEach(([key, value]) => {
60
- if (value) cache.set(key, value);
61
- });
62
- });
63
- });
64
- },
65
- getItem: (key: string) => {
66
- const fullKey = `${storageKey}:${key}`;
67
- return cache.get(fullKey) ?? null;
68
- },
69
- setItem: (key: string, value: string) => {
70
- const fullKey = `${storageKey}:${key}`;
71
- cache.set(fullKey, value);
72
- AsyncStorage.setItem(fullKey, value).catch(console.error);
73
- },
74
- removeItem: (key: string) => {
75
- const fullKey = `${storageKey}:${key}`;
76
- cache.delete(fullKey);
77
- AsyncStorage.removeItem(fullKey).catch(console.error);
78
- },
79
- clear: () => {
80
- cache.clear();
81
- },
82
- keys: () => {
83
- return Array.from(cache.keys());
84
- },
85
- };
86
- };
87
- }
88
- ```
89
-
90
- ### Encrypted Storage
91
-
92
- ```typescript
93
- import EncryptedStorage from 'react-native-encrypted-storage';
94
-
95
- function createEncryptedStorageFactory(): SupportedStorageFactory {
96
- return (storageKey: string) => {
97
- const cache = new Map<string, string>();
98
-
99
- return {
100
- preload: async () => {
101
- try {
102
- const keys = await EncryptedStorage.getAllKeys();
103
- const relevantKeys = keys.filter(k => k.startsWith(storageKey));
104
-
105
- for (const key of relevantKeys) {
106
- const value = await EncryptedStorage.getItem(key);
107
- if (value) cache.set(key, value);
108
- }
109
- } catch (error) {
110
- console.error('Preload failed:', error);
111
- }
112
- },
113
- getItem: (key: string) => {
114
- return cache.get(`${storageKey}:${key}`) ?? null;
115
- },
116
- setItem: (key: string, value: string) => {
117
- const fullKey = `${storageKey}:${key}`;
118
- cache.set(fullKey, value);
119
- EncryptedStorage.setItem(fullKey, value).catch(console.error);
120
- },
121
- removeItem: (key: string) => {
122
- const fullKey = `${storageKey}:${key}`;
123
- cache.delete(fullKey);
124
- EncryptedStorage.removeItem(fullKey).catch(console.error);
125
- },
126
- clear: () => {
127
- cache.clear();
128
- },
129
- keys: () => {
130
- return Array.from(cache.keys());
131
- },
132
- };
133
- };
134
- }
135
- ```
136
-
137
- ## Custom Device Adapters
138
-
139
- Create adapters for non-standard platforms.
140
-
141
- ### Web Device Adapter
142
-
143
- ```typescript
144
- import type { DeviceInfo } from '@teardown/schemas';
145
- import type { DeviceInfoAdapter } from '@teardown/react-native';
146
-
147
- class WebDeviceAdapter implements DeviceInfoAdapter {
148
- async getDeviceInfo(): Promise<DeviceInfo> {
149
- return {
150
- app_version: this.getAppVersion(),
151
- app_build: this.getAppBuild(),
152
- app_bundle_id: window.location.hostname,
153
- device_platform: this.getPlatform(),
154
- device_os_version: navigator.userAgent,
155
- device_model: this.getDeviceModel(),
156
- device_manufacturer: this.getBrowser(),
157
- update: null,
158
- };
159
- }
160
-
161
- private getAppVersion(): string {
162
- // Read from meta tag or package.json
163
- const meta = document.querySelector('meta[name="version"]');
164
- return meta?.getAttribute('content') ?? '1.0.0';
165
- }
166
-
167
- private getAppBuild(): string {
168
- const meta = document.querySelector('meta[name="build"]');
169
- return meta?.getAttribute('content') ?? '1';
170
- }
171
-
172
- private getPlatform(): DevicePlatformEnum {
173
- const ua = navigator.userAgent.toLowerCase();
174
- if (ua.includes('android')) return DevicePlatformEnum.ANDROID;
175
- if (ua.includes('iphone') || ua.includes('ipad')) return DevicePlatformEnum.IOS;
176
- return DevicePlatformEnum.WEB;
177
- }
178
-
179
- private getDeviceModel(): string {
180
- if (navigator.userAgentData?.mobile) return 'Mobile';
181
- return 'Desktop';
182
- }
183
-
184
- private getBrowser(): string {
185
- const ua = navigator.userAgent;
186
- if (ua.includes('Chrome')) return 'Chrome';
187
- if (ua.includes('Firefox')) return 'Firefox';
188
- if (ua.includes('Safari')) return 'Safari';
189
- return 'Unknown';
190
- }
191
- }
192
-
193
- // Use it
194
- const teardown = new TeardownCore({
195
- deviceAdapter: new WebDeviceAdapter(),
196
- // ...
197
- });
198
- ```
199
-
200
- ## Multiple SDK Instances
201
-
202
- Use multiple SDK instances for different projects or environments.
203
-
204
- ```typescript
205
- // Production instance
206
- const productionTeardown = new TeardownCore({
207
- org_id: 'prod-org',
208
- project_id: 'prod-project',
209
- api_key: 'prod-key',
210
- storageFactory: createMMKVStorageFactory('production'),
211
- deviceAdapter: new ExpoDeviceAdapter(),
212
- });
213
-
214
- // Staging instance
215
- const stagingTeardown = new TeardownCore({
216
- org_id: 'staging-org',
217
- project_id: 'staging-project',
218
- api_key: 'staging-key',
219
- storageFactory: createMMKVStorageFactory('staging'),
220
- deviceAdapter: new ExpoDeviceAdapter(),
221
- });
222
-
223
- // Use based on environment
224
- const teardown = __DEV__ ? stagingTeardown : productionTeardown;
225
-
226
- <TeardownProvider core={teardown}>
227
- <App />
228
- </TeardownProvider>
229
- ```
230
-
231
- ## Environment-Based Configuration
232
-
233
- ```typescript
234
- const config = {
235
- development: {
236
- org_id: process.env.DEV_ORG_ID!,
237
- project_id: process.env.DEV_PROJECT_ID!,
238
- api_key: process.env.DEV_API_KEY!,
239
- },
240
- staging: {
241
- org_id: process.env.STAGING_ORG_ID!,
242
- project_id: process.env.STAGING_PROJECT_ID!,
243
- api_key: process.env.STAGING_API_KEY!,
244
- },
245
- production: {
246
- org_id: process.env.PROD_ORG_ID!,
247
- project_id: process.env.PROD_PROJECT_ID!,
248
- api_key: process.env.PROD_API_KEY!,
249
- },
250
- };
251
-
252
- const env = process.env.NODE_ENV as keyof typeof config;
253
- const teardown = new TeardownCore({
254
- ...config[env],
255
- storageFactory: createMMKVStorageFactory(),
256
- deviceAdapter: new ExpoDeviceAdapter(),
257
- });
258
- ```
259
-
260
- ## Advanced Identity Patterns
261
-
262
- ### Automatic Re-identification
263
-
264
- ```typescript
265
- function useAutoReidentify(interval: number = 3600000) {
266
- const { core } = useTeardown();
267
-
268
- useEffect(() => {
269
- const timer = setInterval(async () => {
270
- await core.identity.refresh();
271
- }, interval);
272
-
273
- return () => clearInterval(timer);
274
- }, [core, interval]);
275
- }
276
-
277
- // Use in app
278
- function App() {
279
- useAutoReidentify(3600000); // Re-identify every hour
280
- return <YourApp />;
281
- }
282
- ```
283
-
284
- ### Session Persistence Across Launches
285
-
286
- ```typescript
287
- function useRestoreSession() {
288
- const { core } = useTeardown();
289
- const [restored, setRestored] = useState(false);
290
-
291
- useEffect(() => {
292
- const state = core.identity.getIdentifyState();
293
-
294
- if (state.type === 'identified') {
295
- // Session already restored from storage
296
- setRestored(true);
297
- } else {
298
- // Wait for auto-identification
299
- const unsubscribe = core.identity.onIdentifyStateChange((newState) => {
300
- if (newState.type === 'identified') {
301
- setRestored(true);
302
- }
303
- });
304
-
305
- return unsubscribe;
306
- }
307
- }, [core]);
308
-
309
- return restored;
310
- }
311
-
312
- // Use for loading state
313
- function App() {
314
- const restored = useRestoreSession();
315
-
316
- if (!restored) {
317
- return <SplashScreen />;
318
- }
319
-
320
- return <MainApp />;
321
- }
322
- ```
323
-
324
- ## Advanced Force Update Patterns
325
-
326
- ### Custom Update UI with Animation
327
-
328
- ```typescript
329
- function AnimatedUpdateBanner() {
330
- const { isUpdateAvailable, isUpdateRequired } = useForceUpdate();
331
- const slideAnim = useRef(new Animated.Value(-100)).current;
332
-
333
- useEffect(() => {
334
- if (isUpdateAvailable && !isUpdateRequired) {
335
- Animated.spring(slideAnim, {
336
- toValue: 0,
337
- useNativeDriver: true,
338
- }).start();
339
- } else {
340
- Animated.timing(slideAnim, {
341
- toValue: -100,
342
- duration: 200,
343
- useNativeDriver: true,
344
- }).start();
345
- }
346
- }, [isUpdateAvailable, isUpdateRequired]);
347
-
348
- return (
349
- <Animated.View style={[styles.banner, { transform: [{ translateY: slideAnim }] }]}>
350
- <Text>Update Available</Text>
351
- <Button title="Update" onPress={handleUpdate} />
352
- </Animated.View>
353
- );
354
- }
355
- ```
356
-
357
- ### Deferred Update Prompts
358
-
359
- ```typescript
360
- function useDeferredUpdatePrompt(deferCount: number = 3) {
361
- const { isUpdateAvailable, isUpdateRequired } = useForceUpdate();
362
- const [dismissCount, setDismissCount] = useState(0);
363
-
364
- const shouldShow = isUpdateAvailable && (
365
- isUpdateRequired || dismissCount < deferCount
366
- );
367
-
368
- const dismiss = () => {
369
- if (!isUpdateRequired) {
370
- setDismissCount(c => c + 1);
371
- }
372
- };
373
-
374
- return { shouldShow, dismiss, isForced: isUpdateRequired };
375
- }
376
-
377
- // Use it
378
- function UpdatePrompt() {
379
- const { shouldShow, dismiss, isForced } = useDeferredUpdatePrompt(3);
380
-
381
- if (!shouldShow) return null;
382
-
383
- return (
384
- <Modal visible={true}>
385
- <Text>Update Available</Text>
386
- <Button title="Update Now" onPress={handleUpdate} />
387
- {!isForced && (
388
- <Button title="Remind Me Later" onPress={dismiss} />
389
- )}
390
- </Modal>
391
- );
392
- }
393
- ```
394
-
395
- ## Monitoring and Analytics
396
-
397
- ### Track SDK Events
398
-
399
- ```typescript
400
- function useSDKAnalytics() {
401
- const { core } = useTeardown();
402
-
403
- useEffect(() => {
404
- // Track identity changes
405
- const unsubscribeIdentity = core.identity.onIdentifyStateChange((state) => {
406
- analytics.track('identity_state_changed', {
407
- state: state.type,
408
- timestamp: Date.now(),
409
- });
410
- });
411
-
412
- // Track version status changes
413
- const unsubscribeVersion = core.forceUpdate.onVersionStatusChange((status) => {
414
- analytics.track('version_status_changed', {
415
- status: status.type,
416
- timestamp: Date.now(),
417
- });
418
- });
419
-
420
- return () => {
421
- unsubscribeIdentity();
422
- unsubscribeVersion();
423
- };
424
- }, [core]);
425
- }
426
- ```
427
-
428
- ### Error Tracking
429
-
430
- ```typescript
431
- function useSDKErrorTracking() {
432
- const { core } = useTeardown();
433
-
434
- const trackError = async (operation: string, error: string) => {
435
- const deviceId = await core.device.getDeviceId();
436
- const session = core.identity.getSessionState();
437
-
438
- errorTracker.log(error, {
439
- operation,
440
- deviceId,
441
- sessionId: session?.session_id,
442
- personaId: session?.persona_id,
443
- });
444
- };
445
-
446
- return trackError;
447
- }
448
- ```
449
-
450
- ## Testing
451
-
452
- ### Mock SDK for Tests
453
-
454
- ```typescript
455
- import { TeardownCore } from '@teardown/react-native';
456
-
457
- export function createMockTeardown(): TeardownCore {
458
- return new TeardownCore({
459
- org_id: 'test-org',
460
- project_id: 'test-project',
461
- api_key: 'test-key',
462
- storageFactory: createInMemoryStorageFactory(),
463
- deviceAdapter: {
464
- getDeviceInfo: async () => ({
465
- app_version: '1.0.0',
466
- app_build: '1',
467
- app_bundle_id: 'com.test.app',
468
- device_platform: DevicePlatformEnum.IOS,
469
- device_os_version: '17.0',
470
- device_model: 'iPhone 14',
471
- device_manufacturer: 'Apple',
472
- update: null,
473
- }),
474
- },
475
- });
476
- }
477
-
478
- // Use in tests
479
- import { render } from '@testing-library/react-native';
480
-
481
- it('renders with mock SDK', () => {
482
- const mockTeardown = createMockTeardown();
483
-
484
- const { getByText } = render(
485
- <TeardownProvider core={mockTeardown}>
486
- <MyComponent />
487
- </TeardownProvider>
488
- );
489
-
490
- expect(getByText('Welcome')).toBeTruthy();
491
- });
492
- ```
493
-
494
- ### Test Hooks
495
-
496
- ```typescript
497
- import { renderHook, waitFor } from '@testing-library/react-native';
498
-
499
- it('useSession returns session after identify', async () => {
500
- const mockTeardown = createMockTeardown();
501
-
502
- const wrapper = ({ children }) => (
503
- <TeardownProvider core={mockTeardown}>
504
- {children}
505
- </TeardownProvider>
506
- );
507
-
508
- const { result } = renderHook(() => useSession(), { wrapper });
509
-
510
- // Initially null
511
- expect(result.current).toBeNull();
512
-
513
- // Identify
514
- await mockTeardown.identity.identify({ user_id: 'test-user' });
515
-
516
- // Wait for update
517
- await waitFor(() => {
518
- expect(result.current).not.toBeNull();
519
- });
520
-
521
- expect(result.current?.persona_id).toBe('test-user');
522
- });
523
- ```
524
-
525
- ## Performance Optimization
526
-
527
- ### Lazy Initialization
528
-
529
- ```typescript
530
- function useLazyTeardown() {
531
- const [teardown] = useState(() => {
532
- // Only initialize once
533
- return new TeardownCore({
534
- // ... config
535
- });
536
- });
537
-
538
- return teardown;
539
- }
540
- ```
541
-
542
- ### Memoized Selectors
543
-
544
- ```typescript
545
- function usePersonaId() {
546
- const session = useSession();
547
- return useMemo(() => session?.persona_id ?? null, [session]);
548
- }
549
-
550
- function useIsUpdateRequired() {
551
- const { versionStatus } = useForceUpdate();
552
- return useMemo(
553
- () => versionStatus.type === 'update_required',
554
- [versionStatus]
555
- );
556
- }
557
- ```
558
-
559
- ## Next Steps
560
-
561
- - [API Reference](./07-api-reference.mdx)
562
- - [Hooks Reference](./08-hooks-reference.mdx)
563
- - [Getting Started](./01-getting-started.mdx)