@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 +104 -34
- package/package.json +18 -9
- package/src/clients/api/api.client.ts +1 -1
- package/src/clients/device/adapters/device-info.adapter.ts +36 -0
- package/src/clients/device/adapters/device.adpater-interface.ts +23 -0
- package/src/clients/device/adapters/expo.adapter.ts +2 -36
- package/src/clients/force-update/force-update.client.test.ts +7 -7
- package/src/clients/force-update/force-update.client.ts +30 -6
- package/src/clients/identity/identity.client.test.ts +20 -20
- package/src/clients/identity/identity.client.ts +5 -6
- package/src/clients/storage/adapters/async-storage.adapter.ts +18 -20
- package/src/clients/storage/adapters/storage.adpater-interface.ts +1 -1
- package/src/clients/storage/storage.client.ts +17 -1
- package/src/exports/adapters/device-info.ts +1 -0
- package/src/teardown.core.ts +3 -1
- package/docs/01-getting-started.mdx +0 -147
- package/docs/02-core-concepts.mdx +0 -188
- package/docs/03-identity.mdx +0 -301
- package/docs/04-force-updates.mdx +0 -339
- package/docs/05-device-info.mdx +0 -324
- package/docs/06-logging.mdx +0 -345
- package/docs/06-storage.mdx +0 -349
- package/docs/07-api-reference.mdx +0 -472
- package/docs/07-logging.mdx +0 -345
- package/docs/08-api-reference.mdx +0 -472
- package/docs/08-hooks-reference.mdx +0 -476
- package/docs/09-advanced.mdx +0 -563
- package/docs/09-hooks-reference.mdx +0 -476
- package/docs/10-advanced.mdx +0 -563
|
@@ -27,7 +27,7 @@ function createMockLoggingClient() {
|
|
|
27
27
|
debug: (message: string, ...args: unknown[]) => logs.push({ level: "debug", message, args }),
|
|
28
28
|
}),
|
|
29
29
|
getLogs: () => logs,
|
|
30
|
-
clearLogs: () => logs.length = 0,
|
|
30
|
+
clearLogs: () => { logs.length = 0; },
|
|
31
31
|
};
|
|
32
32
|
}
|
|
33
33
|
|
|
@@ -48,7 +48,7 @@ function createMockUtilsClient() {
|
|
|
48
48
|
let uuidCounter = 0;
|
|
49
49
|
return {
|
|
50
50
|
generateRandomUUID: async () => `mock-uuid-${++uuidCounter}`,
|
|
51
|
-
resetCounter: () => uuidCounter = 0,
|
|
51
|
+
resetCounter: () => { uuidCounter = 0; },
|
|
52
52
|
};
|
|
53
53
|
}
|
|
54
54
|
|
|
@@ -90,12 +90,12 @@ type ApiCallRecord = {
|
|
|
90
90
|
|
|
91
91
|
function createMockApiClient(options: {
|
|
92
92
|
success?: boolean;
|
|
93
|
-
versionStatus?: IdentifyVersionStatusEnum;
|
|
93
|
+
versionStatus?: (typeof IdentifyVersionStatusEnum)[keyof typeof IdentifyVersionStatusEnum];
|
|
94
94
|
errorStatus?: number;
|
|
95
95
|
errorMessage?: string;
|
|
96
96
|
sessionId?: string;
|
|
97
97
|
deviceId?: string;
|
|
98
|
-
|
|
98
|
+
user_id?: string;
|
|
99
99
|
token?: string;
|
|
100
100
|
throwError?: Error;
|
|
101
101
|
} = {}) {
|
|
@@ -106,7 +106,7 @@ function createMockApiClient(options: {
|
|
|
106
106
|
errorMessage,
|
|
107
107
|
sessionId = "session-123",
|
|
108
108
|
deviceId = "device-123",
|
|
109
|
-
|
|
109
|
+
user_id = "user-123",
|
|
110
110
|
token = "token-123",
|
|
111
111
|
throwError,
|
|
112
112
|
} = options;
|
|
@@ -143,7 +143,7 @@ function createMockApiClient(options: {
|
|
|
143
143
|
data: {
|
|
144
144
|
session_id: sessionId,
|
|
145
145
|
device_id: deviceId,
|
|
146
|
-
|
|
146
|
+
user_id: user_id,
|
|
147
147
|
token: token,
|
|
148
148
|
version_info: { status: versionStatus },
|
|
149
149
|
},
|
|
@@ -152,7 +152,7 @@ function createMockApiClient(options: {
|
|
|
152
152
|
},
|
|
153
153
|
getCalls: () => calls,
|
|
154
154
|
getLastCall: () => calls[calls.length - 1],
|
|
155
|
-
clearCalls: () => calls.length = 0,
|
|
155
|
+
clearCalls: () => { calls.length = 0; },
|
|
156
156
|
};
|
|
157
157
|
}
|
|
158
158
|
|
|
@@ -214,7 +214,7 @@ describe("IdentityClient", () => {
|
|
|
214
214
|
const mockStorage = createMockStorageClient();
|
|
215
215
|
const storedState = {
|
|
216
216
|
type: "identified",
|
|
217
|
-
session: { session_id: "s1", device_id: "d1",
|
|
217
|
+
session: { session_id: "s1", device_id: "d1", user_id: "p1", token: "t1" },
|
|
218
218
|
version_info: { status: IdentifyVersionStatusEnum.UP_TO_DATE, update: null },
|
|
219
219
|
};
|
|
220
220
|
mockStorage.getStorage().set(IDENTIFY_STORAGE_KEY, JSON.stringify(storedState));
|
|
@@ -227,7 +227,7 @@ describe("IdentityClient", () => {
|
|
|
227
227
|
if (state.type === "identified") {
|
|
228
228
|
expect(state.session.session_id).toBe("s1");
|
|
229
229
|
expect(state.session.device_id).toBe("d1");
|
|
230
|
-
expect(state.session.
|
|
230
|
+
expect(state.session.user_id).toBe("p1");
|
|
231
231
|
expect(state.session.token).toBe("t1");
|
|
232
232
|
}
|
|
233
233
|
});
|
|
@@ -296,7 +296,7 @@ describe("IdentityClient", () => {
|
|
|
296
296
|
versionStatus: IdentifyVersionStatusEnum.UPDATE_AVAILABLE,
|
|
297
297
|
sessionId: "custom-session",
|
|
298
298
|
deviceId: "custom-device",
|
|
299
|
-
|
|
299
|
+
user_id: "custom-user_id",
|
|
300
300
|
token: "custom-token",
|
|
301
301
|
});
|
|
302
302
|
const { client } = createTestClient({ api: mockApi });
|
|
@@ -307,7 +307,7 @@ describe("IdentityClient", () => {
|
|
|
307
307
|
if (result.success) {
|
|
308
308
|
expect(result.data.session_id).toBe("custom-session");
|
|
309
309
|
expect(result.data.device_id).toBe("custom-device");
|
|
310
|
-
expect(result.data.
|
|
310
|
+
expect(result.data.user_id).toBe("custom-user_id");
|
|
311
311
|
expect(result.data.token).toBe("custom-token");
|
|
312
312
|
expect(result.data.version_info.status).toBe(IdentifyVersionStatusEnum.UPDATE_AVAILABLE);
|
|
313
313
|
expect(result.data.version_info.update).toBeNull();
|
|
@@ -360,7 +360,7 @@ describe("IdentityClient", () => {
|
|
|
360
360
|
const mockStorage = createMockStorageClient();
|
|
361
361
|
const storedState = {
|
|
362
362
|
type: "identified",
|
|
363
|
-
session: { session_id: "s1", device_id: "d1",
|
|
363
|
+
session: { session_id: "s1", device_id: "d1", user_id: "p1", token: "t1" },
|
|
364
364
|
version_info: { status: IdentifyVersionStatusEnum.UP_TO_DATE, update: null },
|
|
365
365
|
};
|
|
366
366
|
mockStorage.getStorage().set(IDENTIFY_STORAGE_KEY, JSON.stringify(storedState));
|
|
@@ -484,7 +484,7 @@ describe("IdentityClient", () => {
|
|
|
484
484
|
|
|
485
485
|
const persona: Persona = {
|
|
486
486
|
name: "John Doe",
|
|
487
|
-
user_id: "user-
|
|
487
|
+
user_id: "user-123",
|
|
488
488
|
email: "john@example.com",
|
|
489
489
|
};
|
|
490
490
|
|
|
@@ -566,7 +566,7 @@ describe("IdentityClient", () => {
|
|
|
566
566
|
const mockStorage = createMockStorageClient();
|
|
567
567
|
const storedState = {
|
|
568
568
|
type: "identified",
|
|
569
|
-
session: { session_id: "s1", device_id: "d1",
|
|
569
|
+
session: { session_id: "s1", device_id: "d1", user_id: "p1", token: "t1" },
|
|
570
570
|
version_info: { status: IdentifyVersionStatusEnum.UP_TO_DATE, update: null },
|
|
571
571
|
};
|
|
572
572
|
mockStorage.getStorage().set(IDENTIFY_STORAGE_KEY, JSON.stringify(storedState));
|
|
@@ -605,10 +605,10 @@ describe("IdentityClient", () => {
|
|
|
605
605
|
|
|
606
606
|
// Should have debug log about state already being 'identifying' (first setIdentifyState call)
|
|
607
607
|
// Then transitions to identified
|
|
608
|
-
const debugLogs = mockLogging.getLogs().filter(l => l.level === "debug");
|
|
608
|
+
const debugLogs = mockLogging.getLogs().filter((l) => l.level === "debug");
|
|
609
609
|
// The "identifying" to "identifying" won't happen since we start from "identified"
|
|
610
610
|
// But we can check that state changes are logged properly
|
|
611
|
-
expect(mockLogging.getLogs().some(l => l.level === "
|
|
611
|
+
expect(mockLogging.getLogs().some(l => l.level === "debug")).toBe(true);
|
|
612
612
|
});
|
|
613
613
|
});
|
|
614
614
|
|
|
@@ -731,7 +731,7 @@ describe("IdentityClient", () => {
|
|
|
731
731
|
data: {
|
|
732
732
|
session_id: "refreshed-session",
|
|
733
733
|
device_id: "device-123",
|
|
734
|
-
|
|
734
|
+
user_id: "user-123",
|
|
735
735
|
token: "token-123",
|
|
736
736
|
version_info: { status: IdentifyVersionStatusEnum.UP_TO_DATE },
|
|
737
737
|
},
|
|
@@ -873,7 +873,7 @@ describe("IdentityClient", () => {
|
|
|
873
873
|
expect(session).not.toBeNull();
|
|
874
874
|
expect(session?.session_id).toBe("session-123");
|
|
875
875
|
expect(session?.device_id).toBe("device-123");
|
|
876
|
-
expect(session?.
|
|
876
|
+
expect(session?.user_id).toBe("user-123");
|
|
877
877
|
expect(session?.token).toBe("token-123");
|
|
878
878
|
});
|
|
879
879
|
|
|
@@ -891,7 +891,7 @@ describe("IdentityClient", () => {
|
|
|
891
891
|
data: {
|
|
892
892
|
session_id: "second-session",
|
|
893
893
|
device_id: "device-123",
|
|
894
|
-
|
|
894
|
+
user_id: "user-123",
|
|
895
895
|
token: "token-123",
|
|
896
896
|
version_info: { status: IdentifyVersionStatusEnum.UP_TO_DATE },
|
|
897
897
|
},
|
|
@@ -1002,7 +1002,7 @@ describe("IdentityClient", () => {
|
|
|
1002
1002
|
data: {
|
|
1003
1003
|
session_id: "session-123",
|
|
1004
1004
|
device_id: "device-123",
|
|
1005
|
-
|
|
1005
|
+
user_id: "user-123",
|
|
1006
1006
|
token: "token-123",
|
|
1007
1007
|
version_info: { status: IdentifyVersionStatusEnum.UP_TO_DATE },
|
|
1008
1008
|
},
|
|
@@ -17,7 +17,7 @@ export type Persona = {
|
|
|
17
17
|
export type IdentityUser = {
|
|
18
18
|
session_id: string;
|
|
19
19
|
device_id: string;
|
|
20
|
-
|
|
20
|
+
user_id: string;
|
|
21
21
|
token: string;
|
|
22
22
|
version_info: {
|
|
23
23
|
status: IdentifyVersionStatusEnum;
|
|
@@ -40,7 +40,7 @@ export const UpdateVersionStatusBodySchema = z.object({
|
|
|
40
40
|
export const SessionSchema = z.object({
|
|
41
41
|
session_id: z.string(),
|
|
42
42
|
device_id: z.string(),
|
|
43
|
-
|
|
43
|
+
user_id: z.string(),
|
|
44
44
|
token: z.string(),
|
|
45
45
|
});
|
|
46
46
|
export type Session = z.infer<typeof SessionSchema>;
|
|
@@ -211,8 +211,8 @@ export class IdentityClient {
|
|
|
211
211
|
}
|
|
212
212
|
}
|
|
213
213
|
|
|
214
|
-
async identify(
|
|
215
|
-
this.logger.debug(`Identifying user with persona: ${
|
|
214
|
+
async identify(user?: Persona): AsyncResult<IdentityUser> {
|
|
215
|
+
this.logger.debug(`Identifying user with persona: ${user?.name ?? "none"}`);
|
|
216
216
|
const previousState = this.identifyState;
|
|
217
217
|
this.setIdentifyState({ type: "identifying" });
|
|
218
218
|
|
|
@@ -231,8 +231,7 @@ export class IdentityClient {
|
|
|
231
231
|
"td-device-id": deviceId,
|
|
232
232
|
},
|
|
233
233
|
body: {
|
|
234
|
-
|
|
235
|
-
// @ts-expect-error - notifications is not yet implemented
|
|
234
|
+
user,
|
|
236
235
|
device: {
|
|
237
236
|
timestamp: deviceInfo.timestamp,
|
|
238
237
|
os: deviceInfo.os,
|
|
@@ -21,29 +21,27 @@ export class AsyncStorageAdapter extends StorageAdapter {
|
|
|
21
21
|
const prefixedKey = (key: string): string => `${storageKey}:${key}`;
|
|
22
22
|
|
|
23
23
|
return {
|
|
24
|
-
preload: () => {
|
|
24
|
+
preload: async (): Promise<void> => {
|
|
25
25
|
if (hydrated) return;
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
.
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
const key = fullKey.replace(`${storageKey}:`, "");
|
|
39
|
-
cache[key] = value;
|
|
40
|
-
}
|
|
27
|
+
try {
|
|
28
|
+
const allKeys = await AsyncStorage.getAllKeys();
|
|
29
|
+
const relevantKeys = allKeys.filter((k) =>
|
|
30
|
+
k.startsWith(`${storageKey}:`)
|
|
31
|
+
);
|
|
32
|
+
const pairs = await AsyncStorage.multiGet(relevantKeys);
|
|
33
|
+
|
|
34
|
+
for (const [fullKey, value] of pairs) {
|
|
35
|
+
if (value != null) {
|
|
36
|
+
const key = fullKey.replace(`${storageKey}:`, "");
|
|
37
|
+
cache[key] = value;
|
|
41
38
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
39
|
+
}
|
|
40
|
+
} catch {
|
|
41
|
+
// Silently fail - cache remains empty
|
|
42
|
+
} finally {
|
|
43
|
+
hydrated = true;
|
|
44
|
+
}
|
|
47
45
|
},
|
|
48
46
|
|
|
49
47
|
getItem: (key: string): string | null => {
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* A storage interface that is used to store data.
|
|
4
4
|
*/
|
|
5
5
|
export type SupportedStorage = {
|
|
6
|
-
preload: () => void
|
|
6
|
+
preload: () => void | Promise<void>;
|
|
7
7
|
getItem: (key: string) => string | null;
|
|
8
8
|
setItem: (key: string, value: string) => void;
|
|
9
9
|
removeItem: (key: string) => void;
|
|
@@ -8,6 +8,14 @@ export class StorageClient {
|
|
|
8
8
|
|
|
9
9
|
private readonly storage: Map<string, SupportedStorage> = new Map();
|
|
10
10
|
|
|
11
|
+
private readonly preloadPromises: Promise<void>[] = [];
|
|
12
|
+
|
|
13
|
+
private _isReady = false;
|
|
14
|
+
|
|
15
|
+
get isReady(): boolean {
|
|
16
|
+
return this._isReady;
|
|
17
|
+
}
|
|
18
|
+
|
|
11
19
|
constructor(
|
|
12
20
|
logging: LoggingClient,
|
|
13
21
|
private readonly orgId: string,
|
|
@@ -40,7 +48,10 @@ export class StorageClient {
|
|
|
40
48
|
|
|
41
49
|
this.logger.debug(`Creating new storage for ${fullStorageKey}`);
|
|
42
50
|
const newStorage = this.storageAdapter.createStorage(fullStorageKey);
|
|
43
|
-
newStorage.preload();
|
|
51
|
+
const preloadResult = newStorage.preload();
|
|
52
|
+
if (preloadResult instanceof Promise) {
|
|
53
|
+
this.preloadPromises.push(preloadResult);
|
|
54
|
+
}
|
|
44
55
|
|
|
45
56
|
const remappedStorage = {
|
|
46
57
|
...newStorage,
|
|
@@ -55,6 +66,11 @@ export class StorageClient {
|
|
|
55
66
|
return remappedStorage;
|
|
56
67
|
}
|
|
57
68
|
|
|
69
|
+
async whenReady(): Promise<void> {
|
|
70
|
+
await Promise.all(this.preloadPromises);
|
|
71
|
+
this._isReady = true;
|
|
72
|
+
}
|
|
73
|
+
|
|
58
74
|
shutdown(): void {
|
|
59
75
|
this.storage.forEach((storage) => {
|
|
60
76
|
storage.clear();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "../../clients/device/adapters/device-info.adapter";
|
package/src/teardown.core.ts
CHANGED
|
@@ -69,7 +69,9 @@ export class TeardownCore {
|
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
async initialize(): Promise<void> {
|
|
72
|
-
//
|
|
72
|
+
// Wait for all storage hydration to complete
|
|
73
|
+
await this.storage.whenReady();
|
|
74
|
+
// Initialize identity (loads from storage, then identifies if needed)
|
|
73
75
|
await this.identity.initialize();
|
|
74
76
|
// Then initialize force update (subscribes to identity events)
|
|
75
77
|
this.forceUpdate.initialize();
|
|
@@ -1,147 +0,0 @@
|
|
|
1
|
-
# Getting Started
|
|
2
|
-
|
|
3
|
-
This guide will walk you through installing and setting up the Teardown SDK in your React Native or Expo application.
|
|
4
|
-
|
|
5
|
-
## Installation
|
|
6
|
-
|
|
7
|
-
Install the core package:
|
|
8
|
-
|
|
9
|
-
```bash
|
|
10
|
-
bun add @teardown/react-native
|
|
11
|
-
```
|
|
12
|
-
|
|
13
|
-
### Peer Dependencies
|
|
14
|
-
|
|
15
|
-
Install required peer dependencies:
|
|
16
|
-
|
|
17
|
-
```bash
|
|
18
|
-
bun add react react-native zod
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
### Platform Adapters
|
|
22
|
-
|
|
23
|
-
#### For Expo Projects
|
|
24
|
-
|
|
25
|
-
```bash
|
|
26
|
-
bun add expo-application expo-device expo-updates
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
#### For React Native CLI Projects
|
|
30
|
-
|
|
31
|
-
```bash
|
|
32
|
-
bun add react-native-device-info
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
### Storage Adapter
|
|
36
|
-
|
|
37
|
-
Install MMKV for fast, encrypted storage:
|
|
38
|
-
|
|
39
|
-
```bash
|
|
40
|
-
bun add react-native-mmkv
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
## Initial Setup
|
|
44
|
-
|
|
45
|
-
### 1. Create SDK Configuration
|
|
46
|
-
|
|
47
|
-
Create a file to initialize the Teardown SDK (e.g., `lib/teardown.ts`):
|
|
48
|
-
|
|
49
|
-
```typescript
|
|
50
|
-
import { TeardownCore } from '@teardown/react-native';
|
|
51
|
-
import { ExpoDeviceAdapter } from '@teardown/react-native/expo';
|
|
52
|
-
import { createMMKVStorageFactory } from '@teardown/react-native/mmkv';
|
|
53
|
-
|
|
54
|
-
export const teardown = new TeardownCore({
|
|
55
|
-
org_id: 'your-org-id',
|
|
56
|
-
project_id: 'your-project-id',
|
|
57
|
-
api_key: 'your-api-key',
|
|
58
|
-
storageFactory: createMMKVStorageFactory(),
|
|
59
|
-
deviceAdapter: new ExpoDeviceAdapter(),
|
|
60
|
-
forceUpdate: {
|
|
61
|
-
throttleMs: 30_000, // Check every 30 seconds when app returns to foreground
|
|
62
|
-
checkCooldownMs: 300_000, // Wait 5 minutes between checks
|
|
63
|
-
},
|
|
64
|
-
});
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
### 2. Wrap Your App with TeardownProvider
|
|
68
|
-
|
|
69
|
-
In your root layout file (e.g., `app/_layout.tsx` for Expo Router):
|
|
70
|
-
|
|
71
|
-
```typescript
|
|
72
|
-
import { TeardownProvider } from '@teardown/react-native';
|
|
73
|
-
import { teardown } from '../lib/teardown';
|
|
74
|
-
|
|
75
|
-
export default function RootLayout() {
|
|
76
|
-
return (
|
|
77
|
-
<TeardownProvider core={teardown}>
|
|
78
|
-
<YourApp />
|
|
79
|
-
</TeardownProvider>
|
|
80
|
-
);
|
|
81
|
-
}
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
For traditional React Native:
|
|
85
|
-
|
|
86
|
-
```typescript
|
|
87
|
-
import { TeardownProvider } from '@teardown/react-native';
|
|
88
|
-
import { teardown } from './lib/teardown';
|
|
89
|
-
|
|
90
|
-
export default function App() {
|
|
91
|
-
return (
|
|
92
|
-
<TeardownProvider core={teardown}>
|
|
93
|
-
<YourNavigationStack />
|
|
94
|
-
</TeardownProvider>
|
|
95
|
-
);
|
|
96
|
-
}
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
### 3. Use the SDK in Your Components
|
|
100
|
-
|
|
101
|
-
```typescript
|
|
102
|
-
import { useTeardown } from '@teardown/react-native';
|
|
103
|
-
|
|
104
|
-
function MyComponent() {
|
|
105
|
-
const { core } = useTeardown();
|
|
106
|
-
|
|
107
|
-
const handleLogin = async () => {
|
|
108
|
-
const result = await core.identity.identify({
|
|
109
|
-
user_id: 'user-123',
|
|
110
|
-
email: 'user@example.com',
|
|
111
|
-
name: 'John Doe',
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
if (result.success) {
|
|
115
|
-
console.log('User identified:', result.data);
|
|
116
|
-
}
|
|
117
|
-
};
|
|
118
|
-
|
|
119
|
-
return <Button onPress={handleLogin} title="Login" />;
|
|
120
|
-
}
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
## Configuration Options
|
|
124
|
-
|
|
125
|
-
### TeardownCore Options
|
|
126
|
-
|
|
127
|
-
| Option | Type | Required | Description |
|
|
128
|
-
|--------|------|----------|-------------|
|
|
129
|
-
| `org_id` | string | Yes | Your organization ID from Teardown dashboard |
|
|
130
|
-
| `project_id` | string | Yes | Your project ID from Teardown dashboard |
|
|
131
|
-
| `api_key` | string | Yes | Your API key from Teardown dashboard |
|
|
132
|
-
| `storageFactory` | function | Yes | Storage adapter factory (e.g., MMKV) |
|
|
133
|
-
| `deviceAdapter` | object | Yes | Device info adapter (Expo or RN) |
|
|
134
|
-
| `forceUpdate` | object | No | Force update configuration |
|
|
135
|
-
|
|
136
|
-
### Force Update Options
|
|
137
|
-
|
|
138
|
-
| Option | Type | Default | Description |
|
|
139
|
-
|--------|------|---------|-------------|
|
|
140
|
-
| `throttleMs` | number | 30000 | Minimum time between foreground checks (ms) |
|
|
141
|
-
| `checkCooldownMs` | number | 300000 | Minimum time since last successful check (ms) |
|
|
142
|
-
|
|
143
|
-
## Next Steps
|
|
144
|
-
|
|
145
|
-
- [Core Concepts](./02-core-concepts.mdx) - Understand the architecture
|
|
146
|
-
- [Identity & Authentication](./03-identity.mdx) - Manage user sessions
|
|
147
|
-
- [Force Updates](./04-force-updates.mdx) - Handle version management
|
|
@@ -1,188 +0,0 @@
|
|
|
1
|
-
# Core Concepts
|
|
2
|
-
|
|
3
|
-
Understanding the architecture and design principles of the Teardown SDK.
|
|
4
|
-
|
|
5
|
-
## Architecture Overview
|
|
6
|
-
|
|
7
|
-
The Teardown SDK is built around a central `TeardownCore` instance that manages several specialized clients:
|
|
8
|
-
|
|
9
|
-
```
|
|
10
|
-
TeardownCore
|
|
11
|
-
├── IdentityClient - User and device identity
|
|
12
|
-
├── ForceUpdateClient - Version management
|
|
13
|
-
├── DeviceClient - Device information
|
|
14
|
-
├── StorageClient - Persistent storage
|
|
15
|
-
├── LoggingClient - Structured logging
|
|
16
|
-
└── ApiClient - API communication
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
## Core Principles
|
|
20
|
-
|
|
21
|
-
### 1. Single Source of Truth
|
|
22
|
-
|
|
23
|
-
The `TeardownCore` instance is your single entry point to all SDK functionality. It's initialized once and passed through your app via the `TeardownProvider`.
|
|
24
|
-
|
|
25
|
-
```typescript
|
|
26
|
-
const teardown = new TeardownCore({...});
|
|
27
|
-
|
|
28
|
-
// Use throughout your app
|
|
29
|
-
<TeardownProvider core={teardown}>
|
|
30
|
-
<App />
|
|
31
|
-
</TeardownProvider>
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
### 2. Automatic Initialization
|
|
35
|
-
|
|
36
|
-
The SDK automatically initializes when the `TeardownProvider` mounts:
|
|
37
|
-
|
|
38
|
-
- Loads persisted session data
|
|
39
|
-
- Identifies the device
|
|
40
|
-
- Checks version status
|
|
41
|
-
- Subscribes to app lifecycle events
|
|
42
|
-
|
|
43
|
-
### 3. Reactive State Management
|
|
44
|
-
|
|
45
|
-
All clients use event emitters to notify of state changes. React hooks automatically subscribe to these events:
|
|
46
|
-
|
|
47
|
-
```typescript
|
|
48
|
-
// Hooks automatically subscribe and cleanup
|
|
49
|
-
const session = useSession();
|
|
50
|
-
const { versionStatus } = useForceUpdate();
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
### 4. Namespaced Storage
|
|
54
|
-
|
|
55
|
-
Each client gets its own namespaced storage to prevent key collisions:
|
|
56
|
-
|
|
57
|
-
```typescript
|
|
58
|
-
// Storage is automatically namespaced
|
|
59
|
-
teardown:v1:identity:IDENTIFY_STATE
|
|
60
|
-
teardown:v1:device:deviceId
|
|
61
|
-
teardown:v1:version:VERSION_STATUS
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
## Client Responsibilities
|
|
65
|
-
|
|
66
|
-
### IdentityClient
|
|
67
|
-
|
|
68
|
-
Manages device and user identity:
|
|
69
|
-
|
|
70
|
-
- Generates unique device IDs
|
|
71
|
-
- Tracks user sessions
|
|
72
|
-
- Persists authentication state
|
|
73
|
-
- Provides persona management (anonymous or identified users)
|
|
74
|
-
|
|
75
|
-
### ForceUpdateClient
|
|
76
|
-
|
|
77
|
-
Handles version management:
|
|
78
|
-
|
|
79
|
-
- Checks app version against backend
|
|
80
|
-
- Monitors app state changes
|
|
81
|
-
- Throttles version checks
|
|
82
|
-
- Emits update status changes
|
|
83
|
-
|
|
84
|
-
### DeviceClient
|
|
85
|
-
|
|
86
|
-
Collects device information:
|
|
87
|
-
|
|
88
|
-
- OS version and platform
|
|
89
|
-
- Device model and manufacturer
|
|
90
|
-
- App version and build number
|
|
91
|
-
- Uses platform adapters for consistency
|
|
92
|
-
|
|
93
|
-
### StorageClient
|
|
94
|
-
|
|
95
|
-
Provides persistent storage:
|
|
96
|
-
|
|
97
|
-
- Namespaced key-value storage
|
|
98
|
-
- Platform-agnostic interface
|
|
99
|
-
- Support for multiple adapters (MMKV, AsyncStorage, etc.)
|
|
100
|
-
- Automatic cleanup on shutdown
|
|
101
|
-
|
|
102
|
-
### LoggingClient
|
|
103
|
-
|
|
104
|
-
Structured logging system:
|
|
105
|
-
|
|
106
|
-
- Configurable log levels
|
|
107
|
-
- Named loggers for each client
|
|
108
|
-
- Console binding preservation
|
|
109
|
-
- Debug mode support
|
|
110
|
-
|
|
111
|
-
### ApiClient
|
|
112
|
-
|
|
113
|
-
Handles API communication:
|
|
114
|
-
|
|
115
|
-
- Type-safe API client
|
|
116
|
-
- Automatic header injection
|
|
117
|
-
- Request/response logging
|
|
118
|
-
- Error handling
|
|
119
|
-
|
|
120
|
-
## Lifecycle Management
|
|
121
|
-
|
|
122
|
-
### Initialization
|
|
123
|
-
|
|
124
|
-
```typescript
|
|
125
|
-
const teardown = new TeardownCore({...});
|
|
126
|
-
// Automatically initializes identity client
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
### Runtime
|
|
130
|
-
|
|
131
|
-
The SDK operates automatically:
|
|
132
|
-
- Listens for app state changes
|
|
133
|
-
- Checks version on foreground
|
|
134
|
-
- Persists state changes
|
|
135
|
-
|
|
136
|
-
### Cleanup
|
|
137
|
-
|
|
138
|
-
```typescript
|
|
139
|
-
// Automatic cleanup when provider unmounts
|
|
140
|
-
useEffect(() => {
|
|
141
|
-
return () => {
|
|
142
|
-
core.shutdown();
|
|
143
|
-
};
|
|
144
|
-
}, [core]);
|
|
145
|
-
```
|
|
146
|
-
|
|
147
|
-
## State Persistence
|
|
148
|
-
|
|
149
|
-
All critical state is automatically persisted:
|
|
150
|
-
|
|
151
|
-
```typescript
|
|
152
|
-
// Identity state
|
|
153
|
-
{ type: "identified", session: {...}, version_info: {...} }
|
|
154
|
-
|
|
155
|
-
// Version status
|
|
156
|
-
{ type: "up_to_date" | "update_available" | "update_required" | ... }
|
|
157
|
-
```
|
|
158
|
-
|
|
159
|
-
State is restored on app restart for seamless user experience.
|
|
160
|
-
|
|
161
|
-
## Error Handling
|
|
162
|
-
|
|
163
|
-
The SDK uses `AsyncResult` pattern for predictable error handling:
|
|
164
|
-
|
|
165
|
-
```typescript
|
|
166
|
-
const result = await core.identity.identify({...});
|
|
167
|
-
|
|
168
|
-
if (result.success) {
|
|
169
|
-
// Handle success
|
|
170
|
-
console.log(result.data);
|
|
171
|
-
} else {
|
|
172
|
-
// Handle error
|
|
173
|
-
console.error(result.error);
|
|
174
|
-
}
|
|
175
|
-
```
|
|
176
|
-
|
|
177
|
-
## Type Safety
|
|
178
|
-
|
|
179
|
-
Full TypeScript support with:
|
|
180
|
-
- Runtime validation using Zod schemas
|
|
181
|
-
- Discriminated unions for state types
|
|
182
|
-
- Exported types for all public APIs
|
|
183
|
-
|
|
184
|
-
## Next Steps
|
|
185
|
-
|
|
186
|
-
- [Identity & Authentication](./03-identity.mdx)
|
|
187
|
-
- [Force Updates](./04-force-updates.mdx)
|
|
188
|
-
- [API Reference](./07-api-reference.mdx)
|