@teardown/react-native 2.0.29 → 2.0.32

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.
@@ -0,0 +1,102 @@
1
+ import type { AsyncResult } from "@teardown/types";
2
+ import type { ApiClient } from "../api";
3
+ import type { DeviceClient } from "../device/device.client";
4
+ import type { Logger, LoggingClient } from "../logging";
5
+
6
+ /**
7
+ * Event payload for tracking events
8
+ */
9
+ export interface EventPayload {
10
+ /** Name of the event (e.g., "user_signed_out", "button_clicked") */
11
+ event_name: string;
12
+ /** Type of event */
13
+ event_type?: "action" | "screen_view" | "custom";
14
+ /** Additional properties to attach to the event */
15
+ properties?: Record<string, unknown>;
16
+ /** ISO timestamp. Defaults to current time if not provided */
17
+ timestamp?: string;
18
+ }
19
+
20
+ /**
21
+ * EventsClient - Universal client for sending events to the backend
22
+ *
23
+ * This client provides a centralized way to track events from anywhere in the SDK.
24
+ * It automatically handles device ID retrieval and API communication.
25
+ */
26
+ export class EventsClient {
27
+ private readonly logger: Logger;
28
+
29
+ constructor(
30
+ logging: LoggingClient,
31
+ private readonly api: ApiClient,
32
+ private readonly device: DeviceClient
33
+ ) {
34
+ this.logger = logging.createLogger({
35
+ name: "EventsClient",
36
+ });
37
+ }
38
+
39
+ /**
40
+ * Track a single event
41
+ * @param event - The event payload to track
42
+ * @param sessionId - Optional session ID to associate with the event
43
+ * @returns AsyncResult indicating success/failure
44
+ */
45
+ async track(event: EventPayload, sessionId?: string): AsyncResult<void> {
46
+ return this.trackBatch([event], sessionId);
47
+ }
48
+
49
+ /**
50
+ * Track multiple events in a single batch
51
+ * @param events - Array of event payloads to track
52
+ * @param sessionId - Optional session ID to associate with all events
53
+ * @returns AsyncResult indicating success/failure
54
+ */
55
+ async trackBatch(events: EventPayload[], sessionId?: string): AsyncResult<void> {
56
+ if (events.length === 0) {
57
+ return { success: true, data: undefined };
58
+ }
59
+
60
+ this.logger.debug(`Tracking ${events.length} event(s)`, {
61
+ eventNames: events.map((e) => e.event_name),
62
+ });
63
+
64
+ try {
65
+ const deviceId = await this.device.getDeviceId();
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 } : {}),
76
+ },
77
+ body: {
78
+ events: events.map((event) => ({
79
+ event_name: event.event_name,
80
+ event_type: event.event_type ?? "custom",
81
+ properties: event.properties,
82
+ timestamp: event.timestamp ?? new Date().toISOString(),
83
+ })),
84
+ },
85
+ });
86
+
87
+ if (response.error != null) {
88
+ this.logger.warn("Failed to track events", { error: response.error });
89
+ return { success: false, error: "Failed to track events" };
90
+ }
91
+
92
+ this.logger.debug(`Successfully tracked ${events.length} event(s)`);
93
+ return { success: true, data: undefined };
94
+ } catch (error) {
95
+ this.logger.error("Error tracking events", { error });
96
+ return {
97
+ success: false,
98
+ error: error instanceof Error ? error.message : "Unknown error",
99
+ };
100
+ }
101
+ }
102
+ }
@@ -0,0 +1 @@
1
+ export { type EventPayload, EventsClient } from "./events.client";
@@ -32,9 +32,18 @@ export enum IdentifyVersionStatusEnum {
32
32
  export const InitializingVersionStatusSchema = z.object({ type: z.literal("initializing") });
33
33
  export const CheckingVersionStatusSchema = z.object({ type: z.literal("checking") });
34
34
  export const UpToDateVersionStatusSchema = z.object({ type: z.literal("up_to_date") });
35
- export const UpdateAvailableVersionStatusSchema = z.object({ type: z.literal("update_available") });
36
- export const UpdateRecommendedVersionStatusSchema = z.object({ type: z.literal("update_recommended") });
37
- export const UpdateRequiredVersionStatusSchema = z.object({ type: z.literal("update_required") });
35
+ export const UpdateAvailableVersionStatusSchema = z.object({
36
+ type: z.literal("update_available"),
37
+ releaseNotes: z.string().nullable().optional(),
38
+ });
39
+ export const UpdateRecommendedVersionStatusSchema = z.object({
40
+ type: z.literal("update_recommended"),
41
+ releaseNotes: z.string().nullable().optional(),
42
+ });
43
+ export const UpdateRequiredVersionStatusSchema = z.object({
44
+ type: z.literal("update_required"),
45
+ releaseNotes: z.string().nullable().optional(),
46
+ });
38
47
  export const DisabledVersionStatusSchema = z.object({ type: z.literal("disabled") });
39
48
  /**
40
49
  * The version status schema.
@@ -143,7 +152,7 @@ export class ForceUpdateClient {
143
152
  this.logger.debug(
144
153
  `Identity already identified, syncing version status from: ${currentState.version_info.status}`
145
154
  );
146
- this.updateFromVersionStatus(currentState.version_info.status);
155
+ this.updateFromVersionInfo(currentState.version_info);
147
156
  } else {
148
157
  this.logger.debug(`Identity not yet identified (${currentState.type}), waiting for identify event`);
149
158
  }
@@ -171,7 +180,7 @@ export class ForceUpdateClient {
171
180
 
172
181
  return parsed;
173
182
  } catch (error) {
174
- this.logger.debugError("Error getting version status from storage", { error });
183
+ this.logger.error("Error getting version status from storage", { error });
175
184
  return InitializingVersionStatusSchema.parse({ type: "initializing" });
176
185
  }
177
186
  }
@@ -189,13 +198,19 @@ export class ForceUpdateClient {
189
198
  break;
190
199
  case "identified":
191
200
  this.logger.debug(`Identified with version_info.status: ${state.version_info.status}`);
192
- this.updateFromVersionStatus(state.version_info.status ?? IdentifyVersionStatusEnum.UP_TO_DATE);
201
+ this.updateFromVersionInfo(state.version_info);
193
202
  break;
194
203
  }
195
204
  });
196
205
  }
197
206
 
198
- private updateFromVersionStatus(status?: IdentifyVersionStatusEnum) {
207
+ private updateFromVersionInfo(versionInfo: {
208
+ status: IdentifyVersionStatusEnum;
209
+ update: { release_notes: string | null } | null;
210
+ }) {
211
+ const status = versionInfo.status;
212
+ const releaseNotes = versionInfo.update?.release_notes ?? null;
213
+
199
214
  if (!status) {
200
215
  this.setVersionStatus({ type: "up_to_date" });
201
216
  return;
@@ -203,13 +218,15 @@ export class ForceUpdateClient {
203
218
 
204
219
  switch (status) {
205
220
  case "UPDATE_AVAILABLE":
206
- this.setVersionStatus({ type: "update_available" });
221
+ this.setVersionStatus(releaseNotes ? { type: "update_available", releaseNotes } : { type: "update_available" });
207
222
  break;
208
223
  case "UPDATE_RECOMMENDED":
209
- this.setVersionStatus({ type: "update_recommended" });
224
+ this.setVersionStatus(
225
+ releaseNotes ? { type: "update_recommended", releaseNotes } : { type: "update_recommended" }
226
+ );
210
227
  break;
211
228
  case "UPDATE_REQUIRED":
212
- this.setVersionStatus({ type: "update_required" });
229
+ this.setVersionStatus(releaseNotes ? { type: "update_required", releaseNotes } : { type: "update_required" });
213
230
  break;
214
231
  case "UP_TO_DATE":
215
232
  this.setVersionStatus({ type: "up_to_date" });
@@ -127,6 +127,7 @@ function createMockApiClient(
127
127
  apiKey: "test-api-key",
128
128
  orgId: "test-org-id",
129
129
  projectId: "test-project-id",
130
+ environmentSlug: "production",
130
131
  client: async (endpoint: string, config: ApiCallRecord["config"]) => {
131
132
  calls.push({ endpoint, config });
132
133
 
@@ -635,7 +636,7 @@ describe("IdentityClient", () => {
635
636
 
636
637
  // Should have debug logs about state transitions
637
638
  // When already identified, identify() will transition: identified -> identifying -> identified
638
- const debugLogs = mockLogging.getLogs().filter((l) => l.level === "debug" || l.level === "debugInfo");
639
+ const debugLogs = mockLogging.getLogs().filter((l) => l.level === "debug" || l.level === "info");
639
640
  expect(debugLogs.length).toBeGreaterThan(0);
640
641
  // Check that state transitions are logged
641
642
  expect(debugLogs.some((l) => l.message.includes("Identify state"))).toBe(true);
@@ -2,9 +2,11 @@ import type { AsyncResult } from "@teardown/types";
2
2
  import { EventEmitter } from "eventemitter3";
3
3
  import { z } from "zod";
4
4
  import type { ApiClient } from "../api";
5
- import type { DeviceClient } from "../device/device.client";
5
+ import type { DeviceClient, NotificationPlatformEnum } from "../device/device.client";
6
+ import type { EventsClient } from "../events";
6
7
  import { IdentifyVersionStatusEnum } from "../force-update";
7
8
  import type { Logger, LoggingClient } from "../logging";
9
+ import type { NotificationsClient } from "../notifications/notifications.client";
8
10
  import type { StorageClient, SupportedStorage } from "../storage";
9
11
  import type { UtilsClient } from "../utils";
10
12
 
@@ -14,6 +16,26 @@ export interface Persona {
14
16
  email?: string | undefined;
15
17
  }
16
18
 
19
+ export interface SignOutOptions {
20
+ /**
21
+ * Additional properties to include in the sign out event
22
+ */
23
+ properties?: Record<string, unknown>;
24
+ /**
25
+ * Whether to wait for the event to be sent before clearing state.
26
+ * Default: true (fire-and-forget if false)
27
+ */
28
+ waitForEvent?: boolean;
29
+ }
30
+
31
+ export interface UpdateInfo {
32
+ version: string;
33
+ build: string;
34
+ update_id: string;
35
+ effective_date: Date;
36
+ release_notes: string | null;
37
+ }
38
+
17
39
  export interface IdentityUser {
18
40
  session_id: string;
19
41
  device_id: string;
@@ -21,7 +43,7 @@ export interface IdentityUser {
21
43
  token: string;
22
44
  version_info: {
23
45
  status: IdentifyVersionStatusEnum;
24
- update: null;
46
+ update: UpdateInfo | null;
25
47
  };
26
48
  }
27
49
 
@@ -49,13 +71,50 @@ export const VersionStatusResponseSchema = z.object({
49
71
  latest_version: z.string().optional(),
50
72
  });
51
73
 
74
+ export const UpdateInfoSchema = z.object({
75
+ version: z.string(),
76
+ build: z.string(),
77
+ update_id: z.string(),
78
+ effective_date: z.coerce.date(),
79
+ release_notes: z.string().nullable(),
80
+ });
81
+
82
+ /** Type guard to check if update has nested structure */
83
+ function isNestedUpdate(update: unknown): update is { status: string; update: UpdateInfo } {
84
+ return typeof update === "object" && update !== null && "status" in update && "update" in update;
85
+ }
86
+
87
+ // Accept either the nested schema from API or the flat UpdateInfo schema
88
+ export const VersionInfoResponseSchema = z.object({
89
+ status: z.enum(IdentifyVersionStatusEnum),
90
+ update: z.union([
91
+ z.object({
92
+ status: z.enum(IdentifyVersionStatusEnum),
93
+ update: UpdateInfoSchema,
94
+ }),
95
+ UpdateInfoSchema,
96
+ z.null(),
97
+ ]),
98
+ });
99
+
52
100
  export const IdentifiedSessionStateSchema = z.object({
53
101
  type: z.literal("identified"),
54
102
  session: SessionSchema,
55
- version_info: z.object({
56
- status: z.enum(IdentifyVersionStatusEnum),
57
- update: z.null(),
58
- }),
103
+ version_info: z
104
+ .object({
105
+ status: z.enum(IdentifyVersionStatusEnum),
106
+ update: UpdateInfoSchema.nullable(),
107
+ })
108
+ .transform((data) => {
109
+ // Flatten nested structure if present
110
+ if (isNestedUpdate(data.update)) {
111
+ return {
112
+ status: data.status,
113
+ update: data.update.update,
114
+ };
115
+ }
116
+ return data;
117
+ }),
59
118
  });
60
119
 
61
120
  export const IdentifyStateSchema = z.discriminatedUnion("type", [
@@ -89,7 +148,9 @@ export class IdentityClient {
89
148
  utils: UtilsClient,
90
149
  storage: StorageClient,
91
150
  private readonly api: ApiClient,
92
- private readonly device: DeviceClient
151
+ private readonly device: DeviceClient,
152
+ private readonly events: EventsClient,
153
+ private readonly notificationsClient?: NotificationsClient
93
154
  ) {
94
155
  this.logger = logging.createLogger({
95
156
  name: "IdentityClient",
@@ -113,7 +174,7 @@ export class IdentityClient {
113
174
  this.logger.debug(`Initialized with state: ${this.identifyState.type}`);
114
175
  } catch (error) {
115
176
  // Silently fail on errors - we'll re-identify on app boot if needed
116
- this.logger.debugError("Error initializing IdentityClient", { error });
177
+ this.logger.error("Error initializing IdentityClient", { error });
117
178
  this.identifyState = { type: "unidentified" };
118
179
  }
119
180
 
@@ -126,17 +187,17 @@ export class IdentityClient {
126
187
  const stored = this.storage.getItem(IDENTIFY_STORAGE_KEY);
127
188
 
128
189
  if (stored == null) {
129
- this.logger.debugInfo("No stored identity state, returning unidentified");
190
+ this.logger.info("No stored identity state, returning unidentified");
130
191
  return UnidentifiedSessionStateSchema.parse({ type: "unidentified" });
131
192
  }
132
193
 
133
194
  const parsed = IdentifyStateSchema.parse(JSON.parse(stored));
134
- this.logger.debugInfo(`Parsed identity state from storage: ${parsed.type}`);
195
+ this.logger.info(`Parsed identity state from storage: ${parsed.type}`);
135
196
 
136
197
  // "identifying" is a transient state - if we restore it, treat as unidentified
137
198
  // This can happen if the app was killed during an identify call
138
199
  if (parsed.type === "identifying") {
139
- this.logger.debugInfo("Found stale 'identifying' state in storage, resetting to unidentified");
200
+ this.logger.info("Found stale 'identifying' state in storage, resetting to unidentified");
140
201
  // Clear the stale state from storage immediately
141
202
  this.storage.removeItem(IDENTIFY_STORAGE_KEY);
142
203
  return UnidentifiedSessionStateSchema.parse({ type: "unidentified" });
@@ -144,7 +205,7 @@ export class IdentityClient {
144
205
 
145
206
  return parsed;
146
207
  } catch (error) {
147
- this.logger.debugError("Error getting identify state from storage", { error });
208
+ this.logger.error("Error getting identify state from storage", { error });
148
209
  return { type: "unidentified" };
149
210
  }
150
211
  }
@@ -155,11 +216,11 @@ export class IdentityClient {
155
216
 
156
217
  private setIdentifyState(newState: IdentifyState): void {
157
218
  if (this.identifyState.type === newState.type) {
158
- this.logger.debugInfo(`Identify state already set: ${this.identifyState.type}`);
219
+ this.logger.info(`Identify state already set: ${this.identifyState.type}`);
159
220
  return;
160
221
  }
161
222
 
162
- this.logger.debugInfo(`Identify state: ${this.identifyState.type} -> ${newState.type}`);
223
+ this.logger.info(`Identify state: ${this.identifyState.type} -> ${newState.type}`);
163
224
  this.identifyState = newState;
164
225
  this.saveIdentifyStateToStorage(newState);
165
226
  this.emitter.emit("IDENTIFY_STATE_CHANGED", newState);
@@ -187,11 +248,109 @@ export class IdentityClient {
187
248
  this.emitter.removeAllListeners("IDENTIFY_STATE_CHANGED");
188
249
  }
189
250
 
190
- public reset() {
251
+ /**
252
+ * Reset identity state to unidentified.
253
+ * Clears stored state and emits state change.
254
+ */
255
+ public reset(): void {
256
+ this.setIdentifyState({ type: "unidentified" });
257
+ }
258
+
259
+ /**
260
+ * Internal method to clear identity state.
261
+ * Used by signOut() and signOutAll().
262
+ */
263
+ private clearIdentityState(): void {
191
264
  this.storage.removeItem(IDENTIFY_STORAGE_KEY);
192
265
  this.setIdentifyState({ type: "unidentified" });
193
266
  }
194
267
 
268
+ /**
269
+ * Sign out the current user.
270
+ * Sends a sign_out event to the backend and clears session/user identity.
271
+ * The deviceId is preserved - the same device will be recognized on next identify.
272
+ *
273
+ * @param options - Optional configuration for the sign out
274
+ * @returns AsyncResult indicating success/failure of the event send
275
+ */
276
+ async signOut(options?: SignOutOptions): AsyncResult<void> {
277
+ this.logger.info("Signing out user");
278
+
279
+ const session = this.getSessionState();
280
+ const waitForEvent = options?.waitForEvent ?? true;
281
+
282
+ // Send event FIRST while session/device data still exists
283
+ const eventPromise = this.events.track(
284
+ {
285
+ event_name: "user_signed_out",
286
+ event_type: "action",
287
+ properties: {
288
+ sign_out_type: "sign_out",
289
+ session_id: session?.session_id,
290
+ user_id: session?.user_id,
291
+ ...options?.properties,
292
+ },
293
+ },
294
+ session?.session_id
295
+ );
296
+
297
+ if (waitForEvent) {
298
+ const result = await eventPromise;
299
+ // Clear state regardless of event send result
300
+ this.clearIdentityState();
301
+ return result;
302
+ }
303
+
304
+ // Fire-and-forget mode - don't await but still clear state
305
+ void eventPromise;
306
+ this.clearIdentityState();
307
+ return { success: true, data: undefined };
308
+ }
309
+
310
+ /**
311
+ * Sign out and reset all state including deviceId.
312
+ * Sends a sign_out_all event to the backend and clears all SDK state.
313
+ * The device will appear as a fresh install on next identify.
314
+ *
315
+ * @param options - Optional configuration for the sign out
316
+ * @returns AsyncResult indicating success/failure of the event send
317
+ */
318
+ async signOutAll(options?: SignOutOptions): AsyncResult<void> {
319
+ this.logger.info("Signing out all - full reset");
320
+
321
+ const session = this.getSessionState();
322
+ const waitForEvent = options?.waitForEvent ?? true;
323
+
324
+ // Send event FIRST while session/device data still exists
325
+ const eventPromise = this.events.track(
326
+ {
327
+ event_name: "user_signed_out",
328
+ event_type: "action",
329
+ properties: {
330
+ sign_out_type: "sign_out_all",
331
+ session_id: session?.session_id,
332
+ user_id: session?.user_id,
333
+ ...options?.properties,
334
+ },
335
+ },
336
+ session?.session_id
337
+ );
338
+
339
+ if (waitForEvent) {
340
+ const result = await eventPromise;
341
+ // Clear all state regardless of event send result
342
+ this.clearIdentityState();
343
+ this.device.reset();
344
+ return result;
345
+ }
346
+
347
+ // Fire-and-forget mode - don't await but still clear state
348
+ void eventPromise;
349
+ this.clearIdentityState();
350
+ this.device.reset();
351
+ return { success: true, data: undefined };
352
+ }
353
+
195
354
  /**
196
355
  * Re-identify the current persona to refresh session data.
197
356
  * Only works if already identified.
@@ -223,8 +382,84 @@ export class IdentityClient {
223
382
  }
224
383
  }
225
384
 
385
+ /**
386
+ * Extract error message from API validation error response (422)
387
+ */
388
+ private extractValidationErrorMessage(errorValue: unknown): string {
389
+ if (typeof errorValue === "string") return errorValue;
390
+ if (typeof errorValue === "object" && errorValue !== null) {
391
+ const obj = errorValue as { summary?: string; message?: string };
392
+ if (obj.summary) return obj.summary;
393
+ if (obj.message) return obj.message;
394
+ }
395
+ return "Unknown error";
396
+ }
397
+
398
+ /**
399
+ * Extract error message from API error response (non-422)
400
+ */
401
+ private extractErrorMessage(errorValue: unknown): string {
402
+ if (typeof errorValue === "string") return errorValue;
403
+ if (typeof errorValue === "object" && errorValue !== null) {
404
+ const obj = errorValue as { code?: string; message?: string };
405
+ if (obj.message) return obj.message;
406
+ }
407
+ return "Unknown error";
408
+ }
409
+
410
+ /**
411
+ * Get push notification info if notifications client is configured
412
+ */
413
+ private async getNotificationsInfo(): Promise<
414
+ | {
415
+ push: {
416
+ enabled: boolean;
417
+ granted: boolean;
418
+ token: string | null;
419
+ platform: NotificationPlatformEnum;
420
+ };
421
+ }
422
+ | undefined
423
+ > {
424
+ if (!this.notificationsClient) return undefined;
425
+
426
+ this.logger.debug("Getting push notification token...");
427
+ const token = await this.notificationsClient.getToken();
428
+ this.logger.debug(`Push token retrieved: ${token ? "yes" : "no"}, platform: ${this.notificationsClient.platform}`);
429
+
430
+ return {
431
+ push: {
432
+ enabled: true,
433
+ granted: token !== null,
434
+ token,
435
+ platform: this.notificationsClient.platform,
436
+ },
437
+ };
438
+ }
439
+
440
+ /**
441
+ * Flatten nested version_info structure from API response
442
+ */
443
+ private flattenVersionInfo(rawVersionInfo: { status: string; update: unknown }): {
444
+ status: IdentifyVersionStatusEnum;
445
+ update: UpdateInfo | null;
446
+ } {
447
+ let flattenedUpdate: UpdateInfo | null = null;
448
+
449
+ if (rawVersionInfo.update) {
450
+ flattenedUpdate = isNestedUpdate(rawVersionInfo.update)
451
+ ? rawVersionInfo.update.update
452
+ : (rawVersionInfo.update as UpdateInfo);
453
+ }
454
+
455
+ return {
456
+ status: rawVersionInfo.status as IdentifyVersionStatusEnum,
457
+ update: flattenedUpdate,
458
+ };
459
+ }
460
+
226
461
  async identify(user?: Persona): AsyncResult<IdentityUser> {
227
- this.logger.debugInfo(`Identifying user with persona: ${user?.name ?? "none"}`);
462
+ this.logger.info(`Identifying user with persona: ${user?.name ?? "none"}`);
228
463
  const previousState = this.identifyState;
229
464
  this.setIdentifyState({ type: "identifying" });
230
465
 
@@ -233,6 +468,8 @@ export class IdentityClient {
233
468
  this.logger.debug("Getting device ID...");
234
469
  const deviceId = await this.device.getDeviceId();
235
470
  const deviceInfo = await this.device.getDeviceInfo();
471
+ const notificationsInfo = await this.getNotificationsInfo();
472
+
236
473
  this.logger.debug("Calling identify API...");
237
474
  const response = await this.api.client("/v1/identify", {
238
475
  method: "POST",
@@ -240,7 +477,7 @@ export class IdentityClient {
240
477
  "td-api-key": this.api.apiKey,
241
478
  "td-org-id": this.api.orgId,
242
479
  "td-project-id": this.api.projectId,
243
- "td-environment-slug": "production",
480
+ "td-environment-slug": this.api.environmentSlug,
244
481
  "td-device-id": deviceId,
245
482
  },
246
483
  body: {
@@ -251,57 +488,44 @@ export class IdentityClient {
251
488
  application: deviceInfo.application,
252
489
  hardware: deviceInfo.hardware,
253
490
  update: null,
491
+ notifications: notificationsInfo,
254
492
  },
255
493
  },
256
494
  });
257
495
 
258
- this.logger.debugInfo(`Identify API response received`);
496
+ this.logger.info(`Identify API response received`);
259
497
  if (response.error != null) {
260
498
  this.logger.warn("Identify API error", response.error.status, response.error.value);
261
499
  this.setIdentifyState(previousState);
262
500
 
263
- if (response.error.status === 422) {
264
- this.logger.warn("422 Error identifying user", response.error.value);
265
- return {
266
- success: false,
267
- error: response.error.value.message ?? "Unknown error",
268
- };
269
- }
270
-
271
- const value = response.error.value;
272
- return {
273
- success: false,
274
- error: value?.error?.message ?? "Unknown error",
275
- };
501
+ const errorMessage =
502
+ response.error.status === 422
503
+ ? this.extractValidationErrorMessage(response.error.value)
504
+ : this.extractErrorMessage(response.error.value);
505
+
506
+ return { success: false, error: errorMessage };
276
507
  }
277
508
 
509
+ const parsedSession = SessionSchema.parse(response.data.data);
510
+ const flattenedVersionInfo = this.flattenVersionInfo(response.data.data.version_info);
511
+
512
+ const identityUser: IdentityUser = {
513
+ ...parsedSession,
514
+ version_info: flattenedVersionInfo,
515
+ };
516
+
278
517
  this.setIdentifyState({
279
518
  type: "identified",
280
- session: response.data.data,
281
- version_info: {
282
- status: response.data.data.version_info.status,
283
- update: null,
284
- },
519
+ session: parsedSession,
520
+ version_info: flattenedVersionInfo,
285
521
  });
286
522
 
287
- return {
288
- success: true,
289
- data: {
290
- ...response.data.data,
291
- version_info: {
292
- status: response.data.data.version_info.status,
293
- update: null,
294
- },
295
- },
296
- };
523
+ return { success: true, data: identityUser };
297
524
  },
298
525
  (error) => {
299
526
  this.logger.error("Error identifying user", error);
300
527
  this.setIdentifyState(previousState);
301
- return {
302
- success: false,
303
- error: error.message,
304
- };
528
+ return { success: false, error: error.message };
305
529
  }
306
530
  );
307
531
  }
@@ -57,10 +57,6 @@ export class Logger {
57
57
  warn: console.warn.bind(console),
58
58
  trace: console.trace.bind(console),
59
59
  debug: console.debug.bind(console),
60
- debugError: console.debug.bind(console, "Error: "),
61
- debugWarn: console.debug.bind(console, "Warning: "),
62
- debugInfo: console.debug.bind(console, "Info: "),
63
- debugVerbose: console.debug.bind(console, "Verbose: "),
64
60
  };
65
61
 
66
62
  constructor(private readonly options: LoggerOptions) {}
@@ -93,24 +89,4 @@ export class Logger {
93
89
  if (!this.options.loggingClient.shouldLog("verbose")) return;
94
90
  this.boundConsole.debug(`${this.prefix} ${message}`, ...args);
95
91
  }
96
-
97
- debugError(message: string, ...args: unknown[]) {
98
- if (!this.options.loggingClient.shouldLog("verbose")) return;
99
- this.boundConsole.debugError(`${this.prefix} ${message}`, ...args);
100
- }
101
-
102
- debugWarn(message: string, ...args: unknown[]) {
103
- if (!this.options.loggingClient.shouldLog("verbose")) return;
104
- this.boundConsole.debugWarn(`${this.prefix} ${message}`, ...args);
105
- }
106
-
107
- debugInfo(message: string, ...args: unknown[]) {
108
- if (!this.options.loggingClient.shouldLog("verbose")) return;
109
- this.boundConsole.debugInfo(`${this.prefix} ${message}`, ...args);
110
- }
111
-
112
- debugVerbose(message: string, ...args: unknown[]) {
113
- if (!this.options.loggingClient.shouldLog("verbose")) return;
114
- this.boundConsole.debugVerbose(`${this.prefix} ${message}`, ...args);
115
- }
116
92
  }