@teardown/react-native 1.2.39 → 2.0.1
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 +89 -7
- package/docs/01-getting-started.mdx +147 -0
- package/docs/02-core-concepts.mdx +188 -0
- package/docs/03-identity.mdx +301 -0
- package/docs/04-force-updates.mdx +339 -0
- package/docs/05-device-info.mdx +324 -0
- package/docs/06-logging.mdx +345 -0
- package/docs/06-storage.mdx +349 -0
- package/docs/07-api-reference.mdx +472 -0
- package/docs/07-logging.mdx +345 -0
- package/docs/08-api-reference.mdx +472 -0
- package/docs/08-hooks-reference.mdx +476 -0
- package/docs/09-advanced.mdx +563 -0
- package/docs/09-hooks-reference.mdx +476 -0
- package/docs/10-advanced.mdx +563 -0
- package/package.json +65 -47
- package/src/clients/api/api.client.ts +80 -0
- package/src/clients/api/index.ts +1 -0
- package/src/clients/device/device.adpater-interface.ts +57 -0
- package/src/clients/device/device.client.test.ts +190 -0
- package/src/clients/device/device.client.ts +69 -0
- package/src/clients/device/expo-adapter.ts +90 -0
- package/src/clients/device/index.ts +4 -0
- package/src/clients/force-update/force-update.client.test.ts +295 -0
- package/src/clients/force-update/force-update.client.ts +224 -0
- package/src/clients/force-update/index.ts +1 -0
- package/src/clients/identity/identity.client.test.ts +454 -0
- package/src/clients/identity/identity.client.ts +255 -0
- package/src/clients/identity/index.ts +1 -0
- package/src/clients/logging/index.ts +1 -0
- package/src/clients/logging/logging.client.ts +92 -0
- package/src/clients/notifications/notifications.client.ts +10 -0
- package/src/clients/storage/index.ts +1 -0
- package/src/clients/storage/mmkv-adapter.ts +23 -0
- package/src/clients/storage/storage.client.ts +75 -0
- package/src/clients/utils/index.ts +1 -0
- package/src/clients/utils/utils.client.ts +75 -0
- package/src/components/ui/button.tsx +0 -0
- package/src/components/ui/input.tsx +0 -0
- package/src/contexts/index.ts +1 -0
- package/src/contexts/teardown.context.ts +17 -0
- package/src/exports/expo.ts +1 -0
- package/src/exports/index.ts +16 -0
- package/src/exports/mmkv.ts +1 -0
- package/src/hooks/use-force-update.ts +38 -0
- package/src/hooks/use-session.ts +26 -0
- package/src/providers/teardown.provider.tsx +28 -0
- package/src/teardown.core.ts +76 -0
- package/dist/components/index.d.ts +0 -1
- package/dist/components/index.js +0 -3
- package/dist/components/index.js.map +0 -1
- package/dist/components/teardown-logo.d.ts +0 -4
- package/dist/components/teardown-logo.js +0 -35
- package/dist/components/teardown-logo.js.map +0 -1
- package/dist/containers/index.d.ts +0 -1
- package/dist/containers/index.js +0 -18
- package/dist/containers/index.js.map +0 -1
- package/dist/containers/teardown.container.d.ts +0 -8
- package/dist/containers/teardown.container.js +0 -26
- package/dist/containers/teardown.container.js.map +0 -1
- package/dist/index.d.ts +0 -2
- package/dist/index.js +0 -22
- package/dist/index.js.map +0 -1
- package/dist/plugins/http.plugin.d.ts +0 -23
- package/dist/plugins/http.plugin.js +0 -145
- package/dist/plugins/http.plugin.js.map +0 -1
- package/dist/plugins/index.d.ts +0 -2
- package/dist/plugins/index.js +0 -20
- package/dist/plugins/index.js.map +0 -1
- package/dist/plugins/logging.plugin.d.ts +0 -9
- package/dist/plugins/logging.plugin.js +0 -36
- package/dist/plugins/logging.plugin.js.map +0 -1
- package/dist/plugins/websocket.plugin.d.ts +0 -1
- package/dist/plugins/websocket.plugin.js +0 -108
- package/dist/plugins/websocket.plugin.js.map +0 -1
- package/dist/services/index.d.ts +0 -1
- package/dist/services/index.js +0 -18
- package/dist/services/index.js.map +0 -1
- package/dist/services/teardown.service.d.ts +0 -10
- package/dist/services/teardown.service.js +0 -22
- package/dist/services/teardown.service.js.map +0 -1
- package/dist/teardown.client.d.ts +0 -41
- package/dist/teardown.client.js +0 -60
- package/dist/teardown.client.js.map +0 -1
- package/dist/utils/log.d.ts +0 -5
- package/dist/utils/log.js +0 -9
- package/dist/utils/log.js.map +0 -1
|
@@ -0,0 +1,563 @@
|
|
|
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)
|
package/package.json
CHANGED
|
@@ -1,49 +1,67 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
2
|
+
"name": "@teardown/react-native",
|
|
3
|
+
"version": "2.0.1",
|
|
4
|
+
"private": false,
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "public"
|
|
7
|
+
},
|
|
8
|
+
"files": [
|
|
9
|
+
"src/**/*",
|
|
10
|
+
"docs/**/*"
|
|
11
|
+
],
|
|
12
|
+
"main": "./src/exports/index.ts",
|
|
13
|
+
"module": "./src/exports/index.ts",
|
|
14
|
+
"types": "./src/exports/index.ts",
|
|
15
|
+
"exports": {
|
|
16
|
+
".": {
|
|
17
|
+
"types": "./src/exports/index.ts",
|
|
18
|
+
"import": "./src/exports/index.ts",
|
|
19
|
+
"default": "./src/exports/index.ts"
|
|
20
|
+
},
|
|
21
|
+
"./expo": {
|
|
22
|
+
"types": "./src/exports/expo.ts",
|
|
23
|
+
"import": "./src/exports/expo.ts",
|
|
24
|
+
"default": "./src/exports/expo.ts"
|
|
25
|
+
},
|
|
26
|
+
"./mmkv": {
|
|
27
|
+
"types": "./src/exports/mmkv.ts",
|
|
28
|
+
"import": "./src/exports/mmkv.ts",
|
|
29
|
+
"default": "./src/exports/mmkv.ts"
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"scripts": {
|
|
33
|
+
"build": "tsc --project ./tsconfig.json",
|
|
34
|
+
"dev": "bun run build && tsc --watch --project ./tsconfig.json",
|
|
35
|
+
"typecheck": "tsc --noEmit --project ./tsconfig.json",
|
|
36
|
+
"lint": "biome lint --write",
|
|
37
|
+
"fmt": "biome format --write",
|
|
38
|
+
"test": "bun test",
|
|
39
|
+
"prepublishOnly": "bun run build",
|
|
40
|
+
"nuke": "cd ../../ && bun run nuke"
|
|
41
|
+
},
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"@teardown/ingest-api": "0.1.36",
|
|
44
|
+
"@teardown/schemas": "0.1.36",
|
|
45
|
+
"@teardown/types": "0.1.36",
|
|
46
|
+
"eventemitter3": "^5.0.1",
|
|
47
|
+
"uuid": "^13.0.0"
|
|
48
|
+
},
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"@elysiajs/eden": "^1.4.5",
|
|
51
|
+
"@teardown/tsconfig": "1.0.0",
|
|
52
|
+
"@types/bun": "latest",
|
|
53
|
+
"@types/react": "*",
|
|
54
|
+
"@types/uuid": "^11.0.0",
|
|
55
|
+
"expo-updates": "^29.0.12"
|
|
56
|
+
},
|
|
57
|
+
"peerDependencies": {
|
|
58
|
+
"react": "*",
|
|
59
|
+
"react-native": "*",
|
|
60
|
+
"typescript": "*",
|
|
61
|
+
"expo-application": "^7.0",
|
|
62
|
+
"expo-device": "^8.0",
|
|
63
|
+
"expo-notifications": "^0.29",
|
|
64
|
+
"react-native-mmkv": "^3.0",
|
|
65
|
+
"zod": "^4"
|
|
66
|
+
}
|
|
49
67
|
}
|