@teardown/react-native 2.0.33 → 2.0.34

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@teardown/react-native",
3
- "version": "2.0.33",
3
+ "version": "2.0.34",
4
4
  "type": "module",
5
5
  "private": false,
6
6
  "publishConfig": {
@@ -52,9 +52,9 @@
52
52
  "prepublishOnly": "bun run build"
53
53
  },
54
54
  "dependencies": {
55
- "@teardown/ingest-api": "2.0.33",
56
- "@teardown/schemas": "2.0.33",
57
- "@teardown/types": "2.0.33",
55
+ "@teardown/ingest-api": "2.0.34",
56
+ "@teardown/schemas": "2.0.34",
57
+ "@teardown/types": "2.0.34",
58
58
  "eventemitter3": "^5.0.1",
59
59
  "react-native-get-random-values": "^2.0.0",
60
60
  "uuid": "^13.0.0",
@@ -62,9 +62,8 @@
62
62
  },
63
63
  "devDependencies": {
64
64
  "@biomejs/biome": "2.3.10",
65
- "@elysiajs/eden": "1.4.5",
66
65
  "@react-native-firebase/messaging": "*",
67
- "@teardown/tsconfig": "2.0.33",
66
+ "@teardown/tsconfig": "2.0.34",
68
67
  "@types/bun": "1.3.5",
69
68
  "@types/react": "~19.1.0",
70
69
  "@types/uuid": "^11.0.0",
@@ -1,9 +1,8 @@
1
- import type * as Eden from "@elysiajs/eden";
2
- import * as IngestApi from "@teardown/ingest-api";
1
+ import { createIngestClient, type Endpoints, type IngestClient, type RequestOptions } from "@teardown/ingest-api";
3
2
  import type { LoggingClient } from "../logging";
4
3
  import type { StorageClient } from "../storage";
5
4
 
6
- export type { Eden, IngestApi };
5
+ export type { IngestClient, Endpoints, RequestOptions };
7
6
 
8
7
  const TEARDOWN_INGEST_URL = "https://ingest.teardown.dev";
9
8
  const TEARDOWN_API_KEY_HEADER = "td-api-key";
@@ -11,7 +10,7 @@ const TEARDOWN_ORG_ID_HEADER = "td-org-id";
11
10
  const TEARDOWN_PROJECT_ID_HEADER = "td-project-id";
12
11
  const TEARDOWN_ENVIRONMENT_SLUG_HEADER = "td-environment-slug";
13
12
 
14
- export type ApiClientOptions = {
13
+ export interface ApiClientOptions {
15
14
  /**
16
15
  * The API key.
17
16
  */
@@ -30,29 +29,23 @@ export type ApiClientOptions = {
30
29
  * @default "production"
31
30
  */
32
31
  environment_slug?: string | null;
33
- /**
34
- * A function that will be called before each request.
35
- * @param endpoint The endpoint being requested.
36
- * @param options The options for the request.
37
- * @returns The options for the request.
38
- */
39
- onRequest?: (endpoint: IngestApi.Endpoints, options: IngestApi.RequestOptions) => Promise<IngestApi.RequestOptions>;
40
32
  /**
41
33
  * The URL of the ingest API.
42
34
  * @default https://ingest.teardown.dev
43
35
  */
44
36
  ingestUrl?: string;
45
- };
37
+ }
46
38
 
47
39
  export class ApiClient {
48
- public client: IngestApi.Client;
40
+ public client: IngestClient;
49
41
 
50
42
  constructor(
51
43
  _logging: LoggingClient,
52
44
  _storage: StorageClient,
53
45
  private readonly options: ApiClientOptions
54
46
  ) {
55
- this.client = IngestApi.client(options.ingestUrl ?? TEARDOWN_INGEST_URL, {
47
+ this.client = createIngestClient({
48
+ baseUrl: options.ingestUrl ?? TEARDOWN_INGEST_URL,
56
49
  headers: {
57
50
  [TEARDOWN_API_KEY_HEADER]: `Bearer ${this.apiKey}`,
58
51
  [TEARDOWN_ORG_ID_HEADER]: this.orgId,
@@ -64,28 +64,29 @@ export class EventsClient {
64
64
  try {
65
65
  const deviceId = await this.device.getDeviceId();
66
66
 
67
- const response = await this.api.client("/v1/events", {
68
- method: "POST",
69
- headers: {
70
- "td-api-key": this.api.apiKey,
71
- "td-org-id": this.api.orgId,
72
- "td-project-id": this.api.projectId,
73
- "td-environment-slug": this.api.environmentSlug,
74
- "td-device-id": deviceId,
75
- ...(sessionId ? { "td-session-id": sessionId } : {}),
67
+ const { error } = await this.api.client.POST("/v1/events", {
68
+ params: {
69
+ header: {
70
+ "td-api-key": this.api.apiKey,
71
+ "td-org-id": this.api.orgId,
72
+ "td-project-id": this.api.projectId,
73
+ "td-environment-slug": this.api.environmentSlug,
74
+ "td-device-id": deviceId,
75
+ ...(sessionId ? { "td-session-id": sessionId } : {}),
76
+ },
76
77
  },
77
78
  body: {
78
79
  events: events.map((event) => ({
79
80
  event_name: event.event_name,
80
81
  event_type: event.event_type ?? "custom",
81
- properties: event.properties,
82
+ properties: event.properties as Record<string, unknown>,
82
83
  timestamp: event.timestamp ?? new Date().toISOString(),
83
84
  })),
84
85
  },
85
86
  });
86
87
 
87
- if (response.error != null) {
88
- this.logger.warn("Failed to track events", { error: response.error });
88
+ if (error) {
89
+ this.logger.warn("Failed to track events", { error });
89
90
  return { success: false, error: "Failed to track events" };
90
91
  }
91
92
 
@@ -61,19 +61,19 @@ function createMockUtilsClient() {
61
61
  function createMockDeviceClient(
62
62
  overrides: Partial<{
63
63
  deviceId: string;
64
- timestamp: string;
64
+ timestamp: Date;
65
65
  application: { name: string; version: string; build: string; bundle_id: string };
66
66
  hardware: { brand: string; model: string; device_type: string };
67
- os: { name: string; version: string };
67
+ os: { name: string; version: string; platform: string };
68
68
  notifications: { push_token: string | null; platform: string | null };
69
69
  update: null;
70
70
  }> = {}
71
71
  ) {
72
72
  const defaultDeviceInfo = {
73
- timestamp: new Date().toISOString(),
73
+ timestamp: new Date(),
74
74
  application: { name: "TestApp", version: "1.0.0", build: "100", bundle_id: "com.test.app" },
75
75
  hardware: { brand: "Apple", model: "iPhone 15", device_type: "PHONE" },
76
- os: { name: "iOS", version: "17.0" },
76
+ os: { name: "iOS", version: "17.0", platform: "iOS" },
77
77
  notifications: { push_token: null, platform: null },
78
78
  update: null,
79
79
  };
@@ -84,15 +84,23 @@ function createMockDeviceClient(
84
84
  ...defaultDeviceInfo,
85
85
  ...overrides,
86
86
  }),
87
+ reset: () => {},
88
+ };
89
+ }
90
+
91
+ function createMockEventsClient() {
92
+ return {
93
+ track: async () => ({ success: true, data: undefined }),
87
94
  };
88
95
  }
89
96
 
90
97
  type ApiCallRecord = {
91
98
  endpoint: string;
92
99
  config: {
93
- method: string;
94
- headers: Record<string, string>;
95
- body: unknown;
100
+ params?: {
101
+ header?: Record<string, string>;
102
+ };
103
+ body?: unknown;
96
104
  };
97
105
  };
98
106
 
@@ -123,49 +131,59 @@ function createMockApiClient(
123
131
 
124
132
  const calls: ApiCallRecord[] = [];
125
133
 
126
- return {
127
- apiKey: "test-api-key",
128
- orgId: "test-org-id",
129
- projectId: "test-project-id",
130
- environmentSlug: "production",
131
- client: async (endpoint: string, config: ApiCallRecord["config"]) => {
132
- calls.push({ endpoint, config });
133
-
134
- if (throwError) {
135
- throw throwError;
136
- }
134
+ const postHandler = async (endpoint: string, config: ApiCallRecord["config"]) => {
135
+ calls.push({ endpoint, config });
137
136
 
138
- if (!success) {
139
- return {
140
- error: {
141
- status: errorStatus ?? 500,
142
- value: {
143
- message: errorMessage ?? "API Error",
144
- error: { message: errorMessage ?? "API Error" },
145
- },
146
- },
147
- data: null,
148
- };
149
- }
137
+ if (throwError) {
138
+ throw throwError;
139
+ }
150
140
 
141
+ if (!success) {
151
142
  return {
152
- error: null,
153
- data: {
154
- data: {
155
- session_id: sessionId,
156
- device_id: deviceId,
157
- user_id: user_id,
158
- token: token,
159
- version_info: { status: versionStatus },
160
- },
143
+ error: {
144
+ message: errorMessage ?? "API Error",
145
+ },
146
+ data: null,
147
+ response: {
148
+ status: errorStatus ?? 500,
161
149
  },
162
150
  };
151
+ }
152
+
153
+ return {
154
+ error: null,
155
+ data: {
156
+ data: {
157
+ session_id: sessionId,
158
+ device_id: deviceId,
159
+ user_id: user_id,
160
+ token: token,
161
+ version_info: { status: versionStatus },
162
+ },
163
+ },
164
+ response: {
165
+ status: 200,
166
+ },
167
+ };
168
+ };
169
+
170
+ return {
171
+ apiKey: "test-api-key",
172
+ orgId: "test-org-id",
173
+ projectId: "test-project-id",
174
+ environmentSlug: "production",
175
+ client: {
176
+ POST: postHandler,
163
177
  },
164
178
  getCalls: () => calls,
165
179
  getLastCall: () => calls[calls.length - 1],
166
180
  clearCalls: () => {
167
181
  calls.length = 0;
168
182
  },
183
+ setPostHandler: (handler: typeof postHandler) => {
184
+ // @ts-expect-error - dynamic override
185
+ return handler as unknown as typeof postHandler;
186
+ },
169
187
  };
170
188
  }
171
189
 
@@ -177,6 +195,7 @@ function createTestClient(
177
195
  utils?: ReturnType<typeof createMockUtilsClient>;
178
196
  api?: ReturnType<typeof createMockApiClient>;
179
197
  device?: ReturnType<typeof createMockDeviceClient>;
198
+ events?: ReturnType<typeof createMockEventsClient>;
180
199
  } = {}
181
200
  ) {
182
201
  const mockLogging = overrides.logging ?? createMockLoggingClient();
@@ -184,13 +203,15 @@ function createTestClient(
184
203
  const mockUtils = overrides.utils ?? createMockUtilsClient();
185
204
  const mockApi = overrides.api ?? createMockApiClient();
186
205
  const mockDevice = overrides.device ?? createMockDeviceClient();
206
+ const mockEvents = overrides.events ?? createMockEventsClient();
187
207
 
188
208
  const client = new IdentityClient(
189
209
  mockLogging as never,
190
210
  mockUtils as never,
191
211
  mockStorage as never,
192
212
  mockApi as never,
193
- mockDevice as never
213
+ mockDevice as never,
214
+ mockEvents as never
194
215
  );
195
216
 
196
217
  return {
@@ -200,6 +221,7 @@ function createTestClient(
200
221
  mockUtils,
201
222
  mockApi,
202
223
  mockDevice,
224
+ mockEvents,
203
225
  };
204
226
  }
205
227
 
@@ -425,12 +447,12 @@ describe("IdentityClient", () => {
425
447
  const mockApi = createMockApiClient({ success: false, errorStatus: 422 });
426
448
  // Override to return null message
427
449
  // @ts-expect-error - message is not yet implemented
428
- mockApi.client = async () => ({
429
- error: {
450
+ mockApi.client.POST = async () => ({
451
+ error: {},
452
+ data: null,
453
+ response: {
430
454
  status: 422,
431
- value: { message: null, error: { message: null } },
432
455
  },
433
- data: null,
434
456
  });
435
457
 
436
458
  const { client } = createTestClient({ api: mockApi });
@@ -474,7 +496,7 @@ describe("IdentityClient", () => {
474
496
 
475
497
  test("handles non-Error thrown exceptions", async () => {
476
498
  const mockApi = createMockApiClient();
477
- mockApi.client = async () => {
499
+ mockApi.client.POST = async () => {
478
500
  throw "string error"; // Non-Error throw
479
501
  };
480
502
 
@@ -542,7 +564,6 @@ describe("IdentityClient", () => {
542
564
 
543
565
  const lastCall = mockApi.getLastCall();
544
566
  expect(lastCall.endpoint).toBe("/v1/identify");
545
- expect(lastCall.config.method).toBe("POST");
546
567
  });
547
568
 
548
569
  test("sends correct headers to API", async () => {
@@ -551,19 +572,20 @@ describe("IdentityClient", () => {
551
572
  await client.identify();
552
573
 
553
574
  const lastCall = mockApi.getLastCall();
554
- expect(lastCall.config.headers["td-api-key"]).toBe("test-api-key");
555
- expect(lastCall.config.headers["td-org-id"]).toBe("test-org-id");
556
- expect(lastCall.config.headers["td-project-id"]).toBe("test-project-id");
557
- expect(lastCall.config.headers["td-environment-slug"]).toBe("production");
558
- expect(lastCall.config.headers["td-device-id"]).toBe("mock-device-id");
575
+ expect(lastCall.config.params?.header?.["td-api-key"]).toBe("test-api-key");
576
+ expect(lastCall.config.params?.header?.["td-org-id"]).toBe("test-org-id");
577
+ expect(lastCall.config.params?.header?.["td-project-id"]).toBe("test-project-id");
578
+ expect(lastCall.config.params?.header?.["td-environment-slug"]).toBe("production");
579
+ expect(lastCall.config.params?.header?.["td-device-id"]).toBe("mock-device-id");
559
580
  });
560
581
 
561
582
  test("sends device info in request body", async () => {
583
+ const mockTimestamp = new Date("2024-01-15T10:30:00.000Z");
562
584
  const mockDevice = createMockDeviceClient({
563
585
  deviceId: "custom-device-id",
564
- timestamp: "2024-01-15T10:30:00.000Z",
586
+ timestamp: mockTimestamp,
565
587
  application: { name: "MyApp", version: "2.0.0", build: "200", bundle_id: "com.my.app" },
566
- os: { name: "Android", version: "14" },
588
+ os: { name: "Android", version: "14", platform: "Android" },
567
589
  hardware: { brand: "Samsung", model: "Galaxy S24", device_type: "PHONE" },
568
590
  });
569
591
  const { client, mockApi } = createTestClient({ device: mockDevice });
@@ -576,7 +598,7 @@ describe("IdentityClient", () => {
576
598
  device?: {
577
599
  timestamp: string;
578
600
  application: { name: string; version: string; build: string; bundle_id: string };
579
- os: { name: string; version: string };
601
+ os: { name: string; version: string; platform: string };
580
602
  hardware: { brand: string; model: string; device_type: string };
581
603
  update: null;
582
604
  };
@@ -756,7 +778,7 @@ describe("IdentityClient", () => {
756
778
  await client.identify();
757
779
 
758
780
  // Update mock to return new session
759
- mockApi.client = async (_endpoint: string, _config: ApiCallRecord["config"]) => ({
781
+ mockApi.client.POST = async (_endpoint: string, _config: ApiCallRecord["config"]) => ({
760
782
  error: null,
761
783
  data: {
762
784
  data: {
@@ -767,6 +789,9 @@ describe("IdentityClient", () => {
767
789
  version_info: { status: IdentifyVersionStatusEnum.UP_TO_DATE },
768
790
  },
769
791
  },
792
+ response: {
793
+ status: 200,
794
+ },
770
795
  });
771
796
 
772
797
  const result = await client.refresh();
@@ -916,7 +941,7 @@ describe("IdentityClient", () => {
916
941
  expect(client.getSessionState()?.session_id).toBe("first-session");
917
942
 
918
943
  // Change what API returns
919
- mockApi.client = async () => ({
944
+ mockApi.client.POST = async () => ({
920
945
  error: null,
921
946
  data: {
922
947
  data: {
@@ -927,6 +952,9 @@ describe("IdentityClient", () => {
927
952
  version_info: { status: IdentifyVersionStatusEnum.UP_TO_DATE },
928
953
  },
929
954
  },
955
+ response: {
956
+ status: 200,
957
+ },
930
958
  });
931
959
 
932
960
  await client.identify();
@@ -1027,7 +1055,7 @@ describe("IdentityClient", () => {
1027
1055
  resolveApi = resolve;
1028
1056
  });
1029
1057
 
1030
- mockApi.client = async () => {
1058
+ mockApi.client.POST = async () => {
1031
1059
  await apiPromise;
1032
1060
  return {
1033
1061
  error: null,
@@ -1040,6 +1068,9 @@ describe("IdentityClient", () => {
1040
1068
  version_info: { status: IdentifyVersionStatusEnum.UP_TO_DATE },
1041
1069
  },
1042
1070
  },
1071
+ response: {
1072
+ status: 200,
1073
+ },
1043
1074
  };
1044
1075
  };
1045
1076
 
@@ -440,7 +440,7 @@ export class IdentityClient {
440
440
  /**
441
441
  * Flatten nested version_info structure from API response
442
442
  */
443
- private flattenVersionInfo(rawVersionInfo: { status: string; update: unknown }): {
443
+ private flattenVersionInfo(rawVersionInfo: { status: string; update?: unknown }): {
444
444
  status: IdentifyVersionStatusEnum;
445
445
  update: UpdateInfo | null;
446
446
  } {
@@ -471,43 +471,57 @@ export class IdentityClient {
471
471
  const notificationsInfo = await this.getNotificationsInfo();
472
472
 
473
473
  this.logger.debug("Calling identify API...");
474
- const response = await this.api.client("/v1/identify", {
475
- method: "POST",
476
- headers: {
477
- "td-api-key": this.api.apiKey,
478
- "td-org-id": this.api.orgId,
479
- "td-project-id": this.api.projectId,
480
- "td-environment-slug": this.api.environmentSlug,
481
- "td-device-id": deviceId,
474
+ const { data, error, response } = await this.api.client.POST("/v1/identify", {
475
+ params: {
476
+ header: {
477
+ "td-api-key": this.api.apiKey,
478
+ "td-org-id": this.api.orgId,
479
+ "td-project-id": this.api.projectId,
480
+ "td-environment-slug": this.api.environmentSlug,
481
+ "td-device-id": deviceId,
482
+ },
482
483
  },
483
484
  body: {
484
485
  user,
485
486
  device: {
486
- timestamp: deviceInfo.timestamp,
487
- os: deviceInfo.os,
487
+ timestamp: deviceInfo.timestamp?.toISOString(),
488
+ os: {
489
+ platform: deviceInfo.os.platform.toLowerCase() as "ios" | "android" | "web",
490
+ name: deviceInfo.os.name,
491
+ version: deviceInfo.os.version,
492
+ },
488
493
  application: deviceInfo.application,
489
494
  hardware: deviceInfo.hardware,
490
495
  update: null,
491
- notifications: notificationsInfo,
496
+ notifications: notificationsInfo
497
+ ? {
498
+ push: {
499
+ enabled: notificationsInfo.push.enabled,
500
+ granted: notificationsInfo.push.granted,
501
+ token: notificationsInfo.push.token,
502
+ platform: notificationsInfo.push.platform.toLowerCase() as "fcm" | "apns" | "expo",
503
+ },
504
+ }
505
+ : undefined,
492
506
  },
493
507
  },
494
508
  });
495
509
 
496
510
  this.logger.info(`Identify API response received`);
497
- if (response.error != null) {
498
- this.logger.warn("Identify API error", response.error.status, response.error.value);
511
+ if (error) {
512
+ this.logger.warn("Identify API error", response.status, error);
499
513
  this.setIdentifyState(previousState);
500
514
 
501
515
  const errorMessage =
502
- response.error.status === 422
503
- ? this.extractValidationErrorMessage(response.error.value)
504
- : this.extractErrorMessage(response.error.value);
516
+ response.status === 422 ? this.extractValidationErrorMessage(error) : this.extractErrorMessage(error);
505
517
 
506
518
  return { success: false, error: errorMessage };
507
519
  }
508
520
 
509
- const parsedSession = SessionSchema.parse(response.data.data);
510
- const flattenedVersionInfo = this.flattenVersionInfo(response.data.data.version_info);
521
+ const parsedSession = SessionSchema.parse(data.data);
522
+ const flattenedVersionInfo = this.flattenVersionInfo(
523
+ data.data.version_info ?? { status: "up_to_date", update: null }
524
+ );
511
525
 
512
526
  const identityUser: IdentityUser = {
513
527
  ...parsedSession,