@teardown/react-native 2.0.9 → 2.0.11
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 +4 -4
- package/src/clients/api/api.client.ts +1 -1
- package/src/clients/force-update/force-update.client.test.ts +7 -7
- package/src/clients/identity/identity.client.test.ts +18 -18
- 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/teardown.core.ts +3 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@teardown/react-native",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.11",
|
|
4
4
|
"private": false,
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -50,9 +50,9 @@
|
|
|
50
50
|
"nuke": "cd ../../ && bun run nuke"
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
|
-
"@teardown/ingest-api": "0.1.
|
|
54
|
-
"@teardown/schemas": "0.1.
|
|
55
|
-
"@teardown/types": "0.1.
|
|
53
|
+
"@teardown/ingest-api": "0.1.46",
|
|
54
|
+
"@teardown/schemas": "0.1.46",
|
|
55
|
+
"@teardown/types": "0.1.46",
|
|
56
56
|
"eventemitter3": "^5.0.1",
|
|
57
57
|
"react-native-get-random-values": "^2.0.0",
|
|
58
58
|
"uuid": "^13.0.0",
|
|
@@ -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";
|
|
@@ -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
|
|
|
@@ -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));
|
|
@@ -608,7 +608,7 @@ describe("IdentityClient", () => {
|
|
|
608
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();
|
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();
|