@teardown/react-native 1.2.38 → 2.0.0
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 +75 -7
- package/package.json +65 -47
- package/src/clients/api/api.client.ts +55 -0
- package/src/clients/api/index.ts +1 -0
- package/src/clients/device/device.adpater-interface.ts +57 -0
- package/src/clients/device/device.client.test.ts +195 -0
- package/src/clients/device/device.client.ts +69 -0
- package/src/clients/device/expo-adapter.ts +128 -0
- package/src/clients/device/index.ts +4 -0
- package/src/clients/force-update/force-update.client.test.ts +296 -0
- package/src/clients/force-update/force-update.client.ts +224 -0
- package/src/clients/force-update/index.ts +1 -0
- package/src/clients/identity/identity.client.test.ts +454 -0
- package/src/clients/identity/identity.client.ts +249 -0
- package/src/clients/identity/index.ts +1 -0
- package/src/clients/logging/index.ts +1 -0
- package/src/clients/logging/logging.client.ts +92 -0
- package/src/clients/notifications/notifications.client.ts +10 -0
- package/src/clients/storage/index.ts +1 -0
- package/src/clients/storage/mmkv-adapter.ts +23 -0
- package/src/clients/storage/storage.client.ts +75 -0
- package/src/clients/utils/index.ts +1 -0
- package/src/clients/utils/utils.client.ts +75 -0
- package/src/components/ui/button.tsx +0 -0
- package/src/components/ui/input.tsx +0 -0
- package/src/contexts/index.ts +1 -0
- package/src/contexts/teardown.context.ts +17 -0
- package/src/exports/expo.ts +1 -0
- package/src/exports/index.ts +16 -0
- package/src/exports/mmkv.ts +1 -0
- package/src/hooks/use-force-update.ts +38 -0
- package/src/hooks/use-session.ts +26 -0
- package/src/providers/teardown.provider.tsx +28 -0
- package/src/teardown.core.ts +76 -0
- package/dist/components/index.js +0 -2
- package/dist/components/teardown-logo.js +0 -34
- package/dist/containers/index.js +0 -17
- package/dist/containers/teardown.container.js +0 -25
- package/dist/index.js +0 -21
- package/dist/plugins/http.plugin.js +0 -144
- package/dist/plugins/index.js +0 -19
- package/dist/plugins/logging.plugin.js +0 -35
- package/dist/plugins/websocket.plugin.js +0 -107
- package/dist/services/index.js +0 -17
- package/dist/services/teardown.service.js +0 -21
- package/dist/teardown.client.js +0 -59
- package/dist/utils/log.js +0 -8
package/README.md
CHANGED
|
@@ -1,20 +1,88 @@
|
|
|
1
|
+
# @teardown/react-native
|
|
1
2
|
|
|
3
|
+
Comprehensive SDK for managing device identity, force updates, logging, and analytics in React Native and Expo applications.
|
|
2
4
|
|
|
5
|
+
## Features
|
|
3
6
|
|
|
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
|
|
4
14
|
|
|
5
|
-
|
|
6
|
-
To get started:
|
|
15
|
+
## Installation
|
|
7
16
|
|
|
8
17
|
```bash
|
|
9
|
-
|
|
18
|
+
bun add @teardown/react-native
|
|
10
19
|
```
|
|
11
20
|
|
|
12
|
-
|
|
21
|
+
### Peer Dependencies
|
|
13
22
|
|
|
14
23
|
```bash
|
|
15
|
-
|
|
24
|
+
bun add react react-native zod
|
|
25
|
+
bun add expo-application expo-device expo-updates
|
|
26
|
+
bun add react-native-device-info
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Quick Start
|
|
30
|
+
|
|
31
|
+
```tsx
|
|
32
|
+
import { TeardownCore, TeardownProvider, useTeardown } from '@teardown/react-native';
|
|
33
|
+
|
|
34
|
+
// Initialize
|
|
35
|
+
const teardown = new TeardownCore({
|
|
36
|
+
api: {
|
|
37
|
+
api_key: 'your-api-key',
|
|
38
|
+
org_id: 'your-org-id',
|
|
39
|
+
project_id: 'your-project-id',
|
|
40
|
+
environment_slug: 'production',
|
|
41
|
+
},
|
|
42
|
+
storage: { /* ... */ },
|
|
43
|
+
device: { /* ... */ },
|
|
44
|
+
identity: { identifyOnLoad: true },
|
|
45
|
+
});
|
|
16
46
|
|
|
17
|
-
|
|
47
|
+
// Wrap your app
|
|
48
|
+
export default function App() {
|
|
49
|
+
return (
|
|
50
|
+
<TeardownProvider core={teardown}>
|
|
51
|
+
<YourApp />
|
|
52
|
+
</TeardownProvider>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
18
55
|
|
|
19
|
-
|
|
56
|
+
// Use in components
|
|
57
|
+
function YourComponent() {
|
|
58
|
+
const { core } = useTeardown();
|
|
59
|
+
|
|
60
|
+
const handleLogin = async () => {
|
|
61
|
+
await core.identity.identify({
|
|
62
|
+
user_id: 'user-123',
|
|
63
|
+
email: 'user@example.com',
|
|
64
|
+
});
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
return <Button onPress={handleLogin} />;
|
|
68
|
+
}
|
|
20
69
|
```
|
|
70
|
+
|
|
71
|
+
## Documentation
|
|
72
|
+
|
|
73
|
+
Complete documentation is available in the [docs](./docs) folder:
|
|
74
|
+
|
|
75
|
+
- [Getting Started](./docs/01-getting-started.mdx) - Installation and setup
|
|
76
|
+
- [Core Concepts](./docs/02-core-concepts.mdx) - Architecture overview
|
|
77
|
+
- [Identity & Authentication](./docs/03-identity.mdx) - User session management
|
|
78
|
+
- [Force Updates](./docs/04-force-updates.mdx) - Version management
|
|
79
|
+
- [Device Information](./docs/05-device-info.mdx) - Device data collection
|
|
80
|
+
- [Storage](./docs/06-storage.mdx) - Persistent storage
|
|
81
|
+
- [Logging](./docs/06-logging.mdx) - Structured logging
|
|
82
|
+
- [API Reference](./docs/07-api-reference.mdx) - Complete API docs
|
|
83
|
+
- [Hooks Reference](./docs/08-hooks-reference.mdx) - React hooks
|
|
84
|
+
- [Advanced Usage](./docs/09-advanced.mdx) - Advanced patterns
|
|
85
|
+
|
|
86
|
+
## License
|
|
87
|
+
|
|
88
|
+
Proprietary - See LICENSE file for details
|
package/package.json
CHANGED
|
@@ -1,49 +1,67 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
2
|
+
"name": "@teardown/react-native",
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "public"
|
|
7
|
+
},
|
|
8
|
+
"files": [
|
|
9
|
+
"src/**/*",
|
|
10
|
+
"docs/**/*"
|
|
11
|
+
],
|
|
12
|
+
"main": "./src/exports/index.ts",
|
|
13
|
+
"module": "./src/exports/index.ts",
|
|
14
|
+
"types": "./src/exports/index.ts",
|
|
15
|
+
"exports": {
|
|
16
|
+
".": {
|
|
17
|
+
"types": "./src/exports/index.ts",
|
|
18
|
+
"import": "./src/exports/index.ts",
|
|
19
|
+
"default": "./src/exports/index.ts"
|
|
20
|
+
},
|
|
21
|
+
"./expo": {
|
|
22
|
+
"types": "./src/exports/expo.ts",
|
|
23
|
+
"import": "./src/exports/expo.ts",
|
|
24
|
+
"default": "./src/exports/expo.ts"
|
|
25
|
+
},
|
|
26
|
+
"./mmkv": {
|
|
27
|
+
"types": "./src/exports/mmkv.ts",
|
|
28
|
+
"import": "./src/exports/mmkv.ts",
|
|
29
|
+
"default": "./src/exports/mmkv.ts"
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"scripts": {
|
|
33
|
+
"build": "tsc --project ./tsconfig.json",
|
|
34
|
+
"dev": "bun run build && tsc --watch --project ./tsconfig.json",
|
|
35
|
+
"typecheck": "tsc --noEmit --project ./tsconfig.json",
|
|
36
|
+
"lint": "biome lint --write",
|
|
37
|
+
"fmt": "biome format --write",
|
|
38
|
+
"test": "bun test",
|
|
39
|
+
"prepublishOnly": "bun run build",
|
|
40
|
+
"nuke": "cd ../../ && bun run nuke"
|
|
41
|
+
},
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"@teardown/ingest-api": "0.1.36",
|
|
44
|
+
"@teardown/schemas": "0.1.36",
|
|
45
|
+
"@teardown/types": "0.1.36",
|
|
46
|
+
"eventemitter3": "^5.0.1",
|
|
47
|
+
"uuid": "^13.0.0"
|
|
48
|
+
},
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"@elysiajs/eden": "^1.4.5",
|
|
51
|
+
"@teardown/tsconfig": "1.0.0",
|
|
52
|
+
"@types/bun": "latest",
|
|
53
|
+
"@types/react": "*",
|
|
54
|
+
"@types/uuid": "^11.0.0",
|
|
55
|
+
"expo-updates": "^29.0.12"
|
|
56
|
+
},
|
|
57
|
+
"peerDependencies": {
|
|
58
|
+
"react": "*",
|
|
59
|
+
"react-native": "*",
|
|
60
|
+
"typescript": "*",
|
|
61
|
+
"expo-application": "^7.0",
|
|
62
|
+
"expo-device": "^8.0",
|
|
63
|
+
"expo-notifications": "^0.29",
|
|
64
|
+
"react-native-mmkv": "^3.0",
|
|
65
|
+
"zod": "^4"
|
|
66
|
+
}
|
|
49
67
|
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import * as Eden from "@elysiajs/eden";
|
|
2
|
+
import * as IngestApi from "@teardown/ingest-api";
|
|
3
|
+
import type { LoggingClient } from "../logging";
|
|
4
|
+
import type { StorageClient } from "../storage";
|
|
5
|
+
|
|
6
|
+
export { Eden, IngestApi };
|
|
7
|
+
|
|
8
|
+
const TEARDOWN_INGEST_URL = "http://localhost:4880";
|
|
9
|
+
const TEARDOWN_API_KEY_HEADER = "td-api-key";
|
|
10
|
+
const TEARDOWN_ORG_ID_HEADER = "td-org-id";
|
|
11
|
+
const TEARDOWN_PROJECT_ID_HEADER = "td-project-id";
|
|
12
|
+
const TEARDOWN_ENVIRONMENT_SLUG_HEADER = "td-environment-slug";
|
|
13
|
+
|
|
14
|
+
export type ApiClientOptions = {
|
|
15
|
+
api_key: string;
|
|
16
|
+
org_id: string;
|
|
17
|
+
project_id: string;
|
|
18
|
+
environment_slug: string;
|
|
19
|
+
onRequest?: (endpoint: IngestApi.Endpoints, options: IngestApi.RequestOptions) => Promise<IngestApi.RequestOptions>;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export class ApiClient {
|
|
23
|
+
public client: IngestApi.Client;
|
|
24
|
+
|
|
25
|
+
constructor(
|
|
26
|
+
_logging: LoggingClient,
|
|
27
|
+
_storage: StorageClient,
|
|
28
|
+
private readonly options: ApiClientOptions
|
|
29
|
+
) {
|
|
30
|
+
this.client = IngestApi.client(TEARDOWN_INGEST_URL, {
|
|
31
|
+
headers: {
|
|
32
|
+
[TEARDOWN_API_KEY_HEADER]: `Bearer ${this.options.api_key}`,
|
|
33
|
+
[TEARDOWN_ORG_ID_HEADER]: this.options.org_id,
|
|
34
|
+
[TEARDOWN_PROJECT_ID_HEADER]: this.options.project_id,
|
|
35
|
+
[TEARDOWN_ENVIRONMENT_SLUG_HEADER]: this.options.environment_slug,
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
get orgId(): string {
|
|
41
|
+
return this.options.org_id;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
get projectId(): string {
|
|
45
|
+
return this.options.project_id;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
get apiKey(): string {
|
|
49
|
+
return this.options.api_key;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
get environmentSlug(): string {
|
|
53
|
+
return this.options.environment_slug;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./api.client";
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ApplicationInfo,
|
|
3
|
+
DeviceInfo,
|
|
4
|
+
HardwareInfo,
|
|
5
|
+
NotificationsInfo,
|
|
6
|
+
OSInfo
|
|
7
|
+
} from "@teardown/schemas";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* An interface for a device adapter.
|
|
11
|
+
* This interface is used to abstract the device adapter implementation.
|
|
12
|
+
* Everything is optional and should be implemented by the adapter.
|
|
13
|
+
*
|
|
14
|
+
* The aim of this interface is to provide a consistent way to get information about the device.
|
|
15
|
+
* With an opt-in approach to get the information your want to use and track.
|
|
16
|
+
*
|
|
17
|
+
*/
|
|
18
|
+
export abstract class DeviceInfoAdapter {
|
|
19
|
+
// -- Application Information --
|
|
20
|
+
/**
|
|
21
|
+
* The information about the application running.
|
|
22
|
+
*/
|
|
23
|
+
abstract get applicationInfo(): ApplicationInfo;
|
|
24
|
+
// -- Updates Information --
|
|
25
|
+
/**
|
|
26
|
+
* The information about the update running.
|
|
27
|
+
*/
|
|
28
|
+
// abstract get updateInfo(): UpdateInfo | null;
|
|
29
|
+
// -- Hardware Information --
|
|
30
|
+
/**
|
|
31
|
+
* The information about the hardware of the device.
|
|
32
|
+
*/
|
|
33
|
+
abstract get hardwareInfo(): HardwareInfo;
|
|
34
|
+
// -- OS Information --
|
|
35
|
+
/**
|
|
36
|
+
* The information about the operating system of the device.
|
|
37
|
+
*/
|
|
38
|
+
abstract get osInfo(): OSInfo;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* The information about the notifications of the device.
|
|
42
|
+
*/
|
|
43
|
+
abstract get notificationsInfo(): NotificationsInfo;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* The information about the device.
|
|
47
|
+
*/
|
|
48
|
+
public getDeviceInfo(): Promise<DeviceInfo> {
|
|
49
|
+
return Promise.resolve({
|
|
50
|
+
application: this.applicationInfo,
|
|
51
|
+
hardware: this.hardwareInfo,
|
|
52
|
+
os: this.osInfo,
|
|
53
|
+
notifications: this.notificationsInfo,
|
|
54
|
+
update: null,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import type { DeviceInfo } from "@teardown/schemas";
|
|
2
|
+
import { describe, expect, test } from "bun:test";
|
|
3
|
+
import type { DeviceInfoAdapter } from "./device.adpater-interface";
|
|
4
|
+
import { DeviceClient } from "./device.client";
|
|
5
|
+
|
|
6
|
+
function createMockLoggingClient() {
|
|
7
|
+
return {
|
|
8
|
+
createLogger: () => ({
|
|
9
|
+
info: () => { },
|
|
10
|
+
warn: () => { },
|
|
11
|
+
error: () => { },
|
|
12
|
+
debug: () => { },
|
|
13
|
+
}),
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function createMockStorageClient() {
|
|
18
|
+
const storage = new Map<string, string>();
|
|
19
|
+
return {
|
|
20
|
+
createStorage: () => ({
|
|
21
|
+
getItem: (key: string) => storage.get(key) ?? null,
|
|
22
|
+
setItem: (key: string, value: string) => storage.set(key, value),
|
|
23
|
+
removeItem: (key: string) => storage.delete(key),
|
|
24
|
+
}),
|
|
25
|
+
getStorage: () => storage,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function createMockUtilsClient() {
|
|
30
|
+
let uuidCounter = 0;
|
|
31
|
+
return {
|
|
32
|
+
generateRandomUUID: async () => `mock-uuid-${++uuidCounter}`,
|
|
33
|
+
getUuidCounter: () => uuidCounter,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function createMockDeviceAdapter(): DeviceInfoAdapter {
|
|
38
|
+
const mockDeviceInfo: DeviceInfo = {
|
|
39
|
+
application: {
|
|
40
|
+
name: "TestApp",
|
|
41
|
+
version: "1.0.0",
|
|
42
|
+
build: "100",
|
|
43
|
+
bundle_id: "com.test.app",
|
|
44
|
+
},
|
|
45
|
+
hardware: {
|
|
46
|
+
brand: "Apple",
|
|
47
|
+
model: "iPhone 15",
|
|
48
|
+
device_type: "PHONE",
|
|
49
|
+
},
|
|
50
|
+
os: {
|
|
51
|
+
name: "iOS",
|
|
52
|
+
version: "17.0",
|
|
53
|
+
},
|
|
54
|
+
notifications: {
|
|
55
|
+
push_token: null,
|
|
56
|
+
platform: null,
|
|
57
|
+
},
|
|
58
|
+
update: null,
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
applicationInfo: mockDeviceInfo.application,
|
|
63
|
+
hardwareInfo: mockDeviceInfo.hardware,
|
|
64
|
+
osInfo: mockDeviceInfo.os,
|
|
65
|
+
notificationsInfo: mockDeviceInfo.notifications,
|
|
66
|
+
getDeviceInfo: async () => mockDeviceInfo,
|
|
67
|
+
} as DeviceInfoAdapter;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
describe("DeviceClient", () => {
|
|
71
|
+
describe("getDeviceId", () => {
|
|
72
|
+
test("generates new UUID when no device ID stored", async () => {
|
|
73
|
+
const mockLogging = createMockLoggingClient();
|
|
74
|
+
const mockStorage = createMockStorageClient();
|
|
75
|
+
const mockUtils = createMockUtilsClient();
|
|
76
|
+
const mockAdapter = createMockDeviceAdapter();
|
|
77
|
+
|
|
78
|
+
const client = new DeviceClient(
|
|
79
|
+
mockLogging as never,
|
|
80
|
+
mockUtils as never,
|
|
81
|
+
mockStorage as never,
|
|
82
|
+
{ adapter: mockAdapter }
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
const deviceId = await client.getDeviceId();
|
|
86
|
+
|
|
87
|
+
expect(deviceId).toBe("mock-uuid-1");
|
|
88
|
+
expect(mockUtils.getUuidCounter()).toBe(1);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test("returns stored device ID when available", async () => {
|
|
92
|
+
const mockLogging = createMockLoggingClient();
|
|
93
|
+
const mockStorage = createMockStorageClient();
|
|
94
|
+
const mockUtils = createMockUtilsClient();
|
|
95
|
+
const mockAdapter = createMockDeviceAdapter();
|
|
96
|
+
|
|
97
|
+
// Pre-populate storage with a device ID
|
|
98
|
+
mockStorage.getStorage().set("deviceId", "existing-device-id");
|
|
99
|
+
|
|
100
|
+
const client = new DeviceClient(
|
|
101
|
+
mockLogging as never,
|
|
102
|
+
mockUtils as never,
|
|
103
|
+
mockStorage as never,
|
|
104
|
+
{ adapter: mockAdapter }
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
const deviceId = await client.getDeviceId();
|
|
108
|
+
|
|
109
|
+
expect(deviceId).toBe("existing-device-id");
|
|
110
|
+
// Should not have generated a new UUID
|
|
111
|
+
expect(mockUtils.getUuidCounter()).toBe(0);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test("returns consistent device ID on multiple calls", async () => {
|
|
115
|
+
const mockLogging = createMockLoggingClient();
|
|
116
|
+
const mockStorage = createMockStorageClient();
|
|
117
|
+
const mockUtils = createMockUtilsClient();
|
|
118
|
+
const mockAdapter = createMockDeviceAdapter();
|
|
119
|
+
|
|
120
|
+
mockStorage.getStorage().set("deviceId", "consistent-id");
|
|
121
|
+
|
|
122
|
+
const client = new DeviceClient(
|
|
123
|
+
mockLogging as never,
|
|
124
|
+
mockUtils as never,
|
|
125
|
+
mockStorage as never,
|
|
126
|
+
{ adapter: mockAdapter }
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
const id1 = await client.getDeviceId();
|
|
130
|
+
const id2 = await client.getDeviceId();
|
|
131
|
+
const id3 = await client.getDeviceId();
|
|
132
|
+
|
|
133
|
+
expect(id1).toBe("consistent-id");
|
|
134
|
+
expect(id2).toBe("consistent-id");
|
|
135
|
+
expect(id3).toBe("consistent-id");
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
describe("getDeviceInfo", () => {
|
|
140
|
+
test("returns device info from adapter", async () => {
|
|
141
|
+
const mockLogging = createMockLoggingClient();
|
|
142
|
+
const mockStorage = createMockStorageClient();
|
|
143
|
+
const mockUtils = createMockUtilsClient();
|
|
144
|
+
const mockAdapter = createMockDeviceAdapter();
|
|
145
|
+
|
|
146
|
+
const client = new DeviceClient(
|
|
147
|
+
mockLogging as never,
|
|
148
|
+
mockUtils as never,
|
|
149
|
+
mockStorage as never,
|
|
150
|
+
{ adapter: mockAdapter }
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
const deviceInfo = await client.getDeviceInfo();
|
|
154
|
+
|
|
155
|
+
expect(deviceInfo.application.name).toBe("TestApp");
|
|
156
|
+
expect(deviceInfo.application.version).toBe("1.0.0");
|
|
157
|
+
expect(deviceInfo.hardware.brand).toBe("Apple");
|
|
158
|
+
expect(deviceInfo.hardware.model).toBe("iPhone 15");
|
|
159
|
+
expect(deviceInfo.os.name).toBe("iOS");
|
|
160
|
+
expect(deviceInfo.os.version).toBe("17.0");
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
test("delegates to adapter getDeviceInfo", async () => {
|
|
164
|
+
const mockLogging = createMockLoggingClient();
|
|
165
|
+
const mockStorage = createMockStorageClient();
|
|
166
|
+
const mockUtils = createMockUtilsClient();
|
|
167
|
+
|
|
168
|
+
let getDeviceInfoCalled = false;
|
|
169
|
+
const customAdapter = {
|
|
170
|
+
getDeviceInfo: async () => {
|
|
171
|
+
getDeviceInfoCalled = true;
|
|
172
|
+
return {
|
|
173
|
+
application: { name: "Custom", version: "2.0.0", build: "200", bundle_id: "com.custom" },
|
|
174
|
+
hardware: { brand: "Custom", model: "Device", device_type: "TABLET" },
|
|
175
|
+
os: { name: "CustomOS", version: "1.0" },
|
|
176
|
+
notifications: { push_token: null, platform: null },
|
|
177
|
+
update: null,
|
|
178
|
+
};
|
|
179
|
+
},
|
|
180
|
+
} as DeviceInfoAdapter;
|
|
181
|
+
|
|
182
|
+
const client = new DeviceClient(
|
|
183
|
+
mockLogging as never,
|
|
184
|
+
mockUtils as never,
|
|
185
|
+
mockStorage as never,
|
|
186
|
+
{ adapter: customAdapter }
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
const deviceInfo = await client.getDeviceInfo();
|
|
190
|
+
|
|
191
|
+
expect(getDeviceInfoCalled).toBe(true);
|
|
192
|
+
expect(deviceInfo.application.name).toBe("Custom");
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
});
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { DeviceInfo } from "@teardown/schemas";
|
|
2
|
+
import type { Logger, LoggingClient } from "../logging/";
|
|
3
|
+
import type { StorageClient, SupportedStorage } from "../storage";
|
|
4
|
+
import type { UtilsClient } from "../utils/utils.client";
|
|
5
|
+
import type { DeviceInfoAdapter } from "./device.adpater-interface";
|
|
6
|
+
|
|
7
|
+
// TODO: sort out why importing these enuims from schemas is not working - @teardown/schemas
|
|
8
|
+
export enum NotificationPlatformEnum {
|
|
9
|
+
APNS = "APNS", // Apple Push Notification Service
|
|
10
|
+
FCM = "FCM", // Firebase Cloud Messaging
|
|
11
|
+
EXPO = "EXPO", // Expo Push Notifications
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// TODO: sort out why importing these enuims from schemas is not working - @teardown/schemas
|
|
15
|
+
export enum DevicePlatformEnum {
|
|
16
|
+
IOS = "IOS",
|
|
17
|
+
ANDROID = "ANDROID",
|
|
18
|
+
WEB = "WEB",
|
|
19
|
+
WINDOWS = "WINDOWS",
|
|
20
|
+
MACOS = "MACOS",
|
|
21
|
+
LINUX = "LINUX",
|
|
22
|
+
PHONE = "PHONE",
|
|
23
|
+
TABLET = "TABLET",
|
|
24
|
+
DESKTOP = "DESKTOP",
|
|
25
|
+
CONSOLE = "CONSOLE",
|
|
26
|
+
TV = "TV",
|
|
27
|
+
WEARABLE = "WEARABLE",
|
|
28
|
+
GAME_CONSOLE = "GAME_CONSOLE",
|
|
29
|
+
VR = "VR",
|
|
30
|
+
UNKNOWN = "UNKNOWN",
|
|
31
|
+
OTHER = "OTHER",
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export type DeviceClientOptions = {
|
|
35
|
+
adapter: DeviceInfoAdapter;
|
|
36
|
+
};
|
|
37
|
+
export class DeviceClient {
|
|
38
|
+
private logger: Logger;
|
|
39
|
+
private storage: SupportedStorage;
|
|
40
|
+
|
|
41
|
+
constructor(
|
|
42
|
+
logging: LoggingClient,
|
|
43
|
+
private readonly utils: UtilsClient,
|
|
44
|
+
storage: StorageClient,
|
|
45
|
+
private readonly options: DeviceClientOptions
|
|
46
|
+
) {
|
|
47
|
+
this.logger = logging.createLogger({
|
|
48
|
+
name: "DeviceClient",
|
|
49
|
+
});
|
|
50
|
+
this.storage = storage.createStorage("device");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async getDeviceId(): Promise<string> {
|
|
54
|
+
|
|
55
|
+
this.logger.debug("Getting device ID");
|
|
56
|
+
const deviceId = this.storage.getItem("deviceId");
|
|
57
|
+
if (deviceId) {
|
|
58
|
+
return deviceId;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const newDeviceId = await this.utils.generateRandomUUID();
|
|
62
|
+
|
|
63
|
+
return newDeviceId;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async getDeviceInfo(): Promise<DeviceInfo> {
|
|
67
|
+
return this.options.adapter.getDeviceInfo();
|
|
68
|
+
}
|
|
69
|
+
}
|