@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,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)
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
# Device Information
|
|
2
|
+
|
|
3
|
+
Collect comprehensive device, OS, and app information with the DeviceClient.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The DeviceClient provides:
|
|
8
|
+
- Unique device identification
|
|
9
|
+
- Comprehensive device information
|
|
10
|
+
- Platform-specific adapters
|
|
11
|
+
- Automatic device fingerprinting
|
|
12
|
+
|
|
13
|
+
## Device Adapters
|
|
14
|
+
|
|
15
|
+
The SDK uses adapters to collect device information across different platforms.
|
|
16
|
+
|
|
17
|
+
### Expo Adapter
|
|
18
|
+
|
|
19
|
+
For Expo applications:
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
import { TeardownCore } from '@teardown/react-native';
|
|
23
|
+
import { ExpoDeviceAdapter } from '@teardown/react-native/expo';
|
|
24
|
+
|
|
25
|
+
const teardown = new TeardownCore({
|
|
26
|
+
// ... other options
|
|
27
|
+
deviceAdapter: new ExpoDeviceAdapter(),
|
|
28
|
+
});
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Required dependencies:
|
|
32
|
+
```bash
|
|
33
|
+
bun add expo-application expo-device expo-updates
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### React Native Adapter
|
|
37
|
+
|
|
38
|
+
For React Native CLI applications:
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
import { TeardownCore } from '@teardown/react-native';
|
|
42
|
+
import { ReactNativeDeviceAdapter } from '@teardown/react-native/device-info';
|
|
43
|
+
|
|
44
|
+
const teardown = new TeardownCore({
|
|
45
|
+
// ... other options
|
|
46
|
+
deviceAdapter: new ReactNativeDeviceAdapter(),
|
|
47
|
+
});
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Required dependencies:
|
|
51
|
+
```bash
|
|
52
|
+
bun add react-native-device-info
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Device ID
|
|
56
|
+
|
|
57
|
+
Every device gets a unique, persistent identifier.
|
|
58
|
+
|
|
59
|
+
### Getting Device ID
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
import { useTeardown } from '@teardown/react-native';
|
|
63
|
+
|
|
64
|
+
function MyComponent() {
|
|
65
|
+
const { core } = useTeardown();
|
|
66
|
+
|
|
67
|
+
const getDeviceId = async () => {
|
|
68
|
+
const deviceId = await core.device.getDeviceId();
|
|
69
|
+
console.log('Device ID:', deviceId);
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### How It Works
|
|
75
|
+
|
|
76
|
+
1. On first launch, a random UUID is generated
|
|
77
|
+
2. ID is persisted in namespaced storage
|
|
78
|
+
3. Same ID is used for all subsequent sessions
|
|
79
|
+
4. Survives app updates and reinstalls (depending on storage)
|
|
80
|
+
|
|
81
|
+
## Device Information
|
|
82
|
+
|
|
83
|
+
### Getting Device Info
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
const deviceInfo = await core.device.getDeviceInfo();
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### DeviceInfo Object
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
type DeviceInfo = {
|
|
93
|
+
// App Information
|
|
94
|
+
app_version: string; // e.g., "1.0.0"
|
|
95
|
+
app_build: string; // e.g., "100"
|
|
96
|
+
app_bundle_id: string; // e.g., "com.company.app"
|
|
97
|
+
|
|
98
|
+
// Device Information
|
|
99
|
+
device_platform: DevicePlatformEnum; // IOS, ANDROID, WEB, etc.
|
|
100
|
+
device_os_version: string; // e.g., "17.0"
|
|
101
|
+
device_model: string; // e.g., "iPhone 14 Pro"
|
|
102
|
+
device_manufacturer: string; // e.g., "Apple"
|
|
103
|
+
|
|
104
|
+
// Update Information (Expo only)
|
|
105
|
+
update?: {
|
|
106
|
+
update_id: string;
|
|
107
|
+
channel: string;
|
|
108
|
+
created_at: string;
|
|
109
|
+
runtime_version: string;
|
|
110
|
+
} | null;
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Platform Enums
|
|
115
|
+
|
|
116
|
+
### DevicePlatformEnum
|
|
117
|
+
|
|
118
|
+
```typescript
|
|
119
|
+
enum DevicePlatformEnum {
|
|
120
|
+
IOS = "IOS",
|
|
121
|
+
ANDROID = "ANDROID",
|
|
122
|
+
WEB = "WEB",
|
|
123
|
+
WINDOWS = "WINDOWS",
|
|
124
|
+
MACOS = "MACOS",
|
|
125
|
+
LINUX = "LINUX",
|
|
126
|
+
PHONE = "PHONE",
|
|
127
|
+
TABLET = "TABLET",
|
|
128
|
+
DESKTOP = "DESKTOP",
|
|
129
|
+
CONSOLE = "CONSOLE",
|
|
130
|
+
TV = "TV",
|
|
131
|
+
WEARABLE = "WEARABLE",
|
|
132
|
+
GAME_CONSOLE = "GAME_CONSOLE",
|
|
133
|
+
VR = "VR",
|
|
134
|
+
UNKNOWN = "UNKNOWN",
|
|
135
|
+
OTHER = "OTHER",
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## Creating Custom Adapters
|
|
140
|
+
|
|
141
|
+
You can create custom device adapters for specific platforms or needs.
|
|
142
|
+
|
|
143
|
+
### Adapter Interface
|
|
144
|
+
|
|
145
|
+
```typescript
|
|
146
|
+
interface DeviceInfoAdapter {
|
|
147
|
+
getDeviceInfo(): Promise<DeviceInfo>;
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Example: Custom Web Adapter
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
class WebDeviceAdapter implements DeviceInfoAdapter {
|
|
155
|
+
async getDeviceInfo(): Promise<DeviceInfo> {
|
|
156
|
+
return {
|
|
157
|
+
app_version: '1.0.0',
|
|
158
|
+
app_build: '1',
|
|
159
|
+
app_bundle_id: 'com.yourapp.web',
|
|
160
|
+
device_platform: DevicePlatformEnum.WEB,
|
|
161
|
+
device_os_version: navigator.userAgent,
|
|
162
|
+
device_model: 'Web Browser',
|
|
163
|
+
device_manufacturer: this.getBrowserName(),
|
|
164
|
+
update: null,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
private getBrowserName(): string {
|
|
169
|
+
const ua = navigator.userAgent;
|
|
170
|
+
if (ua.includes('Chrome')) return 'Chrome';
|
|
171
|
+
if (ua.includes('Firefox')) return 'Firefox';
|
|
172
|
+
if (ua.includes('Safari')) return 'Safari';
|
|
173
|
+
return 'Unknown';
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Use it
|
|
178
|
+
const teardown = new TeardownCore({
|
|
179
|
+
deviceAdapter: new WebDeviceAdapter(),
|
|
180
|
+
// ...
|
|
181
|
+
});
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
## Expo Update Information
|
|
185
|
+
|
|
186
|
+
When using Expo with OTA updates, the SDK automatically includes update information:
|
|
187
|
+
|
|
188
|
+
```typescript
|
|
189
|
+
{
|
|
190
|
+
update: {
|
|
191
|
+
update_id: "abc123",
|
|
192
|
+
channel: "production",
|
|
193
|
+
created_at: "2024-01-01T00:00:00Z",
|
|
194
|
+
runtime_version: "1.0.0",
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
This helps track which OTA update version users are running.
|
|
200
|
+
|
|
201
|
+
## Usage in Identification
|
|
202
|
+
|
|
203
|
+
Device information is automatically sent during identification:
|
|
204
|
+
|
|
205
|
+
```typescript
|
|
206
|
+
await core.identity.identify({
|
|
207
|
+
user_id: 'user-123',
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
// Automatically includes:
|
|
211
|
+
// - Device ID
|
|
212
|
+
// - Device platform
|
|
213
|
+
// - OS version
|
|
214
|
+
// - App version
|
|
215
|
+
// - Device model
|
|
216
|
+
// - Manufacturer
|
|
217
|
+
// - Update info (if Expo)
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
This data is visible in your Teardown dashboard for analytics and debugging.
|
|
221
|
+
|
|
222
|
+
## Platform Detection Examples
|
|
223
|
+
|
|
224
|
+
### Check if iOS
|
|
225
|
+
|
|
226
|
+
```typescript
|
|
227
|
+
const deviceInfo = await core.device.getDeviceInfo();
|
|
228
|
+
const isIOS = deviceInfo.device_platform === DevicePlatformEnum.IOS;
|
|
229
|
+
|
|
230
|
+
if (isIOS) {
|
|
231
|
+
// iOS-specific logic
|
|
232
|
+
}
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### Check Version
|
|
236
|
+
|
|
237
|
+
```typescript
|
|
238
|
+
const deviceInfo = await core.device.getDeviceInfo();
|
|
239
|
+
const version = deviceInfo.app_version;
|
|
240
|
+
|
|
241
|
+
if (compareVersions(version, '2.0.0') < 0) {
|
|
242
|
+
// Running version older than 2.0.0
|
|
243
|
+
}
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### Check if Tablet
|
|
247
|
+
|
|
248
|
+
```typescript
|
|
249
|
+
const deviceInfo = await core.device.getDeviceInfo();
|
|
250
|
+
const isTablet = deviceInfo.device_platform === DevicePlatformEnum.TABLET;
|
|
251
|
+
|
|
252
|
+
if (isTablet) {
|
|
253
|
+
// Show tablet-optimized UI
|
|
254
|
+
}
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
## Storage
|
|
258
|
+
|
|
259
|
+
Device ID is stored in namespaced storage:
|
|
260
|
+
|
|
261
|
+
```
|
|
262
|
+
Key: teardown:v1:device:deviceId
|
|
263
|
+
Value: "uuid-generated-on-first-launch"
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
## Best Practices
|
|
267
|
+
|
|
268
|
+
### 1. Cache Device Info If Needed Frequently
|
|
269
|
+
|
|
270
|
+
```typescript
|
|
271
|
+
// ❌ Bad - fetches every render
|
|
272
|
+
function MyComponent() {
|
|
273
|
+
const deviceInfo = await core.device.getDeviceInfo();
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// ✅ Good - fetch once and cache
|
|
277
|
+
function MyComponent() {
|
|
278
|
+
const [deviceInfo, setDeviceInfo] = useState(null);
|
|
279
|
+
|
|
280
|
+
useEffect(() => {
|
|
281
|
+
core.device.getDeviceInfo().then(setDeviceInfo);
|
|
282
|
+
}, []);
|
|
283
|
+
}
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
### 2. Use Platform-Specific Code When Needed
|
|
287
|
+
|
|
288
|
+
```typescript
|
|
289
|
+
const deviceInfo = await core.device.getDeviceInfo();
|
|
290
|
+
|
|
291
|
+
if (deviceInfo.device_platform === DevicePlatformEnum.IOS) {
|
|
292
|
+
// iOS-specific behavior
|
|
293
|
+
} else if (deviceInfo.device_platform === DevicePlatformEnum.ANDROID) {
|
|
294
|
+
// Android-specific behavior
|
|
295
|
+
}
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
### 3. Don't Manually Generate Device IDs
|
|
299
|
+
|
|
300
|
+
```typescript
|
|
301
|
+
// ❌ Bad - inconsistent with SDK
|
|
302
|
+
const myDeviceId = uuid.v4();
|
|
303
|
+
|
|
304
|
+
// ✅ Good - use SDK's device ID
|
|
305
|
+
const deviceId = await core.device.getDeviceId();
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
## Privacy Considerations
|
|
309
|
+
|
|
310
|
+
The SDK collects:
|
|
311
|
+
- ✅ Device model and OS (for compatibility)
|
|
312
|
+
- ✅ App version (for update management)
|
|
313
|
+
- ✅ Generated device ID (for analytics)
|
|
314
|
+
- ❌ No personal information
|
|
315
|
+
- ❌ No advertising IDs
|
|
316
|
+
- ❌ No contacts or photos
|
|
317
|
+
|
|
318
|
+
Device ID is randomly generated and not tied to hardware identifiers.
|
|
319
|
+
|
|
320
|
+
## Next Steps
|
|
321
|
+
|
|
322
|
+
- [Storage](./06-storage.mdx)
|
|
323
|
+
- [Logging](./06-logging.mdx)
|
|
324
|
+
- [API Reference](./07-api-reference.mdx)
|