@teardown/react-native 2.0.0 → 2.0.2
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 +33 -19
- 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 +46 -18
- package/src/clients/api/api.client.ts +29 -4
- package/src/clients/device/{expo-adapter.ts → adapters/basic.adapter.ts} +2 -40
- package/src/clients/device/{device.adpater-interface.ts → adapters/device.adpater-interface.ts} +1 -1
- package/src/clients/device/adapters/expo.adapter.ts +90 -0
- package/src/clients/device/device.client.test.ts +1 -6
- package/src/clients/device/device.client.ts +5 -1
- package/src/clients/device/index.ts +1 -1
- package/src/clients/force-update/force-update.client.test.ts +244 -13
- package/src/clients/force-update/force-update.client.ts +71 -11
- package/src/clients/identity/identity.client.test.ts +888 -223
- package/src/clients/identity/identity.client.ts +59 -14
- package/src/clients/storage/adapters/async-storage.adapter.ts +81 -0
- package/src/clients/storage/{mmkv-adapter.ts → adapters/mmkv.adapter.ts} +7 -10
- package/src/clients/storage/adapters/storage.adpater-interface.ts +30 -0
- package/src/clients/storage/index.ts +2 -1
- package/src/clients/storage/storage.client.ts +9 -20
- package/src/clients/utils/utils.client.ts +1 -57
- package/src/exports/adapters/async-storage.ts +1 -0
- package/src/exports/adapters/expo.ts +1 -0
- package/src/exports/adapters/mmkv.ts +1 -0
- package/src/hooks/use-force-update.ts +12 -3
- package/src/hooks/use-session.ts +7 -4
- package/src/teardown.core.ts +16 -6
- package/src/exports/expo.ts +0 -1
- package/src/exports/mmkv.ts +0 -1
|
@@ -0,0 +1,476 @@
|
|
|
1
|
+
# Hooks Reference
|
|
2
|
+
|
|
3
|
+
React hooks for accessing Teardown SDK functionality.
|
|
4
|
+
|
|
5
|
+
## useTeardown
|
|
6
|
+
|
|
7
|
+
Access the TeardownCore instance.
|
|
8
|
+
|
|
9
|
+
### Usage
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
import { useTeardown } from '@teardown/react-native';
|
|
13
|
+
|
|
14
|
+
function MyComponent() {
|
|
15
|
+
const { core } = useTeardown();
|
|
16
|
+
|
|
17
|
+
return <Button onPress={() => core.identity.reset()} />;
|
|
18
|
+
}
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### Return Value
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
type UseTeardownResult = {
|
|
25
|
+
core: TeardownCore;
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### When to Use
|
|
30
|
+
|
|
31
|
+
- Accessing clients directly
|
|
32
|
+
- Calling SDK methods
|
|
33
|
+
- Setting configuration
|
|
34
|
+
|
|
35
|
+
### Example: Manual Identification
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
function LoginButton() {
|
|
39
|
+
const { core } = useTeardown();
|
|
40
|
+
|
|
41
|
+
const handleLogin = async () => {
|
|
42
|
+
const result = await core.identity.identify({
|
|
43
|
+
user_id: 'user-123',
|
|
44
|
+
email: 'user@example.com',
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
if (result.success) {
|
|
48
|
+
console.log('Logged in:', result.data.session_id);
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
return <Button onPress={handleLogin} title="Login" />;
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Example: Enable Debug Logging
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
function DebugToggle() {
|
|
60
|
+
const { core } = useTeardown();
|
|
61
|
+
const [enabled, setEnabled] = useState(false);
|
|
62
|
+
|
|
63
|
+
const toggle = () => {
|
|
64
|
+
const newValue = !enabled;
|
|
65
|
+
setEnabled(newValue);
|
|
66
|
+
core.setLogLevel(newValue ? 'verbose' : 'none');
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
return <Switch value={enabled} onValueChange={toggle} />;
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## useSession
|
|
76
|
+
|
|
77
|
+
Get current user session with automatic updates.
|
|
78
|
+
|
|
79
|
+
### Usage
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
import { useSession } from '@teardown/react-native';
|
|
83
|
+
|
|
84
|
+
function UserProfile() {
|
|
85
|
+
const session = useSession();
|
|
86
|
+
|
|
87
|
+
if (!session) {
|
|
88
|
+
return <LoginPrompt />;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return <Text>Session: {session.session_id}</Text>;
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Return Value
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
type UseSessionResult = Session | null
|
|
99
|
+
|
|
100
|
+
type Session = {
|
|
101
|
+
session_id: string;
|
|
102
|
+
device_id: string;
|
|
103
|
+
persona_id: string;
|
|
104
|
+
token: string;
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Returns `null` when:
|
|
109
|
+
- User is not identified
|
|
110
|
+
- Identity state is "unidentified" or "identifying"
|
|
111
|
+
|
|
112
|
+
Returns `Session` when:
|
|
113
|
+
- Identity state is "identified"
|
|
114
|
+
|
|
115
|
+
### Reactivity
|
|
116
|
+
|
|
117
|
+
The hook automatically re-renders when:
|
|
118
|
+
- User is identified
|
|
119
|
+
- User is logged out
|
|
120
|
+
- Session is refreshed
|
|
121
|
+
|
|
122
|
+
### Example: Conditional Rendering
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
function App() {
|
|
126
|
+
const session = useSession();
|
|
127
|
+
|
|
128
|
+
if (!session) {
|
|
129
|
+
return <LoginScreen />;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return <MainApp session={session} />;
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Example: Display Session Info
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
function SessionInfo() {
|
|
140
|
+
const session = useSession();
|
|
141
|
+
|
|
142
|
+
return (
|
|
143
|
+
<View>
|
|
144
|
+
<Text>Session ID: {session?.session_id ?? 'Not logged in'}</Text>
|
|
145
|
+
<Text>Device ID: {session?.device_id ?? 'Unknown'}</Text>
|
|
146
|
+
<Text>Persona ID: {session?.persona_id ?? 'Anonymous'}</Text>
|
|
147
|
+
</View>
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Example: Protected Route
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
function ProtectedScreen() {
|
|
156
|
+
const session = useSession();
|
|
157
|
+
const navigation = useNavigation();
|
|
158
|
+
|
|
159
|
+
useEffect(() => {
|
|
160
|
+
if (!session) {
|
|
161
|
+
navigation.navigate('Login');
|
|
162
|
+
}
|
|
163
|
+
}, [session]);
|
|
164
|
+
|
|
165
|
+
if (!session) {
|
|
166
|
+
return <ActivityIndicator />;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return <SecureContent />;
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## useForceUpdate
|
|
176
|
+
|
|
177
|
+
Get version update status with automatic updates.
|
|
178
|
+
|
|
179
|
+
### Usage
|
|
180
|
+
|
|
181
|
+
```typescript
|
|
182
|
+
import { useForceUpdate } from '@teardown/react-native';
|
|
183
|
+
|
|
184
|
+
function UpdateBanner() {
|
|
185
|
+
const { versionStatus, isUpdateRequired, isUpdateAvailable } = useForceUpdate();
|
|
186
|
+
|
|
187
|
+
if (isUpdateRequired) {
|
|
188
|
+
return <UpdateRequiredScreen />;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### Return Value
|
|
196
|
+
|
|
197
|
+
```typescript
|
|
198
|
+
type UseForceUpdateResult = {
|
|
199
|
+
versionStatus: VersionStatus;
|
|
200
|
+
isUpdateAvailable: boolean;
|
|
201
|
+
isUpdateRequired: boolean;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
type VersionStatus =
|
|
205
|
+
| { type: "initializing" }
|
|
206
|
+
| { type: "checking" }
|
|
207
|
+
| { type: "up_to_date" }
|
|
208
|
+
| { type: "update_available" }
|
|
209
|
+
| { type: "update_recommended" }
|
|
210
|
+
| { type: "update_required" }
|
|
211
|
+
| { type: "disabled" }
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### Fields
|
|
215
|
+
|
|
216
|
+
#### `versionStatus`
|
|
217
|
+
|
|
218
|
+
Current version status object with discriminated union type.
|
|
219
|
+
|
|
220
|
+
#### `isUpdateAvailable`
|
|
221
|
+
|
|
222
|
+
Boolean indicating if any update exists (available, recommended, or required).
|
|
223
|
+
|
|
224
|
+
```typescript
|
|
225
|
+
isUpdateAvailable =
|
|
226
|
+
versionStatus.type === "update_available" ||
|
|
227
|
+
versionStatus.type === "update_recommended" ||
|
|
228
|
+
versionStatus.type === "update_required"
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
#### `isUpdateRequired`
|
|
232
|
+
|
|
233
|
+
Boolean indicating if update is mandatory.
|
|
234
|
+
|
|
235
|
+
```typescript
|
|
236
|
+
isUpdateRequired = versionStatus.type === "update_required"
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### Reactivity
|
|
240
|
+
|
|
241
|
+
The hook automatically re-renders when:
|
|
242
|
+
- Version status changes
|
|
243
|
+
- App returns to foreground
|
|
244
|
+
- User is identified/re-identified
|
|
245
|
+
|
|
246
|
+
### Example: Update Required Modal
|
|
247
|
+
|
|
248
|
+
```typescript
|
|
249
|
+
function UpdateRequiredModal() {
|
|
250
|
+
const { isUpdateRequired } = useForceUpdate();
|
|
251
|
+
|
|
252
|
+
if (!isUpdateRequired) return null;
|
|
253
|
+
|
|
254
|
+
return (
|
|
255
|
+
<Modal visible={true} animationType="slide">
|
|
256
|
+
<View style={styles.container}>
|
|
257
|
+
<Text>Update Required</Text>
|
|
258
|
+
<Text>Please update to continue</Text>
|
|
259
|
+
<Button
|
|
260
|
+
title="Update Now"
|
|
261
|
+
onPress={() => Linking.openURL(STORE_URL)}
|
|
262
|
+
/>
|
|
263
|
+
</View>
|
|
264
|
+
</Modal>
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
### Example: Optional Update Banner
|
|
270
|
+
|
|
271
|
+
```typescript
|
|
272
|
+
function UpdateBanner() {
|
|
273
|
+
const { isUpdateAvailable, isUpdateRequired } = useForceUpdate();
|
|
274
|
+
const [dismissed, setDismissed] = useState(false);
|
|
275
|
+
|
|
276
|
+
// Don't show if required (use modal instead) or dismissed
|
|
277
|
+
if (isUpdateRequired || !isUpdateAvailable || dismissed) {
|
|
278
|
+
return null;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return (
|
|
282
|
+
<View style={styles.banner}>
|
|
283
|
+
<Text>New version available</Text>
|
|
284
|
+
<Button title="Update" onPress={() => Linking.openURL(STORE_URL)} />
|
|
285
|
+
<Button title="Later" onPress={() => setDismissed(true)} />
|
|
286
|
+
</View>
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
### Example: Status-Based UI
|
|
292
|
+
|
|
293
|
+
```typescript
|
|
294
|
+
function VersionStatusIndicator() {
|
|
295
|
+
const { versionStatus } = useForceUpdate();
|
|
296
|
+
|
|
297
|
+
switch (versionStatus.type) {
|
|
298
|
+
case 'checking':
|
|
299
|
+
return <ActivityIndicator />;
|
|
300
|
+
case 'update_required':
|
|
301
|
+
return <Badge color="red">Update Required</Badge>;
|
|
302
|
+
case 'update_recommended':
|
|
303
|
+
return <Badge color="orange">Update Recommended</Badge>;
|
|
304
|
+
case 'update_available':
|
|
305
|
+
return <Badge color="blue">Update Available</Badge>;
|
|
306
|
+
case 'up_to_date':
|
|
307
|
+
return <Badge color="green">Up to Date</Badge>;
|
|
308
|
+
default:
|
|
309
|
+
return null;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
### Example: Full Screen Takeover
|
|
315
|
+
|
|
316
|
+
```typescript
|
|
317
|
+
function FullscreenTakeover() {
|
|
318
|
+
const { isUpdateRequired } = useForceUpdate();
|
|
319
|
+
|
|
320
|
+
if (!isUpdateRequired) return null;
|
|
321
|
+
|
|
322
|
+
return (
|
|
323
|
+
<Modal
|
|
324
|
+
visible={true}
|
|
325
|
+
animationType="fade"
|
|
326
|
+
presentationStyle="overFullScreen"
|
|
327
|
+
>
|
|
328
|
+
<SafeAreaView style={styles.container}>
|
|
329
|
+
<Image source={appIcon} />
|
|
330
|
+
<Text style={styles.title}>Update Required</Text>
|
|
331
|
+
<Text style={styles.body}>
|
|
332
|
+
A new version of the app is required to continue.
|
|
333
|
+
</Text>
|
|
334
|
+
<Button
|
|
335
|
+
title="Update Now"
|
|
336
|
+
onPress={() => Linking.openURL(STORE_URL)}
|
|
337
|
+
/>
|
|
338
|
+
</SafeAreaView>
|
|
339
|
+
</Modal>
|
|
340
|
+
);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// In root layout
|
|
344
|
+
<TeardownProvider core={teardown}>
|
|
345
|
+
<App />
|
|
346
|
+
<FullscreenTakeover />
|
|
347
|
+
</TeardownProvider>
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
---
|
|
351
|
+
|
|
352
|
+
## Hook Patterns
|
|
353
|
+
|
|
354
|
+
### Combining Hooks
|
|
355
|
+
|
|
356
|
+
```typescript
|
|
357
|
+
function AppShell() {
|
|
358
|
+
const session = useSession();
|
|
359
|
+
const { isUpdateRequired } = useForceUpdate();
|
|
360
|
+
|
|
361
|
+
if (isUpdateRequired) {
|
|
362
|
+
return <UpdateRequiredScreen />;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
if (!session) {
|
|
366
|
+
return <LoginScreen />;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
return <MainApp />;
|
|
370
|
+
}
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
### Custom Hooks
|
|
374
|
+
|
|
375
|
+
```typescript
|
|
376
|
+
function useIsAuthenticated(): boolean {
|
|
377
|
+
const session = useSession();
|
|
378
|
+
return session !== null;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
function useRequireAuth() {
|
|
382
|
+
const session = useSession();
|
|
383
|
+
const navigation = useNavigation();
|
|
384
|
+
|
|
385
|
+
useEffect(() => {
|
|
386
|
+
if (!session) {
|
|
387
|
+
navigation.navigate('Login');
|
|
388
|
+
}
|
|
389
|
+
}, [session, navigation]);
|
|
390
|
+
|
|
391
|
+
return session;
|
|
392
|
+
}
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
### Memoization
|
|
396
|
+
|
|
397
|
+
```typescript
|
|
398
|
+
function UserDashboard() {
|
|
399
|
+
const session = useSession();
|
|
400
|
+
|
|
401
|
+
const personaId = useMemo(() => {
|
|
402
|
+
return session?.persona_id ?? 'anonymous';
|
|
403
|
+
}, [session]);
|
|
404
|
+
|
|
405
|
+
return <Dashboard personaId={personaId} />;
|
|
406
|
+
}
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
---
|
|
410
|
+
|
|
411
|
+
## Best Practices
|
|
412
|
+
|
|
413
|
+
### 1. Use Hooks for Reactive UI
|
|
414
|
+
|
|
415
|
+
```typescript
|
|
416
|
+
// ✅ Good - automatic updates
|
|
417
|
+
const session = useSession();
|
|
418
|
+
|
|
419
|
+
// ❌ Bad - manual subscription
|
|
420
|
+
const [session, setSession] = useState(null);
|
|
421
|
+
useEffect(() => {
|
|
422
|
+
const unsubscribe = core.identity.onIdentifyStateChange(/* ... */);
|
|
423
|
+
return unsubscribe;
|
|
424
|
+
}, []);
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
### 2. Handle Null States
|
|
428
|
+
|
|
429
|
+
```typescript
|
|
430
|
+
// ✅ Good - null check
|
|
431
|
+
const session = useSession();
|
|
432
|
+
if (!session) return <LoginPrompt />;
|
|
433
|
+
|
|
434
|
+
// ❌ Bad - assumes session exists
|
|
435
|
+
const session = useSession();
|
|
436
|
+
return <Text>{session.session_id}</Text>; // Can crash!
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
### 3. Combine Conditions Properly
|
|
440
|
+
|
|
441
|
+
```typescript
|
|
442
|
+
// ✅ Good - correct priority
|
|
443
|
+
if (isUpdateRequired) return <UpdateModal />;
|
|
444
|
+
if (!session) return <LoginScreen />;
|
|
445
|
+
return <App />;
|
|
446
|
+
|
|
447
|
+
// ❌ Bad - wrong priority
|
|
448
|
+
if (!session) return <LoginScreen />;
|
|
449
|
+
if (isUpdateRequired) return <UpdateModal />; // Never shown!
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
### 4. Don't Call Hooks Conditionally
|
|
453
|
+
|
|
454
|
+
```typescript
|
|
455
|
+
// ✅ Good - hooks at top level
|
|
456
|
+
function MyComponent() {
|
|
457
|
+
const session = useSession();
|
|
458
|
+
|
|
459
|
+
if (!session) return null;
|
|
460
|
+
return <Content />;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
// ❌ Bad - conditional hook
|
|
464
|
+
function MyComponent() {
|
|
465
|
+
if (someCondition) {
|
|
466
|
+
const session = useSession(); // Error!
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
---
|
|
472
|
+
|
|
473
|
+
## Next Steps
|
|
474
|
+
|
|
475
|
+
- [Advanced Usage](./09-advanced.mdx)
|
|
476
|
+
- [API Reference](./07-api-reference.mdx)
|