@teardown/react-native 2.0.1 → 2.0.2
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/package.json +46 -18
- package/src/clients/api/api.client.ts +2 -2
- package/src/clients/device/{expo-adapter.ts → adapters/basic.adapter.ts} +1 -1
- package/src/clients/device/{device.adpater-interface.ts → adapters/device.adpater-interface.ts} +1 -1
- package/src/clients/device/adapters/expo.adapter.ts +90 -0
- package/src/clients/device/device.client.test.ts +1 -1
- package/src/clients/device/device.client.ts +5 -1
- package/src/clients/device/index.ts +1 -1
- package/src/clients/force-update/force-update.client.test.ts +238 -6
- package/src/clients/force-update/force-update.client.ts +71 -11
- package/src/clients/identity/identity.client.test.ts +888 -223
- package/src/clients/identity/identity.client.ts +59 -20
- package/src/clients/storage/adapters/async-storage.adapter.ts +81 -0
- package/src/clients/storage/{mmkv-adapter.ts → adapters/mmkv.adapter.ts} +7 -10
- package/src/clients/storage/adapters/storage.adpater-interface.ts +30 -0
- package/src/clients/storage/index.ts +2 -1
- package/src/clients/storage/storage.client.ts +9 -20
- package/src/clients/utils/utils.client.ts +1 -57
- package/src/exports/adapters/async-storage.ts +1 -0
- package/src/exports/adapters/expo.ts +1 -0
- package/src/exports/adapters/mmkv.ts +1 -0
- package/src/hooks/use-force-update.ts +12 -3
- package/src/hooks/use-session.ts +7 -4
- package/src/teardown.core.ts +16 -6
- package/src/exports/expo.ts +0 -1
- package/src/exports/mmkv.ts +0 -1
|
@@ -75,7 +75,8 @@ export const IDENTIFY_STORAGE_KEY = "IDENTIFY_STATE";
|
|
|
75
75
|
|
|
76
76
|
export class IdentityClient {
|
|
77
77
|
private emitter = new EventEmitter<IdentifyStateChangeEvents>();
|
|
78
|
-
private identifyState: IdentifyState;
|
|
78
|
+
private identifyState: IdentifyState = { type: "unidentified" };
|
|
79
|
+
private initialized = false;
|
|
79
80
|
|
|
80
81
|
public readonly logger: Logger;
|
|
81
82
|
public readonly utils: UtilsClient;
|
|
@@ -93,22 +94,46 @@ export class IdentityClient {
|
|
|
93
94
|
});
|
|
94
95
|
this.storage = storage.createStorage("identity");
|
|
95
96
|
this.utils = utils;
|
|
96
|
-
|
|
97
|
+
// Don't load from storage here - defer to initialize()
|
|
97
98
|
}
|
|
98
99
|
|
|
99
100
|
async initialize(): Promise<void> {
|
|
101
|
+
this.logger.debug("Initializing IdentityClient");
|
|
102
|
+
if (this.initialized) {
|
|
103
|
+
this.logger.debug("IdentityClient already initialized");
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
this.initialized = true;
|
|
107
|
+
|
|
108
|
+
// Load state from storage first (for fallback if identify fails)
|
|
109
|
+
this.identifyState = this.getIdentifyStateFromStorage();
|
|
110
|
+
this.logger.debug(`Initialized with state: ${this.identifyState.type}`);
|
|
111
|
+
|
|
112
|
+
// Always identify on app boot to refresh version status
|
|
100
113
|
await this.identify();
|
|
101
114
|
}
|
|
102
115
|
|
|
103
116
|
private getIdentifyStateFromStorage(): IdentifyState {
|
|
104
117
|
const stored = this.storage.getItem(IDENTIFY_STORAGE_KEY);
|
|
118
|
+
|
|
105
119
|
if (stored == null) {
|
|
106
|
-
|
|
120
|
+
this.logger.debug("No stored identity state, returning unidentified");
|
|
121
|
+
return UnidentifiedSessionStateSchema.parse({ type: "unidentified" });
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const parsed = IdentifyStateSchema.parse(JSON.parse(stored));
|
|
125
|
+
this.logger.debug(`Parsed identity state from storage: ${parsed.type}`);
|
|
126
|
+
|
|
127
|
+
// "identifying" is a transient state - if we restore it, treat as unidentified
|
|
128
|
+
// This can happen if the app was killed during an identify call
|
|
129
|
+
if (parsed.type === "identifying") {
|
|
130
|
+
this.logger.debug("Found stale 'identifying' state in storage, resetting to unidentified");
|
|
131
|
+
// Clear the stale state from storage immediately
|
|
132
|
+
this.storage.removeItem(IDENTIFY_STORAGE_KEY);
|
|
107
133
|
return UnidentifiedSessionStateSchema.parse({ type: "unidentified" });
|
|
108
134
|
}
|
|
109
135
|
|
|
110
|
-
|
|
111
|
-
return IdentifyStateSchema.parse(JSON.parse(stored));
|
|
136
|
+
return parsed;
|
|
112
137
|
}
|
|
113
138
|
|
|
114
139
|
private saveIdentifyStateToStorage(identifyState: IdentifyState): void {
|
|
@@ -116,7 +141,13 @@ export class IdentityClient {
|
|
|
116
141
|
}
|
|
117
142
|
|
|
118
143
|
private setIdentifyState(newState: IdentifyState): void {
|
|
119
|
-
|
|
144
|
+
|
|
145
|
+
if (this.identifyState.type === newState.type) {
|
|
146
|
+
this.logger.debug(`Identify state already set: ${this.identifyState.type}`);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
this.logger.debug(`Identify state: ${this.identifyState.type} -> ${newState.type}`);
|
|
120
151
|
this.identifyState = newState;
|
|
121
152
|
this.saveIdentifyStateToStorage(newState);
|
|
122
153
|
this.emitter.emit("IDENTIFY_STATE_CHANGED", newState);
|
|
@@ -165,11 +196,14 @@ export class IdentityClient {
|
|
|
165
196
|
* @param fn - The function to try
|
|
166
197
|
* @returns An {@link AsyncResult}
|
|
167
198
|
*/
|
|
168
|
-
private async tryCatch<T>(fn: () => AsyncResult<T
|
|
199
|
+
private async tryCatch<T>(fn: () => AsyncResult<T>, onError?: (error: Error) => void): AsyncResult<T> {
|
|
169
200
|
try {
|
|
170
201
|
const result = await fn();
|
|
171
202
|
return result;
|
|
172
203
|
} catch (error) {
|
|
204
|
+
if (onError) {
|
|
205
|
+
onError(error instanceof Error ? error : new Error(String(error)));
|
|
206
|
+
}
|
|
173
207
|
return {
|
|
174
208
|
success: false,
|
|
175
209
|
error: error instanceof Error ? error.message : "Unknown error",
|
|
@@ -178,17 +212,15 @@ export class IdentityClient {
|
|
|
178
212
|
}
|
|
179
213
|
|
|
180
214
|
async identify(persona?: Persona): AsyncResult<IdentityUser> {
|
|
215
|
+
this.logger.debug(`Identifying user with persona: ${persona?.name ?? "none"}`);
|
|
181
216
|
const previousState = this.identifyState;
|
|
182
217
|
this.setIdentifyState({ type: "identifying" });
|
|
183
218
|
|
|
184
219
|
return this.tryCatch(async () => {
|
|
220
|
+
this.logger.debug("Getting device ID...");
|
|
185
221
|
const deviceId = await this.device.getDeviceId();
|
|
186
222
|
const deviceInfo = await this.device.getDeviceInfo();
|
|
187
|
-
|
|
188
|
-
console.log("this.api.orgId", this.api.orgId);
|
|
189
|
-
console.log("this.api.projectId", this.api.projectId);
|
|
190
|
-
console.log("this.api.environmentSlug", this.api.environmentSlug);
|
|
191
|
-
console.log("this.api.deviceId", deviceId);
|
|
223
|
+
this.logger.debug("Calling identify API...");
|
|
192
224
|
const response = await this.api.client("/v1/identify", {
|
|
193
225
|
method: "POST",
|
|
194
226
|
headers: {
|
|
@@ -200,20 +232,20 @@ export class IdentityClient {
|
|
|
200
232
|
},
|
|
201
233
|
body: {
|
|
202
234
|
persona,
|
|
235
|
+
// @ts-expect-error - notifications is not yet implemented
|
|
203
236
|
device: {
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
}
|
|
210
|
-
: null,
|
|
237
|
+
timestamp: deviceInfo.timestamp,
|
|
238
|
+
os: deviceInfo.os,
|
|
239
|
+
application: deviceInfo.application,
|
|
240
|
+
hardware: deviceInfo.hardware,
|
|
241
|
+
update: null,
|
|
211
242
|
},
|
|
212
243
|
},
|
|
213
244
|
});
|
|
214
245
|
|
|
246
|
+
this.logger.debug(`Identify API response received`);
|
|
215
247
|
if (response.error != null) {
|
|
216
|
-
|
|
248
|
+
this.logger.warn("Identify API error", response.error.status, response.error.value);
|
|
217
249
|
this.setIdentifyState(previousState);
|
|
218
250
|
|
|
219
251
|
if (response.error.status === 422) {
|
|
@@ -250,6 +282,13 @@ export class IdentityClient {
|
|
|
250
282
|
},
|
|
251
283
|
},
|
|
252
284
|
};
|
|
285
|
+
}, (error) => {
|
|
286
|
+
this.logger.error("Error identifying user", error);
|
|
287
|
+
this.setIdentifyState(previousState);
|
|
288
|
+
return {
|
|
289
|
+
success: false,
|
|
290
|
+
error: error.message,
|
|
291
|
+
};
|
|
253
292
|
});
|
|
254
293
|
}
|
|
255
294
|
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import AsyncStorage from "@react-native-async-storage/async-storage";
|
|
2
|
+
import { StorageAdapter, type SupportedStorage } from "./storage.adpater-interface";
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Creates a SupportedStorage adapter backed by AsyncStorage.
|
|
7
|
+
*
|
|
8
|
+
* Since SupportedStorage interface is synchronous but AsyncStorage is async,
|
|
9
|
+
* this adapter uses an in-memory cache for sync access. Writes are persisted
|
|
10
|
+
* to AsyncStorage asynchronously.
|
|
11
|
+
*
|
|
12
|
+
* Call `preload()` after creation to hydrate the cache from AsyncStorage.
|
|
13
|
+
* Until hydration completes, reads return null for persisted values.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
export class AsyncStorageAdapter extends StorageAdapter {
|
|
17
|
+
createStorage(storageKey: string): SupportedStorage {
|
|
18
|
+
let cache: Record<string, string> = {};
|
|
19
|
+
let hydrated = false;
|
|
20
|
+
|
|
21
|
+
const prefixedKey = (key: string): string => `${storageKey}:${key}`;
|
|
22
|
+
|
|
23
|
+
return {
|
|
24
|
+
preload: () => {
|
|
25
|
+
if (hydrated) return;
|
|
26
|
+
|
|
27
|
+
// Fire async hydration - cache will be populated when complete
|
|
28
|
+
AsyncStorage.getAllKeys()
|
|
29
|
+
.then((allKeys) => {
|
|
30
|
+
const relevantKeys = allKeys.filter((k) =>
|
|
31
|
+
k.startsWith(`${storageKey}:`)
|
|
32
|
+
);
|
|
33
|
+
return AsyncStorage.multiGet(relevantKeys);
|
|
34
|
+
})
|
|
35
|
+
.then((pairs) => {
|
|
36
|
+
for (const [fullKey, value] of pairs) {
|
|
37
|
+
if (value != null) {
|
|
38
|
+
const key = fullKey.replace(`${storageKey}:`, "");
|
|
39
|
+
cache[key] = value;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
hydrated = true;
|
|
43
|
+
})
|
|
44
|
+
.catch(() => {
|
|
45
|
+
// Silently fail - cache remains empty
|
|
46
|
+
});
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
getItem: (key: string): string | null => {
|
|
50
|
+
return cache[key] ?? null;
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
setItem: (key: string, value: string): void => {
|
|
54
|
+
cache[key] = value;
|
|
55
|
+
AsyncStorage.setItem(prefixedKey(key), value).catch(() => {
|
|
56
|
+
// Silently fail - value remains in cache
|
|
57
|
+
});
|
|
58
|
+
},
|
|
59
|
+
|
|
60
|
+
removeItem: (key: string): void => {
|
|
61
|
+
delete cache[key];
|
|
62
|
+
AsyncStorage.removeItem(prefixedKey(key)).catch(() => {
|
|
63
|
+
// Silently fail
|
|
64
|
+
});
|
|
65
|
+
},
|
|
66
|
+
|
|
67
|
+
clear: (): void => {
|
|
68
|
+
const keysToRemove = Object.keys(cache).map(prefixedKey);
|
|
69
|
+
cache = {};
|
|
70
|
+
AsyncStorage.multiRemove(keysToRemove).catch(() => {
|
|
71
|
+
// Silently fail
|
|
72
|
+
});
|
|
73
|
+
},
|
|
74
|
+
|
|
75
|
+
keys: (): string[] => {
|
|
76
|
+
return Object.keys(cache);
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
}
|
|
@@ -1,14 +1,10 @@
|
|
|
1
|
-
import type { SupportedStorageFactory } from "./storage.client";
|
|
2
1
|
import * as MMKV from "react-native-mmkv";
|
|
2
|
+
import { StorageAdapter, type SupportedStorage } from "./storage.adpater-interface";
|
|
3
3
|
|
|
4
|
-
/**
|
|
5
|
-
* Creates a storage factory that uses MMKV for persistence.
|
|
6
|
-
* Each storage key gets its own MMKV instance.
|
|
7
|
-
*/
|
|
8
|
-
export const createMMKVStorageFactory = (): SupportedStorageFactory => {
|
|
9
|
-
return (storageKey: string) => {
|
|
10
|
-
const storage = MMKV.createMMKV({ id: storageKey });
|
|
11
4
|
|
|
5
|
+
export class MMKVStorageAdapter extends StorageAdapter {
|
|
6
|
+
createStorage(storageKey: string): SupportedStorage {
|
|
7
|
+
const storage = MMKV.createMMKV({ id: storageKey });
|
|
12
8
|
return {
|
|
13
9
|
preload: () => {
|
|
14
10
|
storage.getAllKeys();
|
|
@@ -19,5 +15,6 @@ export const createMMKVStorageFactory = (): SupportedStorageFactory => {
|
|
|
19
15
|
clear: () => storage.clearAll(),
|
|
20
16
|
keys: () => storage.getAllKeys(),
|
|
21
17
|
};
|
|
22
|
-
}
|
|
23
|
-
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
|
|
2
|
+
/**
|
|
3
|
+
* A storage interface that is used to store data.
|
|
4
|
+
*/
|
|
5
|
+
export type SupportedStorage = {
|
|
6
|
+
preload: () => void;
|
|
7
|
+
getItem: (key: string) => string | null;
|
|
8
|
+
setItem: (key: string, value: string) => void;
|
|
9
|
+
removeItem: (key: string) => void;
|
|
10
|
+
clear: () => void;
|
|
11
|
+
keys: () => string[];
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* An interface for a storage adapter.
|
|
16
|
+
* This interface is used to abstract the storage adapter implementation.
|
|
17
|
+
*/
|
|
18
|
+
export abstract class StorageAdapter {
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Creates a storage instance for a given storage key.
|
|
22
|
+
* @param storageKey - The key to create the storage instance for.
|
|
23
|
+
* @returns A storage instance.
|
|
24
|
+
*
|
|
25
|
+
* We can have multiple storage instances for different purposes. Hence the storage key is used to create the storage instance.
|
|
26
|
+
*/
|
|
27
|
+
abstract createStorage(storageKey: string): SupportedStorage;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
|
|
@@ -1 +1,2 @@
|
|
|
1
|
-
export * from "./storage.
|
|
1
|
+
export * from "./adapters/storage.adpater-interface";
|
|
2
|
+
export * from "./storage.client";
|
|
@@ -1,19 +1,6 @@
|
|
|
1
1
|
import type { Logger, LoggingClient } from "../logging";
|
|
2
|
+
import type { StorageAdapter, SupportedStorage } from "./adapters/storage.adpater-interface";
|
|
2
3
|
|
|
3
|
-
export type SupportedStorage = {
|
|
4
|
-
preload: () => void;
|
|
5
|
-
getItem: (key: string) => string | null;
|
|
6
|
-
setItem: (key: string, value: string) => void;
|
|
7
|
-
removeItem: (key: string) => void;
|
|
8
|
-
clear: () => void;
|
|
9
|
-
keys: () => string[];
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
export type SupportedStorageFactory = (storageKey: string) => SupportedStorage;
|
|
13
|
-
|
|
14
|
-
export type StorageClientOptions = {
|
|
15
|
-
createStorage: SupportedStorageFactory;
|
|
16
|
-
};
|
|
17
4
|
|
|
18
5
|
export class StorageClient {
|
|
19
6
|
|
|
@@ -21,14 +8,18 @@ export class StorageClient {
|
|
|
21
8
|
|
|
22
9
|
private readonly storage: Map<string, SupportedStorage> = new Map();
|
|
23
10
|
|
|
24
|
-
constructor(
|
|
11
|
+
constructor(
|
|
12
|
+
logging: LoggingClient,
|
|
13
|
+
private readonly orgId: string,
|
|
14
|
+
private readonly projectId: string,
|
|
15
|
+
private readonly storageAdapter: StorageAdapter) {
|
|
25
16
|
this.logger = logging.createLogger({
|
|
26
17
|
name: "StorageClient",
|
|
27
18
|
});
|
|
28
19
|
}
|
|
29
20
|
|
|
30
21
|
private createStorageKey(storageKey: string): string {
|
|
31
|
-
return `teardown:v1:${storageKey}`;
|
|
22
|
+
return `teardown:v1:${this.orgId}:${this.projectId}:${storageKey}`;
|
|
32
23
|
}
|
|
33
24
|
|
|
34
25
|
createStorage(storageKey: string): SupportedStorage {
|
|
@@ -48,20 +39,18 @@ export class StorageClient {
|
|
|
48
39
|
}
|
|
49
40
|
|
|
50
41
|
this.logger.debug(`Creating new storage for ${fullStorageKey}`);
|
|
51
|
-
const newStorage = this.
|
|
42
|
+
const newStorage = this.storageAdapter.createStorage(fullStorageKey);
|
|
52
43
|
newStorage.preload();
|
|
53
44
|
|
|
54
|
-
|
|
55
45
|
const remappedStorage = {
|
|
56
46
|
...newStorage,
|
|
57
47
|
clear: () => {
|
|
58
|
-
this.logger.debug(`Clearing storage for ${fullStorageKey}`);
|
|
59
48
|
this.storage.delete(fullStorageKey);
|
|
60
49
|
},
|
|
61
50
|
}
|
|
62
51
|
|
|
63
52
|
this.storage.set(fullStorageKey, remappedStorage);
|
|
64
|
-
this.logger.
|
|
53
|
+
this.logger.debug(`Storage created for ${fullStorageKey}`);
|
|
65
54
|
|
|
66
55
|
return remappedStorage;
|
|
67
56
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Logger, LoggingClient } from "../logging";
|
|
2
|
+
import 'react-native-get-random-values';
|
|
2
3
|
import { v4 as uuidv4 } from "uuid";
|
|
3
|
-
|
|
4
4
|
export class UtilsClient {
|
|
5
5
|
private readonly logger: Logger;
|
|
6
6
|
|
|
@@ -15,61 +15,5 @@ export class UtilsClient {
|
|
|
15
15
|
const uuid = uuidv4();
|
|
16
16
|
this.logger.debug(`Random UUID generated: ${uuid}`);
|
|
17
17
|
return uuid;
|
|
18
|
-
// this.logger.debug("Generating random UUID");
|
|
19
|
-
// // Generate a random UUID v4 (xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx)
|
|
20
|
-
// // Uses only Math.random and string manipulation, no dependencies.
|
|
21
|
-
// const hex: string[] = [];
|
|
22
|
-
// for (let i = 0; i < 256; ++i) {
|
|
23
|
-
// hex.push((i < 16 ? "0" : "") + i.toString(16));
|
|
24
|
-
// }
|
|
25
|
-
|
|
26
|
-
// // Seeded random number generator using mulberry32
|
|
27
|
-
// let seedValue = 0;
|
|
28
|
-
// if (seed) {
|
|
29
|
-
// // Simple hash function to convert seed string to number
|
|
30
|
-
// for (let i = 0; i < seed.length; i++) {
|
|
31
|
-
// seedValue = ((seedValue << 5) - seedValue + seed.charCodeAt(i)) | 0;
|
|
32
|
-
// }
|
|
33
|
-
// seedValue = Math.abs(seedValue);
|
|
34
|
-
// }
|
|
35
|
-
|
|
36
|
-
// const seededRandom = (): number => {
|
|
37
|
-
// seedValue = (seedValue + 0x6d2b79f5) | 0;
|
|
38
|
-
// let t = Math.imul(seedValue ^ (seedValue >>> 15), 1 | seedValue);
|
|
39
|
-
// t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
|
|
40
|
-
// return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
|
|
41
|
-
// };
|
|
42
|
-
|
|
43
|
-
// const getRandomByte = (): number => Math.floor((seed ? seededRandom() : Math.random()) * 256);
|
|
44
|
-
// const rnds = new Array(16).fill(0).map(getRandomByte);
|
|
45
|
-
|
|
46
|
-
// // Per spec:
|
|
47
|
-
// rnds[6] = (rnds[6]! & 0x0f) | 0x40; // version 4
|
|
48
|
-
// rnds[8] = (rnds[8]! & 0x3f) | 0x80; // variant 10
|
|
49
|
-
|
|
50
|
-
// const uuid =
|
|
51
|
-
// hex[rnds[0]!]! +
|
|
52
|
-
// hex[rnds[1]!]! +
|
|
53
|
-
// hex[rnds[2]!]! +
|
|
54
|
-
// hex[rnds[3]!]! +
|
|
55
|
-
// "-" +
|
|
56
|
-
// hex[rnds[4]!]! +
|
|
57
|
-
// hex[rnds[5]!]! +
|
|
58
|
-
// "-" +
|
|
59
|
-
// hex[rnds[6]!]! +
|
|
60
|
-
// hex[rnds[7]!]! +
|
|
61
|
-
// "-" +
|
|
62
|
-
// hex[rnds[8]!]! +
|
|
63
|
-
// hex[rnds[9]!]! +
|
|
64
|
-
// "-" +
|
|
65
|
-
// hex[rnds[10]!]! +
|
|
66
|
-
// hex[rnds[11]!]! +
|
|
67
|
-
// hex[rnds[12]!]! +
|
|
68
|
-
// hex[rnds[13]!]! +
|
|
69
|
-
// hex[rnds[14]!]! +
|
|
70
|
-
// hex[rnds[15]!]!;
|
|
71
|
-
|
|
72
|
-
// this.logger.debug(`Random UUID generated: ${uuid}`);
|
|
73
|
-
// return uuid;
|
|
74
18
|
}
|
|
75
19
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "../../clients/storage/adapters/async-storage.adapter";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "../../clients/device/adapters/expo.adapter";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "../../clients/storage/adapters/mmkv.adapter";
|
|
@@ -12,7 +12,11 @@ export type UseForceUpdateResult = {
|
|
|
12
12
|
*/
|
|
13
13
|
isUpdateAvailable: boolean;
|
|
14
14
|
/**
|
|
15
|
-
* Whether
|
|
15
|
+
* Whether an update is recommended for this version, but is not required.
|
|
16
|
+
*/
|
|
17
|
+
isUpdateRecommended: boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Whether the the current version is out of date and is forced to be updated.
|
|
16
20
|
*/
|
|
17
21
|
isUpdateRequired: boolean;
|
|
18
22
|
};
|
|
@@ -29,10 +33,15 @@ export const useForceUpdate = (): UseForceUpdateResult => {
|
|
|
29
33
|
return unsubscribe;
|
|
30
34
|
}, [core.forceUpdate]);
|
|
31
35
|
|
|
36
|
+
const isUpdateRequired = versionStatus.type === "update_required";
|
|
37
|
+
const isUpdateRecommended = versionStatus.type === "update_recommended";
|
|
38
|
+
const isUpdateAvailable = isUpdateRequired || isUpdateRecommended || (versionStatus.type === "update_available");
|
|
39
|
+
|
|
32
40
|
return {
|
|
33
41
|
versionStatus,
|
|
34
|
-
isUpdateRequired
|
|
35
|
-
|
|
42
|
+
isUpdateRequired,
|
|
43
|
+
isUpdateRecommended,
|
|
44
|
+
isUpdateAvailable,
|
|
36
45
|
};
|
|
37
46
|
};
|
|
38
47
|
|
package/src/hooks/use-session.ts
CHANGED
|
@@ -13,10 +13,13 @@ export const useSession = (): UseSessionResult => {
|
|
|
13
13
|
|
|
14
14
|
useEffect(() => {
|
|
15
15
|
const unsubscribe = core.identity.onIdentifyStateChange((state) => {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
switch (state.type) {
|
|
17
|
+
case "identified":
|
|
18
|
+
setSession(state.session);
|
|
19
|
+
break;
|
|
20
|
+
case "unidentified":
|
|
21
|
+
setSession(null);
|
|
22
|
+
break;
|
|
20
23
|
}
|
|
21
24
|
});
|
|
22
25
|
return unsubscribe;
|
package/src/teardown.core.ts
CHANGED
|
@@ -2,8 +2,8 @@ import { ApiClient } from "./clients/api";
|
|
|
2
2
|
import { DeviceClient, type DeviceClientOptions } from "./clients/device/device.client";
|
|
3
3
|
import { ForceUpdateClient, type ForceUpdateClientOptions } from "./clients/force-update";
|
|
4
4
|
import { IdentityClient } from "./clients/identity";
|
|
5
|
-
import { type LogLevel,
|
|
6
|
-
import { StorageClient, type
|
|
5
|
+
import { LoggingClient, type LogLevel, type Logger } from "./clients/logging";
|
|
6
|
+
import { StorageClient, type StorageAdapter } from "./clients/storage";
|
|
7
7
|
import { UtilsClient } from "./clients/utils/utils.client";
|
|
8
8
|
|
|
9
9
|
export type TeardownCoreOptions = {
|
|
@@ -11,7 +11,7 @@ export type TeardownCoreOptions = {
|
|
|
11
11
|
project_id: string;
|
|
12
12
|
api_key: string;
|
|
13
13
|
// environment_slug: string; // TODO: add this back in
|
|
14
|
-
|
|
14
|
+
storageAdapter: StorageAdapter;
|
|
15
15
|
deviceAdapter: DeviceClientOptions["adapter"];
|
|
16
16
|
forceUpdate?: ForceUpdateClientOptions;
|
|
17
17
|
};
|
|
@@ -30,13 +30,18 @@ export class TeardownCore {
|
|
|
30
30
|
this.options = options;
|
|
31
31
|
|
|
32
32
|
this.logging = new LoggingClient();
|
|
33
|
-
|
|
33
|
+
this.setLogLevel("verbose");
|
|
34
34
|
|
|
35
35
|
this.logger = this.logging.createLogger({
|
|
36
36
|
name: "TeardownCore",
|
|
37
37
|
});
|
|
38
38
|
this.utils = new UtilsClient(this.logging);
|
|
39
|
-
this.storage = new StorageClient(
|
|
39
|
+
this.storage = new StorageClient(
|
|
40
|
+
this.logging,
|
|
41
|
+
this.options.org_id,
|
|
42
|
+
this.options.project_id,
|
|
43
|
+
this.options.storageAdapter
|
|
44
|
+
);
|
|
40
45
|
this.api = new ApiClient(this.logging, this.storage, {
|
|
41
46
|
org_id: this.options.org_id,
|
|
42
47
|
project_id: this.options.project_id,
|
|
@@ -57,12 +62,17 @@ export class TeardownCore {
|
|
|
57
62
|
|
|
58
63
|
void this.initialize().catch((error) => {
|
|
59
64
|
this.logger.error("Error initializing TeardownCore", { error });
|
|
65
|
+
}).then(() => {
|
|
66
|
+
this.logger.debug("TeardownCore initialized");
|
|
60
67
|
});
|
|
61
68
|
|
|
62
69
|
}
|
|
63
70
|
|
|
64
71
|
async initialize(): Promise<void> {
|
|
72
|
+
// Initialize identity first (loads from storage, then identifies if needed)
|
|
65
73
|
await this.identity.initialize();
|
|
74
|
+
// Then initialize force update (subscribes to identity events)
|
|
75
|
+
this.forceUpdate.initialize();
|
|
66
76
|
}
|
|
67
77
|
|
|
68
78
|
setLogLevel(level: LogLevel): void {
|
|
@@ -70,7 +80,7 @@ export class TeardownCore {
|
|
|
70
80
|
}
|
|
71
81
|
|
|
72
82
|
shutdown(): void {
|
|
73
|
-
this.logger.
|
|
83
|
+
this.logger.debug("Shutting down TeardownCore");
|
|
74
84
|
this.storage.shutdown();
|
|
75
85
|
}
|
|
76
86
|
}
|
package/src/exports/expo.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from "../clients/device/expo-adapter";
|
package/src/exports/mmkv.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from "../clients/storage/mmkv-adapter";
|