@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,301 @@
|
|
|
1
|
+
# Identity & Authentication
|
|
2
|
+
|
|
3
|
+
Manage user sessions and device identity with the IdentityClient.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The IdentityClient handles:
|
|
8
|
+
- Device fingerprinting
|
|
9
|
+
- User identification and session management
|
|
10
|
+
- Anonymous and identified user states
|
|
11
|
+
- Automatic session persistence
|
|
12
|
+
|
|
13
|
+
## Identity States
|
|
14
|
+
|
|
15
|
+
The SDK uses a discriminated union to represent identity states:
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
type IdentifyState =
|
|
19
|
+
| { type: "unidentified" }
|
|
20
|
+
| { type: "identifying" }
|
|
21
|
+
| { type: "identified", session: Session, version_info: {...} }
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### State Transitions
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
unidentified → identifying → identified
|
|
28
|
+
↑ ↓
|
|
29
|
+
└───────── reset() ─────────┘
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Using the Identity Client
|
|
33
|
+
|
|
34
|
+
### Access via Hook
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
import { useSession } from '@teardown/react-native';
|
|
38
|
+
|
|
39
|
+
function MyComponent() {
|
|
40
|
+
const session = useSession();
|
|
41
|
+
|
|
42
|
+
if (!session) {
|
|
43
|
+
return <LoginScreen />;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return <div>Welcome, {session.persona_id}</div>;
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Access via Core
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
import { useTeardown } from '@teardown/react-native';
|
|
54
|
+
|
|
55
|
+
function MyComponent() {
|
|
56
|
+
const { core } = useTeardown();
|
|
57
|
+
|
|
58
|
+
const handleLogin = async () => {
|
|
59
|
+
const result = await core.identity.identify({
|
|
60
|
+
user_id: 'user-123',
|
|
61
|
+
email: 'user@example.com',
|
|
62
|
+
name: 'John Doe',
|
|
63
|
+
});
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Identifying Users
|
|
69
|
+
|
|
70
|
+
### Anonymous Identification
|
|
71
|
+
|
|
72
|
+
Automatically happens on SDK initialization:
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
75
|
+
// Happens automatically when TeardownProvider mounts
|
|
76
|
+
await core.identity.identify();
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
This creates an anonymous session with:
|
|
80
|
+
- Unique device ID
|
|
81
|
+
- Session ID
|
|
82
|
+
- Device information
|
|
83
|
+
- Version status
|
|
84
|
+
|
|
85
|
+
### Identified Users
|
|
86
|
+
|
|
87
|
+
Identify users with their information:
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
const result = await core.identity.identify({
|
|
91
|
+
user_id: 'user-123',
|
|
92
|
+
email: 'user@example.com',
|
|
93
|
+
name: 'John Doe',
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
if (result.success) {
|
|
97
|
+
console.log('Session:', result.data.session_id);
|
|
98
|
+
console.log('Device:', result.data.device_id);
|
|
99
|
+
console.log('Persona:', result.data.persona_id);
|
|
100
|
+
console.log('Token:', result.data.token);
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Persona Object
|
|
105
|
+
|
|
106
|
+
```typescript
|
|
107
|
+
type Persona = {
|
|
108
|
+
user_id?: string;
|
|
109
|
+
email?: string;
|
|
110
|
+
name?: string;
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
All fields are optional. You can identify with just an email, just a user_id, or any combination.
|
|
115
|
+
|
|
116
|
+
## Session Management
|
|
117
|
+
|
|
118
|
+
### Getting Current Session
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
// Via hook (reactive)
|
|
122
|
+
const session = useSession();
|
|
123
|
+
|
|
124
|
+
// Via client (direct)
|
|
125
|
+
const session = core.identity.getSessionState();
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Session Object
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
type Session = {
|
|
132
|
+
session_id: string; // Unique session identifier
|
|
133
|
+
device_id: string; // Unique device identifier
|
|
134
|
+
persona_id: string; // User/persona identifier
|
|
135
|
+
token: string; // Authentication token
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Refreshing Session
|
|
140
|
+
|
|
141
|
+
Re-identify the current persona to refresh session data:
|
|
142
|
+
|
|
143
|
+
```typescript
|
|
144
|
+
const result = await core.identity.refresh();
|
|
145
|
+
|
|
146
|
+
if (result.success) {
|
|
147
|
+
console.log('Session refreshed');
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Note: `refresh()` only works if already identified.
|
|
152
|
+
|
|
153
|
+
## Logging Out
|
|
154
|
+
|
|
155
|
+
Reset the identity state to anonymous:
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
core.identity.reset();
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
This will:
|
|
162
|
+
- Clear persisted session data
|
|
163
|
+
- Set state to "unidentified"
|
|
164
|
+
- Emit state change event
|
|
165
|
+
- Trigger re-identification on next app open
|
|
166
|
+
|
|
167
|
+
## Listening to State Changes
|
|
168
|
+
|
|
169
|
+
### Via Hook
|
|
170
|
+
|
|
171
|
+
```typescript
|
|
172
|
+
const session = useSession();
|
|
173
|
+
|
|
174
|
+
// Automatically re-renders on state changes
|
|
175
|
+
useEffect(() => {
|
|
176
|
+
console.log('Session changed:', session);
|
|
177
|
+
}, [session]);
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### Via Event Listener
|
|
181
|
+
|
|
182
|
+
```typescript
|
|
183
|
+
useEffect(() => {
|
|
184
|
+
const unsubscribe = core.identity.onIdentifyStateChange((state) => {
|
|
185
|
+
console.log('State changed:', state.type);
|
|
186
|
+
|
|
187
|
+
if (state.type === 'identified') {
|
|
188
|
+
console.log('User session:', state.session);
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
return unsubscribe;
|
|
193
|
+
}, []);
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
## Identity State
|
|
197
|
+
|
|
198
|
+
Get the full identity state (not just session):
|
|
199
|
+
|
|
200
|
+
```typescript
|
|
201
|
+
const state = core.identity.getIdentifyState();
|
|
202
|
+
|
|
203
|
+
switch (state.type) {
|
|
204
|
+
case 'unidentified':
|
|
205
|
+
// No session yet
|
|
206
|
+
break;
|
|
207
|
+
case 'identifying':
|
|
208
|
+
// Identifying in progress
|
|
209
|
+
break;
|
|
210
|
+
case 'identified':
|
|
211
|
+
// Has session and version info
|
|
212
|
+
console.log(state.session);
|
|
213
|
+
console.log(state.version_info);
|
|
214
|
+
break;
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
## Error Handling
|
|
219
|
+
|
|
220
|
+
All identity operations return `AsyncResult`:
|
|
221
|
+
|
|
222
|
+
```typescript
|
|
223
|
+
const result = await core.identity.identify({
|
|
224
|
+
email: 'invalid-email',
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
if (!result.success) {
|
|
228
|
+
console.error('Identification failed:', result.error);
|
|
229
|
+
// Handle error (show message, retry, etc.)
|
|
230
|
+
}
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
Common errors:
|
|
234
|
+
- Network errors
|
|
235
|
+
- Invalid API credentials
|
|
236
|
+
- Validation errors (422)
|
|
237
|
+
- Server errors
|
|
238
|
+
|
|
239
|
+
## Best Practices
|
|
240
|
+
|
|
241
|
+
### 1. Let Auto-Initialization Happen
|
|
242
|
+
|
|
243
|
+
```typescript
|
|
244
|
+
// ✅ Good - automatic anonymous identification
|
|
245
|
+
<TeardownProvider core={teardown}>
|
|
246
|
+
<App />
|
|
247
|
+
</TeardownProvider>
|
|
248
|
+
|
|
249
|
+
// ❌ Bad - don't manually call identify on mount
|
|
250
|
+
useEffect(() => {
|
|
251
|
+
core.identity.identify();
|
|
252
|
+
}, []);
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
### 2. Identify on Login/Signup
|
|
256
|
+
|
|
257
|
+
```typescript
|
|
258
|
+
const handleLogin = async (email: string, password: string) => {
|
|
259
|
+
// Your auth logic
|
|
260
|
+
const user = await login(email, password);
|
|
261
|
+
|
|
262
|
+
// Identify with Teardown
|
|
263
|
+
await core.identity.identify({
|
|
264
|
+
user_id: user.id,
|
|
265
|
+
email: user.email,
|
|
266
|
+
name: user.name,
|
|
267
|
+
});
|
|
268
|
+
};
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
### 3. Reset on Logout
|
|
272
|
+
|
|
273
|
+
```typescript
|
|
274
|
+
const handleLogout = async () => {
|
|
275
|
+
// Your logout logic
|
|
276
|
+
await logout();
|
|
277
|
+
|
|
278
|
+
// Reset Teardown identity
|
|
279
|
+
core.identity.reset();
|
|
280
|
+
};
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
### 4. Use the Hook for Reactive UI
|
|
284
|
+
|
|
285
|
+
```typescript
|
|
286
|
+
function UserProfile() {
|
|
287
|
+
const session = useSession();
|
|
288
|
+
|
|
289
|
+
if (!session) {
|
|
290
|
+
return <LoginPrompt />;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return <Profile personaId={session.persona_id} />;
|
|
294
|
+
}
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
## Next Steps
|
|
298
|
+
|
|
299
|
+
- [Force Updates](./04-force-updates.mdx)
|
|
300
|
+
- [Device Information](./05-device-info.mdx)
|
|
301
|
+
- [API Reference](./07-api-reference.mdx)
|
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
# Force Updates
|
|
2
|
+
|
|
3
|
+
Manage app version requirements and force updates with the ForceUpdateClient.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The ForceUpdateClient automatically:
|
|
8
|
+
- Checks app version against your backend
|
|
9
|
+
- Monitors app state changes (background/foreground)
|
|
10
|
+
- Throttles version checks to prevent excessive API calls
|
|
11
|
+
- Emits version status changes for UI updates
|
|
12
|
+
|
|
13
|
+
## Version Status Types
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
type VersionStatus =
|
|
17
|
+
| { type: "initializing" } // Initial state
|
|
18
|
+
| { type: "checking" } // Currently checking version
|
|
19
|
+
| { type: "up_to_date" } // Version is current
|
|
20
|
+
| { type: "update_available" } // New version available (optional)
|
|
21
|
+
| { type: "update_recommended" } // Update is recommended
|
|
22
|
+
| { type: "update_required" } // Update is required
|
|
23
|
+
| { type: "disabled" } // Version/build is disabled
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Using Force Updates
|
|
27
|
+
|
|
28
|
+
### Access via Hook
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
import { useForceUpdate } from '@teardown/react-native';
|
|
32
|
+
|
|
33
|
+
function MyComponent() {
|
|
34
|
+
const { versionStatus, isUpdateRequired, isUpdateAvailable } = useForceUpdate();
|
|
35
|
+
|
|
36
|
+
if (isUpdateRequired) {
|
|
37
|
+
return <UpdateRequiredScreen />;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (isUpdateAvailable) {
|
|
41
|
+
return <UpdateAvailableBanner />;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return <App />;
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Hook Return Value
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
type UseForceUpdateResult = {
|
|
52
|
+
versionStatus: VersionStatus;
|
|
53
|
+
isUpdateAvailable: boolean; // true if any update exists
|
|
54
|
+
isUpdateRequired: boolean; // true if update is required
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Configuration
|
|
59
|
+
|
|
60
|
+
Configure force update behavior when initializing TeardownCore:
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
const teardown = new TeardownCore({
|
|
64
|
+
// ... other options
|
|
65
|
+
forceUpdate: {
|
|
66
|
+
// Minimum time between foreground checks (default: 30000ms = 30s)
|
|
67
|
+
throttleMs: 30_000,
|
|
68
|
+
|
|
69
|
+
// Minimum time since last successful check (default: 300000ms = 5min)
|
|
70
|
+
checkCooldownMs: 300_000,
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Configuration Options
|
|
76
|
+
|
|
77
|
+
| Option | Default | Description |
|
|
78
|
+
|--------|---------|-------------|
|
|
79
|
+
| `throttleMs` | 30000 | Min milliseconds between foreground transitions before checking |
|
|
80
|
+
| `checkCooldownMs` | 300000 | Min milliseconds since last successful check before re-checking |
|
|
81
|
+
|
|
82
|
+
## How It Works
|
|
83
|
+
|
|
84
|
+
### Automatic Checks
|
|
85
|
+
|
|
86
|
+
The SDK automatically checks version in these scenarios:
|
|
87
|
+
|
|
88
|
+
1. **Initial Identification**
|
|
89
|
+
- When the SDK initializes
|
|
90
|
+
- When `identify()` is called
|
|
91
|
+
|
|
92
|
+
2. **App Foreground**
|
|
93
|
+
- When app returns from background
|
|
94
|
+
- Subject to throttle and cooldown timers
|
|
95
|
+
|
|
96
|
+
### Check Flow
|
|
97
|
+
|
|
98
|
+
```
|
|
99
|
+
App goes to foreground
|
|
100
|
+
↓
|
|
101
|
+
Check throttle timer (last foreground < throttleMs ago?)
|
|
102
|
+
↓ No
|
|
103
|
+
Check cooldown timer (last check < checkCooldownMs ago?)
|
|
104
|
+
↓ No
|
|
105
|
+
Call identify() to refresh version status
|
|
106
|
+
↓
|
|
107
|
+
Update version status
|
|
108
|
+
↓
|
|
109
|
+
Emit VERSION_STATUS_CHANGED event
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Throttle vs Cooldown
|
|
113
|
+
|
|
114
|
+
- **Throttle**: Prevents checks when rapidly switching apps
|
|
115
|
+
- **Cooldown**: Prevents excessive API calls even with long background periods
|
|
116
|
+
|
|
117
|
+
Example:
|
|
118
|
+
```typescript
|
|
119
|
+
// User switches between apps rapidly:
|
|
120
|
+
// 10:00:00 - Foreground (check)
|
|
121
|
+
// 10:00:05 - Foreground (throttled - too soon)
|
|
122
|
+
// 10:00:45 - Foreground (check - passed throttle)
|
|
123
|
+
// 10:00:50 - Foreground (cooldown - last check < 5min ago)
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Building Update UI
|
|
127
|
+
|
|
128
|
+
### Required Update Screen
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
function UpdateRequiredScreen() {
|
|
132
|
+
const { isUpdateRequired, versionStatus } = useForceUpdate();
|
|
133
|
+
|
|
134
|
+
if (!isUpdateRequired) return null;
|
|
135
|
+
|
|
136
|
+
return (
|
|
137
|
+
<View style={styles.container}>
|
|
138
|
+
<Text>Update Required</Text>
|
|
139
|
+
<Text>Please update to continue using the app</Text>
|
|
140
|
+
<Button
|
|
141
|
+
title="Update Now"
|
|
142
|
+
onPress={() => Linking.openURL('market://details?id=com.yourapp')}
|
|
143
|
+
/>
|
|
144
|
+
</View>
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Optional Update Banner
|
|
150
|
+
|
|
151
|
+
```typescript
|
|
152
|
+
function UpdateBanner() {
|
|
153
|
+
const { isUpdateAvailable, isUpdateRequired } = useForceUpdate();
|
|
154
|
+
const [dismissed, setDismissed] = useState(false);
|
|
155
|
+
|
|
156
|
+
// Don't show if required (use dedicated screen)
|
|
157
|
+
if (isUpdateRequired || !isUpdateAvailable || dismissed) {
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return (
|
|
162
|
+
<View style={styles.banner}>
|
|
163
|
+
<Text>A new version is available</Text>
|
|
164
|
+
<Button title="Update" onPress={handleUpdate} />
|
|
165
|
+
<Button title="Later" onPress={() => setDismissed(true)} />
|
|
166
|
+
</View>
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### Full-Screen Takeover
|
|
172
|
+
|
|
173
|
+
Example from the SDK:
|
|
174
|
+
|
|
175
|
+
```typescript
|
|
176
|
+
function FullscreenTakeover() {
|
|
177
|
+
const { isUpdateRequired } = useForceUpdate();
|
|
178
|
+
|
|
179
|
+
if (!isUpdateRequired) return null;
|
|
180
|
+
|
|
181
|
+
return (
|
|
182
|
+
<Modal visible={true} animationType="fade">
|
|
183
|
+
<UpdateRequiredScreen />
|
|
184
|
+
</Modal>
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// In your layout
|
|
189
|
+
<TeardownProvider core={teardown}>
|
|
190
|
+
<App />
|
|
191
|
+
<FullscreenTakeover />
|
|
192
|
+
</TeardownProvider>
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## Listening to Status Changes
|
|
196
|
+
|
|
197
|
+
### Via Hook (Recommended)
|
|
198
|
+
|
|
199
|
+
```typescript
|
|
200
|
+
const { versionStatus } = useForceUpdate();
|
|
201
|
+
|
|
202
|
+
useEffect(() => {
|
|
203
|
+
console.log('Version status:', versionStatus.type);
|
|
204
|
+
}, [versionStatus]);
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### Via Event Listener
|
|
208
|
+
|
|
209
|
+
```typescript
|
|
210
|
+
import { useTeardown } from '@teardown/react-native';
|
|
211
|
+
|
|
212
|
+
function MyComponent() {
|
|
213
|
+
const { core } = useTeardown();
|
|
214
|
+
|
|
215
|
+
useEffect(() => {
|
|
216
|
+
const unsubscribe = core.forceUpdate.onVersionStatusChange((status) => {
|
|
217
|
+
console.log('Status changed:', status.type);
|
|
218
|
+
|
|
219
|
+
if (status.type === 'update_required') {
|
|
220
|
+
// Show update UI
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
return unsubscribe;
|
|
225
|
+
}, []);
|
|
226
|
+
}
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
## Getting Current Status
|
|
230
|
+
|
|
231
|
+
```typescript
|
|
232
|
+
// Via hook (reactive)
|
|
233
|
+
const { versionStatus } = useForceUpdate();
|
|
234
|
+
|
|
235
|
+
// Via client (direct)
|
|
236
|
+
const status = core.forceUpdate.getVersionStatus();
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
## Platform-Specific Store Links
|
|
240
|
+
|
|
241
|
+
### iOS
|
|
242
|
+
|
|
243
|
+
```typescript
|
|
244
|
+
const APP_STORE_ID = 'your-app-id';
|
|
245
|
+
const APP_STORE_URL = `https://apps.apple.com/app/id${APP_STORE_ID}`;
|
|
246
|
+
|
|
247
|
+
<Button
|
|
248
|
+
title="Update"
|
|
249
|
+
onPress={() => Linking.openURL(APP_STORE_URL)}
|
|
250
|
+
/>
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
### Android
|
|
254
|
+
|
|
255
|
+
```typescript
|
|
256
|
+
const PACKAGE_NAME = 'com.yourcompany.yourapp';
|
|
257
|
+
const PLAY_STORE_URL = `market://details?id=${PACKAGE_NAME}`;
|
|
258
|
+
const PLAY_STORE_WEB = `https://play.google.com/store/apps/details?id=${PACKAGE_NAME}`;
|
|
259
|
+
|
|
260
|
+
const handleUpdate = async () => {
|
|
261
|
+
const supported = await Linking.canOpenURL(PLAY_STORE_URL);
|
|
262
|
+
const url = supported ? PLAY_STORE_URL : PLAY_STORE_WEB;
|
|
263
|
+
Linking.openURL(url);
|
|
264
|
+
};
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
### Cross-Platform
|
|
268
|
+
|
|
269
|
+
```typescript
|
|
270
|
+
import { Platform, Linking } from 'react-native';
|
|
271
|
+
|
|
272
|
+
const STORE_URL = Platform.select({
|
|
273
|
+
ios: 'https://apps.apple.com/app/id123456789',
|
|
274
|
+
android: 'market://details?id=com.yourapp',
|
|
275
|
+
default: 'https://yourapp.com/download',
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
<Button
|
|
279
|
+
title="Update Now"
|
|
280
|
+
onPress={() => Linking.openURL(STORE_URL)}
|
|
281
|
+
/>
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
## Version Status in Backend
|
|
285
|
+
|
|
286
|
+
Version status is determined by your backend configuration:
|
|
287
|
+
|
|
288
|
+
- `UP_TO_DATE`: Current version matches or is newer than minimum
|
|
289
|
+
- `UPDATE_AVAILABLE`: New version exists, but not required
|
|
290
|
+
- `UPDATE_RECOMMENDED`: New version exists, recommended to update
|
|
291
|
+
- `UPDATE_REQUIRED`: Current version is below minimum required
|
|
292
|
+
- `DISABLED`: This specific version/build is disabled
|
|
293
|
+
|
|
294
|
+
Configure these in your Teardown dashboard under project settings.
|
|
295
|
+
|
|
296
|
+
## Best Practices
|
|
297
|
+
|
|
298
|
+
### 1. Use Full-Screen Takeover for Required Updates
|
|
299
|
+
|
|
300
|
+
```typescript
|
|
301
|
+
// ✅ Good - blocks app usage
|
|
302
|
+
{isUpdateRequired && <UpdateRequiredModal />}
|
|
303
|
+
|
|
304
|
+
// ❌ Bad - user can dismiss and continue with outdated version
|
|
305
|
+
{isUpdateRequired && <UpdateBanner dismissible />}
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
### 2. Make Optional Updates Dismissible
|
|
309
|
+
|
|
310
|
+
```typescript
|
|
311
|
+
// ✅ Good - user can choose when to update
|
|
312
|
+
{isUpdateAvailable && !isUpdateRequired && <DismissibleBanner />}
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
### 3. Respect User Decisions
|
|
316
|
+
|
|
317
|
+
```typescript
|
|
318
|
+
// ✅ Good - remember dismissal for session
|
|
319
|
+
const [dismissed, setDismissed] = useState(false);
|
|
320
|
+
|
|
321
|
+
// ❌ Bad - annoying the user every render
|
|
322
|
+
{isUpdateAvailable && <Banner />}
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
### 4. Provide Clear CTAs
|
|
326
|
+
|
|
327
|
+
```typescript
|
|
328
|
+
// ✅ Good - clear action
|
|
329
|
+
<Button title="Update Now" onPress={openStore} />
|
|
330
|
+
|
|
331
|
+
// ❌ Bad - vague
|
|
332
|
+
<Button title="OK" onPress={openStore} />
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
## Next Steps
|
|
336
|
+
|
|
337
|
+
- [Device Information](./05-device-info.mdx)
|
|
338
|
+
- [Hooks Reference](./08-hooks-reference.mdx)
|
|
339
|
+
- [Advanced Usage](./09-advanced.mdx)
|