@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
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
|
-
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
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
|
-
|
|
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 {
|
|
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
|
-
|
|
72
|
+
storageAdapter: new MMKVStorageAdapter(),
|
|
43
73
|
deviceAdapter: new ExpoDeviceAdapter(),
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
|
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
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
- [
|
|
92
|
-
- [
|
|
93
|
-
- [
|
|
94
|
-
- [
|
|
95
|
-
- [
|
|
96
|
-
- [
|
|
97
|
-
- [
|
|
98
|
-
- [
|
|
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.
|
|
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.
|
|
49
|
-
"@teardown/schemas": "0.1.
|
|
50
|
-
"@teardown/types": "0.1.
|
|
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
|
|
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
|
|
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
|
-
|
|
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",
|
|
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",
|
|
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",
|
|
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",
|
|
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",
|
|
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",
|
|
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",
|
|
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
|
-
/**
|
|
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
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
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 = "
|
|
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
|
-
|
|
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
|