@teardown/react-native 2.0.2 → 2.0.9

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,339 +0,0 @@
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)
@@ -1,324 +0,0 @@
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)