@teardown/react-native 2.0.4 → 2.0.10

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 CHANGED
@@ -2,49 +2,90 @@
2
2
 
3
3
  Comprehensive SDK for managing device identity, force updates, logging, and analytics in React Native and Expo applications.
4
4
 
5
+ **[Documentation](https://teardown.dev/docs)** | **[Dashboard](https://dash.teardown.dev)**
6
+
5
7
  ## Features
6
8
 
7
- - 🔐 **Device & User Identity** - Unique device fingerprinting and user session management
8
- - 🔄 **Force Updates** - Automatic version checking with optional or required update flows
9
- - 📱 **Device Information** - Comprehensive device, OS, and app information collection
10
- - 💾 **Storage** - Namespaced persistent storage with platform adapters
11
- - 📝 **Logging** - Structured logging system with debug modes
12
- - **Performance** - Optimized with caching, throttling, and efficient state management
13
- - 🎯 **Type Safety** - Full TypeScript support with runtime validation
9
+ - **Device & User Identity** - Unique device fingerprinting and user session management
10
+ - **Force Updates** - Automatic version checking with optional or required update flows
11
+ - **Device Information** - Comprehensive device, OS, and app information collection
12
+ - **Storage** - Namespaced persistent storage with platform adapters
13
+ - **Logging** - Structured logging system with debug modes
14
+ - **Performance** - Optimized with caching, throttling, and efficient state management
15
+ - **Type Safety** - Full TypeScript support with runtime validation
14
16
 
15
17
  ## Installation
16
18
 
17
19
  ```bash
20
+ npm install @teardown/react-native
21
+ # or
22
+ yarn add @teardown/react-native
23
+ # or
18
24
  bun add @teardown/react-native
19
25
  ```
20
26
 
21
27
  ### Peer Dependencies
22
28
 
29
+ Choose adapters based on your project setup:
30
+
31
+ **Expo projects:**
32
+ ```bash
33
+ npx expo install expo-device expo-application
34
+ ```
35
+
36
+ **Bare React Native projects:**
23
37
  ```bash
24
- bun add react react-native zod
25
- bun add expo-application expo-device expo-updates
26
- bun add react-native-device-info
38
+ npm install react-native-device-info
27
39
  ```
28
40
 
41
+ **Storage (choose one):**
42
+ ```bash
43
+ # MMKV (recommended - faster)
44
+ npm install react-native-mmkv
45
+
46
+ # or AsyncStorage
47
+ npm install @react-native-async-storage/async-storage
48
+ ```
49
+
50
+ ## Getting Your Credentials
51
+
52
+ 1. Sign up or log in at [dash.teardown.dev](https://dash.teardown.dev)
53
+ 2. Create or select a project
54
+ 3. Copy your `org_id`, `project_id`, and `api_key` from project settings
55
+
29
56
  ## Quick Start
30
57
 
31
58
  ### 1. Initialize the SDK
32
59
 
60
+ Create a file (e.g., `lib/teardown.ts`):
61
+
62
+ **Expo Setup:**
33
63
  ```tsx
34
64
  import { TeardownCore } from '@teardown/react-native';
35
- import { ExpoDeviceAdapter } from '@teardown/react-native/expo';
36
- import { createMMKVStorageFactory } from '@teardown/react-native/mmkv';
65
+ import { ExpoDeviceAdapter } from '@teardown/react-native/adapters/expo';
66
+ import { MMKVStorageAdapter } from '@teardown/react-native/adapters/mmkv';
37
67
 
38
68
  export const teardown = new TeardownCore({
39
69
  org_id: 'your-org-id',
40
70
  project_id: 'your-project-id',
41
71
  api_key: 'your-api-key',
42
- storageFactory: createMMKVStorageFactory(),
72
+ storageAdapter: new MMKVStorageAdapter(),
43
73
  deviceAdapter: new ExpoDeviceAdapter(),
44
- forceUpdate: {
45
- throttleMs: 30_000, // 30 seconds
46
- checkCooldownMs: 10_000, // 10 seconds
47
- },
74
+ });
75
+ ```
76
+
77
+ **Bare React Native Setup:**
78
+ ```tsx
79
+ import { TeardownCore } from '@teardown/react-native';
80
+ import { DeviceInfoAdapter } from '@teardown/react-native/adapters/device-info';
81
+ import { AsyncStorageAdapter } from '@teardown/react-native/adapters/async-storage';
82
+
83
+ export const teardown = new TeardownCore({
84
+ org_id: 'your-org-id',
85
+ project_id: 'your-project-id',
86
+ api_key: 'your-api-key',
87
+ storageAdapter: new AsyncStorageAdapter(),
88
+ deviceAdapter: new DeviceInfoAdapter(),
48
89
  });
49
90
  ```
50
91
 
@@ -54,7 +95,7 @@ export const teardown = new TeardownCore({
54
95
  import { TeardownProvider } from '@teardown/react-native';
55
96
  import { teardown } from './lib/teardown';
56
97
 
57
- export default function RootLayout() {
98
+ export default function App() {
58
99
  return (
59
100
  <TeardownProvider core={teardown}>
60
101
  <YourApp />
@@ -66,36 +107,65 @@ export default function RootLayout() {
66
107
  ### 3. Use in components
67
108
 
68
109
  ```tsx
69
- import { useTeardown } from '@teardown/react-native';
110
+ import { useTeardown, useForceUpdate, useSession } from '@teardown/react-native';
70
111
 
71
112
  function YourComponent() {
72
113
  const { core } = useTeardown();
73
-
114
+ const session = useSession();
115
+ const { status, isUpdateRequired } = useForceUpdate();
116
+
74
117
  const handleLogin = async () => {
75
118
  await core.identity.identify({
76
119
  user_id: 'user-123',
77
120
  email: 'user@example.com',
78
121
  });
79
122
  };
80
-
81
- return <Button onPress={handleLogin} />;
123
+
124
+ return <Button onPress={handleLogin} title="Login" />;
82
125
  }
83
126
  ```
84
127
 
128
+ ## Configuration Options
129
+
130
+ ```tsx
131
+ new TeardownCore({
132
+ org_id: string, // Your organization ID
133
+ project_id: string, // Your project ID
134
+ api_key: string, // Your API key
135
+ storageAdapter: adapter, // Storage implementation
136
+ deviceAdapter: adapter, // Device info source
137
+ forceUpdate: {
138
+ throttleMs: 30_000, // Min time between checks (default: 30s)
139
+ checkCooldownMs: 10_000 // Cooldown after check (default: 10s)
140
+ },
141
+ });
142
+ ```
143
+
144
+ ## Available Adapters
145
+
146
+ | Adapter | Import Path | Use Case |
147
+ |---------|-------------|----------|
148
+ | `ExpoDeviceAdapter` | `@teardown/react-native/adapters/expo` | Expo projects |
149
+ | `DeviceInfoAdapter` | `@teardown/react-native/adapters/device-info` | Bare RN projects |
150
+ | `MMKVStorageAdapter` | `@teardown/react-native/adapters/mmkv` | Fast sync storage |
151
+ | `AsyncStorageAdapter` | `@teardown/react-native/adapters/async-storage` | Standard async storage |
152
+
85
153
  ## Documentation
86
154
 
87
- Complete documentation is available in the [docs](./docs) folder:
88
-
89
- - [Getting Started](./docs/01-getting-started.mdx) - Installation and setup
90
- - [Core Concepts](./docs/02-core-concepts.mdx) - Architecture overview
91
- - [Identity & Authentication](./docs/03-identity.mdx) - User session management
92
- - [Force Updates](./docs/04-force-updates.mdx) - Version management
93
- - [Device Information](./docs/05-device-info.mdx) - Device data collection
94
- - [Storage](./docs/06-storage.mdx) - Persistent storage
95
- - [Logging](./docs/07-logging.mdx) - Structured logging
96
- - [API Reference](./docs/08-api-reference.mdx) - Complete API docs
97
- - [Hooks Reference](./docs/09-hooks-reference.mdx) - React hooks
98
- - [Advanced Usage](./docs/10-advanced.mdx) - Advanced patterns
155
+ For detailed guides, visit **[teardown.dev/docs](https://teardown.dev/docs)**
156
+
157
+ Local documentation is also available in the [docs](./docs) folder:
158
+
159
+ - [Getting Started](./docs/01-getting-started.mdx)
160
+ - [Core Concepts](./docs/02-core-concepts.mdx)
161
+ - [Identity & Authentication](./docs/03-identity.mdx)
162
+ - [Force Updates](./docs/04-force-updates.mdx)
163
+ - [Device Information](./docs/05-device-info.mdx)
164
+ - [Storage](./docs/06-storage.mdx)
165
+ - [Logging](./docs/07-logging.mdx)
166
+ - [API Reference](./docs/08-api-reference.mdx)
167
+ - [Hooks Reference](./docs/09-hooks-reference.mdx)
168
+ - [Advanced Usage](./docs/10-advanced.mdx)
99
169
 
100
170
  ## License
101
171
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@teardown/react-native",
3
- "version": "2.0.4",
3
+ "version": "2.0.10",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -32,6 +32,11 @@
32
32
  "types": "./src/exports/adapters/async-storage.ts",
33
33
  "import": "./src/exports/adapters/async-storage.ts",
34
34
  "default": "./src/exports/adapters/async-storage.ts"
35
+ },
36
+ "./adapters/device-info": {
37
+ "types": "./src/exports/adapters/device-info.ts",
38
+ "import": "./src/exports/adapters/device-info.ts",
39
+ "default": "./src/exports/adapters/device-info.ts"
35
40
  }
36
41
  },
37
42
  "scripts": {
@@ -45,9 +50,9 @@
45
50
  "nuke": "cd ../../ && bun run nuke"
46
51
  },
47
52
  "dependencies": {
48
- "@teardown/ingest-api": "0.1.41",
49
- "@teardown/schemas": "0.1.41",
50
- "@teardown/types": "0.1.41",
53
+ "@teardown/ingest-api": "0.1.46",
54
+ "@teardown/schemas": "0.1.46",
55
+ "@teardown/types": "0.1.46",
51
56
  "eventemitter3": "^5.0.1",
52
57
  "react-native-get-random-values": "^2.0.0",
53
58
  "uuid": "^13.0.0",
@@ -62,20 +67,24 @@
62
67
  "expo-updates": "^29.0.12"
63
68
  },
64
69
  "peerDependencies": {
65
- "@react-native-async-storage/async-storage": "^1.21.0 || ^2.0.0",
66
- "react": "*",
67
- "react-native": "*",
68
- "typescript": "*",
70
+ "@react-native-async-storage/async-storage": "^1.21 || ^2.0",
69
71
  "expo-application": "*",
70
72
  "expo-device": "*",
71
73
  "expo-notifications": "*",
72
74
  "expo-secure-store": "*",
73
- "react-native-mmkv": "*"
75
+ "react": "*",
76
+ "react-native": "*",
77
+ "react-native-device-info": "^15",
78
+ "react-native-mmkv": "^4.0.0",
79
+ "typescript": "*"
74
80
  },
75
81
  "peerDependenciesMeta": {
76
82
  "@react-native-async-storage/async-storage": {
77
83
  "optional": true
78
84
  },
85
+ "react-native-device-info": {
86
+ "optional": true
87
+ },
79
88
  "react-native-mmkv": {
80
89
  "optional": true
81
90
  },
@@ -5,7 +5,7 @@ import type { StorageClient } from "../storage";
5
5
 
6
6
  export type { Eden, IngestApi };
7
7
 
8
- const TEARDOWN_INGEST_URL = "https://ingest.teardown.dev";
8
+ const TEARDOWN_INGEST_URL = "http://localhost:4880";// "https://ingest.teardown.dev";
9
9
  const TEARDOWN_API_KEY_HEADER = "td-api-key";
10
10
  const TEARDOWN_ORG_ID_HEADER = "td-org-id";
11
11
  const TEARDOWN_PROJECT_ID_HEADER = "td-project-id";
@@ -0,0 +1,36 @@
1
+ import type {
2
+ ApplicationInfo,
3
+ HardwareInfo,
4
+ OSInfo
5
+ } from "@teardown/schemas";
6
+ import { Platform } from "react-native";
7
+ import DeviceInfo from "react-native-device-info";
8
+ import { DeviceInfoAdapter as BaseInfoAdapterInterface } from "./device.adpater-interface";
9
+
10
+ export class DeviceInfoAdapter extends BaseInfoAdapterInterface {
11
+ get applicationInfo(): ApplicationInfo {
12
+ return {
13
+ version: DeviceInfo.getVersion() ?? "0.0.0",
14
+ build_number: Number.parseInt(
15
+ DeviceInfo.getBuildNumber() ?? "0",
16
+ 10
17
+ ),
18
+ };
19
+ }
20
+
21
+ get hardwareInfo(): HardwareInfo {
22
+ return {
23
+ device_name: DeviceInfo.getDeviceNameSync() ?? "Unknown Device",
24
+ device_brand: DeviceInfo.getBrand() ?? "Unknown Brand",
25
+ device_type: DeviceInfo.getDeviceType?.() ?? "unknown",
26
+ };
27
+ }
28
+
29
+ get osInfo(): OSInfo {
30
+ return {
31
+ platform: this.mapPlatform(Platform.OS),
32
+ name: DeviceInfo.getSystemName() ?? Platform.OS,
33
+ version: DeviceInfo.getSystemVersion() ?? "0.0.0",
34
+ };
35
+ }
36
+ }
@@ -4,6 +4,8 @@ import type {
4
4
  HardwareInfo,
5
5
  OSInfo
6
6
  } from "@teardown/schemas";
7
+ import type { Platform } from "react-native";
8
+ import { DevicePlatformEnum } from "../device.client";
7
9
 
8
10
  /**
9
11
  * An interface for a device adapter.
@@ -39,4 +41,25 @@ export abstract class DeviceInfoAdapter {
39
41
  update: null,
40
42
  });
41
43
  }
44
+
45
+ /**
46
+ * Maps React Native Platform.OS to DevicePlatformEnum
47
+ */
48
+ mapPlatform(platform: typeof Platform.OS): DevicePlatformEnum {
49
+ switch (platform) {
50
+ case "ios":
51
+ return DevicePlatformEnum.IOS;
52
+ case "android":
53
+ return DevicePlatformEnum.ANDROID;
54
+ case "web":
55
+ return DevicePlatformEnum.WEB;
56
+ case "macos":
57
+ return DevicePlatformEnum.MACOS;
58
+ case "windows":
59
+ return DevicePlatformEnum.WINDOWS;
60
+ default:
61
+ return DevicePlatformEnum.UNKNOWN;
62
+ }
63
+ }
64
+
42
65
  }
@@ -1,15 +1,13 @@
1
1
  import type {
2
2
  ApplicationInfo,
3
3
  HardwareInfo,
4
- NotificationsInfo,
5
- OSInfo,
4
+ OSInfo
6
5
  } from "@teardown/schemas";
7
6
  import * as Application from "expo-application";
8
7
  import * as Device from "expo-device";
9
8
  import { Platform } from "react-native";
10
9
 
11
10
  import { DeviceInfoAdapter } from "./device.adpater-interface";
12
- import { DevicePlatformEnum, NotificationPlatformEnum } from "../device.client";
13
11
 
14
12
  /**
15
13
  * Maps expo-device DeviceType to a string representation
@@ -29,26 +27,6 @@ function mapDeviceType(deviceType: Device.DeviceType | null): string {
29
27
  }
30
28
  }
31
29
 
32
- /**
33
- * Maps React Native Platform.OS to DevicePlatformEnum
34
- */
35
- function mapPlatform(platform: typeof Platform.OS): DevicePlatformEnum {
36
- switch (platform) {
37
- case "ios":
38
- return DevicePlatformEnum.IOS;
39
- case "android":
40
- return DevicePlatformEnum.ANDROID;
41
- case "web":
42
- return DevicePlatformEnum.WEB;
43
- case "macos":
44
- return DevicePlatformEnum.MACOS;
45
- case "windows":
46
- return DevicePlatformEnum.WINDOWS;
47
- default:
48
- return DevicePlatformEnum.UNKNOWN;
49
- }
50
- }
51
-
52
30
  export class ExpoDeviceAdapter extends DeviceInfoAdapter {
53
31
  get applicationInfo(): ApplicationInfo {
54
32
  return {
@@ -70,21 +48,9 @@ export class ExpoDeviceAdapter extends DeviceInfoAdapter {
70
48
 
71
49
  get osInfo(): OSInfo {
72
50
  return {
73
- platform: mapPlatform(Platform.OS),
51
+ platform: this.mapPlatform(Platform.OS),
74
52
  name: Device.osName ?? Platform.OS,
75
53
  version: Device.osVersion ?? "0.0.0",
76
54
  };
77
55
  }
78
-
79
- get notificationsInfo(): NotificationsInfo {
80
- return {
81
- push: {
82
- enabled: false,
83
- granted: false,
84
- token: null,
85
- platform: NotificationPlatformEnum.EXPO,
86
- },
87
- };
88
- }
89
-
90
56
  }
@@ -42,7 +42,7 @@ function createMockIdentityClient(initialState?: IdentifyState) {
42
42
  emitter.emit("IDENTIFY_STATE_CHANGED", currentState);
43
43
  currentState = {
44
44
  type: "identified",
45
- session: { session_id: "s1", device_id: "d1", persona_id: "p1", token: "t1" },
45
+ session: { session_id: "s1", device_id: "d1", user_id: "p1", token: "t1" },
46
46
  version_info: { status: nextIdentifyResult.data?.version_info.status ?? IdentifyVersionStatusEnum.UP_TO_DATE, update: null },
47
47
  };
48
48
  emitter.emit("IDENTIFY_STATE_CHANGED", currentState);
@@ -87,7 +87,7 @@ describe("ForceUpdateClient", () => {
87
87
  test("initializes version status when identity is already identified", () => {
88
88
  const mockIdentity = createMockIdentityClient({
89
89
  type: "identified",
90
- session: { session_id: "s1", device_id: "d1", persona_id: "p1", token: "t1" },
90
+ session: { session_id: "s1", device_id: "d1", user_id: "p1", token: "t1" },
91
91
  version_info: { status: IdentifyVersionStatusEnum.UPDATE_REQUIRED, update: null },
92
92
  });
93
93
  const mockLogging = createMockLoggingClient();
@@ -127,7 +127,7 @@ describe("ForceUpdateClient", () => {
127
127
  test("emits status change during initialization when already identified", () => {
128
128
  const mockIdentity = createMockIdentityClient({
129
129
  type: "identified",
130
- session: { session_id: "s1", device_id: "d1", persona_id: "p1", token: "t1" },
130
+ session: { session_id: "s1", device_id: "d1", user_id: "p1", token: "t1" },
131
131
  version_info: { status: IdentifyVersionStatusEnum.UP_TO_DATE, update: null },
132
132
  });
133
133
  const mockLogging = createMockLoggingClient();
@@ -148,7 +148,7 @@ describe("ForceUpdateClient", () => {
148
148
  // Trigger another identify to verify no duplicate
149
149
  mockIdentity.emitter.emit("IDENTIFY_STATE_CHANGED", {
150
150
  type: "identified",
151
- session: { session_id: "s1", device_id: "d1", persona_id: "p1", token: "t1" },
151
+ session: { session_id: "s1", device_id: "d1", user_id: "p1", token: "t1" },
152
152
  version_info: { status: IdentifyVersionStatusEnum.UP_TO_DATE, update: null },
153
153
  });
154
154
 
@@ -181,7 +181,7 @@ describe("ForceUpdateClient", () => {
181
181
  mockIdentity.emitter.emit("IDENTIFY_STATE_CHANGED", { type: "identifying" });
182
182
  mockIdentity.emitter.emit("IDENTIFY_STATE_CHANGED", {
183
183
  type: "identified",
184
- session: { session_id: "s1", device_id: "d1", persona_id: "p1", token: "t1" },
184
+ session: { session_id: "s1", device_id: "d1", user_id: "p1", token: "t1" },
185
185
  version_info: { status: IdentifyVersionStatusEnum.UPDATE_AVAILABLE, update: null },
186
186
  });
187
187
 
@@ -253,7 +253,7 @@ describe("ForceUpdateClient", () => {
253
253
  // After shutdown, emitting should not trigger listener
254
254
  mockIdentity.emitter.emit("IDENTIFY_STATE_CHANGED", {
255
255
  type: "identified",
256
- session: { session_id: "s1", device_id: "d1", persona_id: "p1", token: "t1" },
256
+ session: { session_id: "s1", device_id: "d1", user_id: "p1", token: "t1" },
257
257
  version_info: { status: IdentifyVersionStatusEnum.UPDATE_AVAILABLE, update: null },
258
258
  });
259
259
 
@@ -515,7 +515,7 @@ describe("ForceUpdateClient", () => {
515
515
 
516
516
  mockIdentity.emitter.emit("IDENTIFY_STATE_CHANGED", {
517
517
  type: "identified",
518
- session: { session_id: "s1", device_id: "d1", persona_id: "p1", token: "t1" },
518
+ session: { session_id: "s1", device_id: "d1", user_id: "p1", token: "t1" },
519
519
  version_info: { status: apiStatus, update: null },
520
520
  });
521
521
 
@@ -69,12 +69,31 @@ export type VersionStatusChangeEvents = {
69
69
  };
70
70
 
71
71
  export type ForceUpdateClientOptions = {
72
- /** Min ms between foreground checks (default: 30000) */
72
+ /**
73
+ * Minimum time (ms) between foreground transitions to prevent rapid-fire checks.
74
+ * Measured from the last time the app came to foreground.
75
+ * Prevents checking when user quickly switches apps back and forth.
76
+ * Default: 30000 (30 seconds)
77
+ *
78
+ * Special values:
79
+ * - -1: Disable throttling, check on every foreground (respects checkCooldownMs)
80
+ *
81
+ * Example: If throttleMs is 30s and user backgrounds then foregrounds the app
82
+ * twice within 20s, only the first transition triggers a check.
83
+ */
73
84
  throttleMs?: number;
74
85
  /**
75
- * Min ms since last successful check before re-checking (default: 300000 = 5min)
76
- * Set this to 0 to disable cooldown and check immediately on every foreground transition.
77
- * Set to -1 disable checking entirely.
86
+ * Minimum time (ms) since the last successful version check before checking again.
87
+ * Measured from when the last check completed successfully (not when it started).
88
+ * Prevents unnecessary API calls after we already have fresh version data.
89
+ * Default: 300000 (5 minutes)
90
+ *
91
+ * Special values:
92
+ * - 0: Disable cooldown, check on every foreground (respects throttleMs)
93
+ * - -1: Disable all automatic version checking
94
+ *
95
+ * Example: If checkCooldownMs is 5min and a check completes at 12:00pm,
96
+ * no new checks occur until 12:05pm, even if user foregrounds the app multiple times.
78
97
  */
79
98
  checkCooldownMs?: number;
80
99
  };
@@ -84,7 +103,7 @@ const DEFAULT_OPTIONS: Required<ForceUpdateClientOptions> = {
84
103
  checkCooldownMs: 300_000, // 5 minutes
85
104
  };
86
105
 
87
- export const VERSION_STATUS_STORAGE_KEY = "VERSION_STATUS";
106
+ export const VERSION_STATUS_STORAGE_KEY = "version_status";
88
107
 
89
108
  export class ForceUpdateClient {
90
109
  private emitter = new EventEmitter<VersionStatusChangeEvents>();
@@ -220,7 +239,12 @@ export class ForceUpdateClient {
220
239
  }
221
240
 
222
241
  const now = Date.now();
223
- const throttleOk = !this.lastForegroundTime || now - this.lastForegroundTime >= this.options.throttleMs;
242
+
243
+ // If throttleMs is -1, disable throttling (always pass)
244
+ // Otherwise, check if enough time has passed since last foreground
245
+ const throttleOk = this.options.throttleMs === -1
246
+ || !this.lastForegroundTime
247
+ || now - this.lastForegroundTime >= this.options.throttleMs;
224
248
 
225
249
  // If checkCooldownMs is 0, always allow check (no cooldown)
226
250
  // Otherwise, check if enough time has passed since last successful check