@umituz/react-native-firebase 2.4.14 → 2.4.16

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.
Files changed (32) hide show
  1. package/package.json +1 -1
  2. package/src/domains/account-deletion/infrastructure/services/account-deletion.service.ts +29 -21
  3. package/src/domains/account-deletion/infrastructure/services/reauthentication.service.ts +2 -2
  4. package/src/domains/auth/domain/utils/user-metadata.util.ts +72 -0
  5. package/src/domains/auth/domain/utils/user-validation.util.ts +77 -0
  6. package/src/domains/auth/infrastructure/services/apple-auth.service.ts +6 -27
  7. package/src/domains/auth/infrastructure/services/auth-guard.service.ts +39 -33
  8. package/src/domains/auth/infrastructure/services/base/base-auth.service.ts +2 -2
  9. package/src/domains/auth/infrastructure/services/email-auth.service.ts +18 -38
  10. package/src/domains/auth/infrastructure/services/google-auth.service.ts +6 -27
  11. package/src/domains/auth/infrastructure/services/utils/auth-result-converter.util.ts +71 -0
  12. package/src/domains/auth/infrastructure/utils/auth-guard.util.ts +98 -0
  13. package/src/domains/firestore/index.ts +1 -1
  14. package/src/domains/firestore/infrastructure/config/FirestoreClient.ts +3 -16
  15. package/src/domains/firestore/infrastructure/repositories/BaseRepository.ts +1 -4
  16. package/src/domains/firestore/utils/firestore-helper.ts +0 -3
  17. package/src/domains/firestore/utils/operation/operation-executor.util.ts +3 -3
  18. package/src/domains/firestore/utils/result/result.util.ts +7 -25
  19. package/src/index.ts +1 -1
  20. package/src/shared/domain/utils/async-executor.util.ts +1 -6
  21. package/src/shared/domain/utils/error-handler.util.ts +4 -2
  22. package/src/shared/domain/utils/error-handlers/error-checkers.ts +1 -1
  23. package/src/shared/domain/utils/error-handlers/error-converters.ts +6 -28
  24. package/src/shared/domain/utils/executors/basic-executors.util.ts +10 -6
  25. package/src/shared/domain/utils/index.ts +0 -4
  26. package/src/shared/domain/utils/result/result-creators.ts +3 -16
  27. package/src/shared/domain/utils/type-guards.util.ts +33 -8
  28. package/src/shared/domain/utils/validators/firebase.validator.ts +4 -5
  29. package/src/shared/domain/utils/validators/url.validator.ts +2 -2
  30. package/src/shared/domain/utils/validators/user-input.validator.ts +1 -1
  31. package/src/shared/domain/utils/validators/validation.util.ts +63 -0
  32. package/src/shared/domain/utils/executors/error-converters.util.ts +0 -45
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-firebase",
3
- "version": "2.4.14",
3
+ "version": "2.4.16",
4
4
  "description": "Unified Firebase package for React Native apps - Auth and Firestore services using Firebase JS SDK (no native modules).",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -14,8 +14,9 @@ import {
14
14
  reauthenticateWithPassword,
15
15
  reauthenticateWithGoogle,
16
16
  } from "./reauthentication.service";
17
- import { successResult, type Result, toAuthErrorInfo } from "../../../../shared/domain/utils";
17
+ import { successResult, type Result, toErrorInfo } from "../../../../shared/domain/utils";
18
18
  import type { AccountDeletionOptions } from "../../application/ports/reauthentication.types";
19
+ import { validateUserUnchanged } from "../../../auth/domain/utils/user-validation.util";
19
20
 
20
21
  export interface AccountDeletionResult extends Result<void> {
21
22
  requiresReauth?: boolean;
@@ -101,15 +102,16 @@ export async function deleteCurrentUser(
101
102
  }
102
103
 
103
104
  try {
104
- // FIX: Verify user hasn't changed before deletion
105
- const currentUserId = auth.currentUser?.uid;
106
- if (currentUserId !== originalUserId) {
105
+ // Verify user hasn't changed before deletion
106
+ const validation = validateUserUnchanged(auth, originalUserId);
107
+ if (!('valid' in validation)) {
108
+ // User changed - validation is a FailureResult
107
109
  if (typeof __DEV__ !== "undefined" && __DEV__) {
108
110
  console.log("[deleteCurrentUser] User changed during operation");
109
111
  }
110
112
  return {
111
113
  success: false,
112
- error: { code: "auth/user-changed", message: "User changed during operation" },
114
+ error: validation.error,
113
115
  requiresReauth: false
114
116
  };
115
117
  }
@@ -131,7 +133,7 @@ export async function deleteCurrentUser(
131
133
  if (typeof __DEV__ !== "undefined" && __DEV__) {
132
134
  console.error("[deleteCurrentUser] deleteUser failed:", error);
133
135
  }
134
- const errorInfo = toAuthErrorInfo(error);
136
+ const errorInfo = toErrorInfo(error, 'auth/failed');
135
137
  const code = errorInfo.code;
136
138
  const message = errorInfo.message;
137
139
 
@@ -160,17 +162,18 @@ async function attemptReauth(user: User, options: AccountDeletionOptions, origin
160
162
  console.log("[attemptReauth] Called");
161
163
  }
162
164
 
163
- // FIX: Verify user hasn't changed if originalUserId provided
165
+ // Verify user hasn't changed if originalUserId provided
164
166
  if (originalUserId) {
165
167
  const auth = getFirebaseAuth();
166
- const currentUserId = auth?.currentUser?.uid;
167
- if (currentUserId && currentUserId !== originalUserId) {
168
+ const validation = validateUserUnchanged(auth, originalUserId);
169
+ if (!('valid' in validation)) {
170
+ // User changed - validation is a FailureResult
168
171
  if (typeof __DEV__ !== "undefined" && __DEV__) {
169
172
  console.log("[attemptReauth] User changed during reauthentication");
170
173
  }
171
174
  return {
172
175
  success: false,
173
- error: { code: "auth/user-changed", message: "User changed during operation" },
176
+ error: validation.error,
174
177
  requiresReauth: false
175
178
  };
176
179
  }
@@ -269,16 +272,21 @@ async function attemptReauth(user: User, options: AccountDeletionOptions, origin
269
272
  const auth = getFirebaseAuth();
270
273
  const currentUser = auth?.currentUser || user;
271
274
 
272
- // FIX: Final verification before deletion
273
- if (originalUserId && currentUser.uid !== originalUserId) {
274
- if (typeof __DEV__ !== "undefined" && __DEV__) {
275
- console.log("[attemptReauth] User changed after reauthentication");
275
+ // Final verification before deletion
276
+ if (originalUserId) {
277
+ const auth = getFirebaseAuth();
278
+ const validation = validateUserUnchanged(auth, originalUserId);
279
+ if (!('valid' in validation)) {
280
+ // User changed - validation is a FailureResult
281
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
282
+ console.log("[attemptReauth] User changed after reauthentication");
283
+ }
284
+ return {
285
+ success: false,
286
+ error: validation.error,
287
+ requiresReauth: false
288
+ };
276
289
  }
277
- return {
278
- success: false,
279
- error: { code: "auth/user-changed", message: "User changed during operation" },
280
- requiresReauth: false
281
- };
282
290
  }
283
291
 
284
292
  if (typeof __DEV__ !== "undefined" && __DEV__) {
@@ -295,7 +303,7 @@ async function attemptReauth(user: User, options: AccountDeletionOptions, origin
295
303
  if (typeof __DEV__ !== "undefined" && __DEV__) {
296
304
  console.error("[attemptReauth] deleteUser failed after reauth:", err);
297
305
  }
298
- const errorInfo = toAuthErrorInfo(err);
306
+ const errorInfo = toErrorInfo(err, 'auth/failed');
299
307
  return {
300
308
  success: false,
301
309
  error: { code: errorInfo.code, message: errorInfo.message },
@@ -330,7 +338,7 @@ export async function deleteUserAccount(user: User | null): Promise<AccountDelet
330
338
  await deleteUser(user);
331
339
  return successResult();
332
340
  } catch (error: unknown) {
333
- const errorInfo = toAuthErrorInfo(error);
341
+ const errorInfo = toErrorInfo(error, 'auth/failed');
334
342
  return {
335
343
  success: false,
336
344
  error: { code: errorInfo.code, message: errorInfo.message },
@@ -13,7 +13,7 @@ import {
13
13
  import * as AppleAuthentication from "expo-apple-authentication";
14
14
  import { Platform } from "react-native";
15
15
  import { generateNonce, hashNonce } from "../../../auth/infrastructure/services/crypto.util";
16
- import { executeOperation, failureResultFrom, toAuthErrorInfo, ERROR_MESSAGES } from "../../../../shared/domain/utils";
16
+ import { executeOperation, failureResultFrom, toErrorInfo, ERROR_MESSAGES } from "../../../../shared/domain/utils";
17
17
  import { isCancelledError } from "../../../../shared/domain/utils/error-handler.util";
18
18
  import type {
19
19
  ReauthenticationResult,
@@ -123,7 +123,7 @@ export async function getAppleReauthCredential(): Promise<ReauthCredentialResult
123
123
  credential
124
124
  };
125
125
  } catch (error: unknown) {
126
- const errorInfo = toAuthErrorInfo(error);
126
+ const errorInfo = toErrorInfo(error, 'auth/failed');
127
127
  const code = isCancelledError(errorInfo) ? "auth/cancelled" : errorInfo.code;
128
128
  return {
129
129
  success: false,
@@ -0,0 +1,72 @@
1
+ /**
2
+ * User Metadata Utilities
3
+ * Shared utilities for working with Firebase user metadata
4
+ * Eliminates duplicate isNewUser logic across auth services
5
+ */
6
+
7
+ import type { UserCredential, User, UserMetadata } from 'firebase/auth';
8
+
9
+ /**
10
+ * Check if user is new based on metadata timestamps
11
+ * Compares creation time and last sign-in time to determine if this is the user's first login
12
+ *
13
+ * A user is considered "new" if their account creation time matches their last sign-in time,
14
+ * indicating this is their first authentication.
15
+ *
16
+ * @param input - UserCredential, User, or UserMetadata to check
17
+ * @returns True if user is new (first sign-in), false otherwise
18
+ *
19
+ * @example
20
+ * ```typescript
21
+ * // From UserCredential
22
+ * const credential = await signInWithCredential(auth, oauthCredential);
23
+ * if (isNewUser(credential)) {
24
+ * // Show onboarding
25
+ * }
26
+ *
27
+ * // From User object
28
+ * const user = auth.currentUser;
29
+ * if (user && isNewUser(user)) {
30
+ * // First time user
31
+ * }
32
+ *
33
+ * // From UserMetadata directly
34
+ * if (isNewUser(user.metadata)) {
35
+ * // New user
36
+ * }
37
+ * ```
38
+ */
39
+ export function isNewUser(userCredential: UserCredential): boolean;
40
+ export function isNewUser(user: User): boolean;
41
+ export function isNewUser(metadata: UserMetadata): boolean;
42
+ export function isNewUser(input: UserCredential | User | UserMetadata): boolean {
43
+ let metadata: UserMetadata;
44
+
45
+ // Extract metadata from different input types
46
+ if ('user' in input) {
47
+ // UserCredential
48
+ metadata = input.user.metadata;
49
+ } else if ('metadata' in input) {
50
+ // User
51
+ metadata = input.metadata;
52
+ } else {
53
+ // UserMetadata
54
+ metadata = input;
55
+ }
56
+
57
+ const { creationTime, lastSignInTime } = metadata;
58
+
59
+ // Validate timestamps exist and are strings
60
+ if (
61
+ !creationTime ||
62
+ !lastSignInTime ||
63
+ typeof creationTime !== 'string' ||
64
+ typeof lastSignInTime !== 'string'
65
+ ) {
66
+ return false;
67
+ }
68
+
69
+ // Convert to timestamps for reliable comparison
70
+ // If creation time === last sign in time, this is the first sign-in
71
+ return new Date(creationTime).getTime() === new Date(lastSignInTime).getTime();
72
+ }
@@ -0,0 +1,77 @@
1
+ /**
2
+ * User Validation Utilities
3
+ * Shared utilities for validating user state during operations
4
+ * Eliminates duplicate user validation patterns in account deletion and reauthentication
5
+ */
6
+
7
+ import type { Auth } from 'firebase/auth';
8
+ import { failureResultFrom } from '../../../../shared/domain/utils/result/result-creators';
9
+ import { ERROR_MESSAGES } from '../../../../shared/domain/utils/error-handlers/error-messages';
10
+ import type { FailureResult } from '../../../../shared/domain/utils/result/result-types';
11
+
12
+ /**
13
+ * Validate that current user hasn't changed during operation
14
+ * Common pattern in account deletion and reauthentication flows
15
+ *
16
+ * This ensures operations complete on the same user that started them,
17
+ * preventing race conditions where user signs out/in during sensitive operations.
18
+ *
19
+ * @param auth - Firebase Auth instance (can be null)
20
+ * @param originalUserId - User ID captured at operation start
21
+ * @returns Success indicator or failure result if user changed
22
+ *
23
+ * @example
24
+ * ```typescript
25
+ * const originalUserId = auth.currentUser?.uid;
26
+ * if (!originalUserId) {
27
+ * return failureResultFrom('auth/no-user', 'No user signed in');
28
+ * }
29
+ *
30
+ * // ... perform operation ...
31
+ *
32
+ * const validation = validateUserUnchanged(auth, originalUserId);
33
+ * if (!validation.valid) {
34
+ * return validation; // Return the failure result
35
+ * }
36
+ * ```
37
+ */
38
+ export function validateUserUnchanged(
39
+ auth: Auth | null,
40
+ originalUserId: string
41
+ ): { valid: true } | FailureResult {
42
+ const currentUserId = auth?.currentUser?.uid;
43
+
44
+ if (currentUserId !== originalUserId) {
45
+ if (typeof __DEV__ !== 'undefined' && __DEV__) {
46
+ console.log(
47
+ `[validateUserUnchanged] User changed during operation. Original: ${originalUserId}, Current: ${currentUserId || 'none'}`
48
+ );
49
+ }
50
+ return failureResultFrom(
51
+ 'auth/user-changed',
52
+ ERROR_MESSAGES.AUTH.USER_CHANGED || 'User changed during operation'
53
+ );
54
+ }
55
+
56
+ return { valid: true };
57
+ }
58
+
59
+ /**
60
+ * Capture current user ID for later validation
61
+ * Returns null if no user is signed in
62
+ *
63
+ * @param auth - Firebase Auth instance
64
+ * @returns Current user ID or null
65
+ *
66
+ * @example
67
+ * ```typescript
68
+ * const auth = getFirebaseAuth();
69
+ * const originalUserId = captureUserId(auth);
70
+ * if (!originalUserId) {
71
+ * return failureResultFrom('auth/no-user', 'No user signed in');
72
+ * }
73
+ * ```
74
+ */
75
+ export function captureUserId(auth: Auth | null): string | null {
76
+ return auth?.currentUser?.uid ?? null;
77
+ }
@@ -15,7 +15,9 @@ import type { AppleAuthResult } from "./apple-auth.types";
15
15
  import {
16
16
  isCancellationError,
17
17
  } from "./base/base-auth.service";
18
- import { executeAuthOperation, isSuccess, type Result } from "../../../../shared/domain/utils";
18
+ import { executeAuthOperation, type Result } from "../../../../shared/domain/utils";
19
+ import { isNewUser as checkIsNewUser } from "../../domain/utils/user-metadata.util";
20
+ import { convertToOAuthResult } from "./utils/auth-result-converter.util";
19
21
 
20
22
  // Conditional import - expo-apple-authentication is optional
21
23
  let AppleAuthentication: any = null;
@@ -42,19 +44,7 @@ export class AppleAuthService {
42
44
  }
43
45
 
44
46
  private convertToAppleAuthResult(result: Result<{ userCredential: any; isNewUser: boolean }>): AppleAuthResult {
45
- // FIX: Use isSuccess() type guard instead of manual check
46
- if (isSuccess(result) && result.data) {
47
- return {
48
- success: true,
49
- userCredential: result.data.userCredential,
50
- isNewUser: result.data.isNewUser,
51
- };
52
- }
53
- return {
54
- success: false,
55
- error: result.error?.message ?? "Apple sign-in failed",
56
- code: result.error?.code,
57
- };
47
+ return convertToOAuthResult(result, "Apple sign-in failed");
58
48
  }
59
49
 
60
50
  async signIn(auth: Auth): Promise<AppleAuthResult> {
@@ -99,19 +89,8 @@ export class AppleAuthService {
99
89
 
100
90
  const userCredential = await signInWithCredential(auth, credential);
101
91
 
102
- // Check if user is new by comparing metadata timestamps
103
- // Convert to timestamps for reliable comparison (string comparison can be unreliable)
104
- const creationTime = userCredential.user.metadata.creationTime;
105
- const lastSignInTime = userCredential.user.metadata.lastSignInTime;
106
-
107
- // FIX: Add typeof validation for metadata timestamps
108
- const isNewUser =
109
- creationTime &&
110
- lastSignInTime &&
111
- typeof creationTime === 'string' &&
112
- typeof lastSignInTime === 'string'
113
- ? new Date(creationTime).getTime() === new Date(lastSignInTime).getTime()
114
- : false;
92
+ // Check if user is new using shared utility
93
+ const isNewUser = checkIsNewUser(userCredential);
115
94
 
116
95
  return {
117
96
  userCredential,
@@ -12,6 +12,7 @@ import {
12
12
  getCurrentUser,
13
13
  } from './auth-utils.service';
14
14
  import { ERROR_MESSAGES } from '../../../../shared/domain/utils/error-handlers/error-messages';
15
+ import { withAuth, withAuthSync } from '../utils/auth-guard.util';
15
16
 
16
17
  /**
17
18
  * Auth Guard Service
@@ -23,26 +24,33 @@ export class AuthGuardService {
23
24
  * @throws Error if user is not authenticated
24
25
  */
25
26
  async requireAuthenticatedUser(): Promise<string> {
26
- const auth = getFirebaseAuth();
27
- if (!auth) {
28
- throw new Error(ERROR_MESSAGES.AUTH.NOT_INITIALIZED);
29
- }
27
+ const result = await withAuth(async (auth) => {
28
+ const userId = getCurrentUserId(auth);
29
+ if (!userId) {
30
+ throw new Error(ERROR_MESSAGES.AUTH.NOT_AUTHENTICATED);
31
+ }
30
32
 
31
- const userId = getCurrentUserId(auth);
32
- if (!userId) {
33
- throw new Error(ERROR_MESSAGES.AUTH.NOT_AUTHENTICATED);
34
- }
33
+ const currentUser = getCurrentUser(auth);
34
+ if (!currentUser) {
35
+ throw new Error(ERROR_MESSAGES.AUTH.NOT_AUTHENTICATED);
36
+ }
37
+
38
+ if (currentUser.isAnonymous) {
39
+ throw new Error(ERROR_MESSAGES.AUTH.NON_ANONYMOUS_ONLY);
40
+ }
41
+
42
+ return userId;
43
+ });
35
44
 
36
- const currentUser = getCurrentUser(auth);
37
- if (!currentUser) {
38
- throw new Error(ERROR_MESSAGES.AUTH.NOT_AUTHENTICATED);
45
+ if (!result.success) {
46
+ throw new Error(result.error?.message || 'Authentication failed');
39
47
  }
40
48
 
41
- if (currentUser.isAnonymous) {
42
- throw new Error(ERROR_MESSAGES.AUTH.NON_ANONYMOUS_ONLY);
49
+ if (!result.data) {
50
+ throw new Error('No user ID returned');
43
51
  }
44
52
 
45
- return userId;
53
+ return result.data;
46
54
  }
47
55
 
48
56
  /**
@@ -61,35 +69,33 @@ export class AuthGuardService {
61
69
  * Check if current user is authenticated (not guest)
62
70
  */
63
71
  isAuthenticated(): boolean {
64
- const auth = getFirebaseAuth();
65
- if (!auth) {
66
- return false;
67
- }
72
+ const result = withAuthSync((auth) => {
73
+ const currentUser = getCurrentUser(auth);
74
+ if (!currentUser) {
75
+ return false;
76
+ }
68
77
 
69
- const currentUser = getCurrentUser(auth);
70
- if (!currentUser) {
71
- return false;
72
- }
78
+ // User must not be anonymous
79
+ return !currentUser.isAnonymous;
80
+ });
73
81
 
74
- // User must not be anonymous
75
- return !currentUser.isAnonymous;
82
+ return result.success && result.data ? result.data : false;
76
83
  }
77
84
 
78
85
  /**
79
86
  * Check if current user is guest (anonymous)
80
87
  */
81
88
  isGuest(): boolean {
82
- const auth = getFirebaseAuth();
83
- if (!auth) {
84
- return false;
85
- }
89
+ const result = withAuthSync((auth) => {
90
+ const currentUser = getCurrentUser(auth);
91
+ if (!currentUser) {
92
+ return false;
93
+ }
86
94
 
87
- const currentUser = getCurrentUser(auth);
88
- if (!currentUser) {
89
- return false;
90
- }
95
+ return currentUser.isAnonymous;
96
+ });
91
97
 
92
- return currentUser.isAnonymous;
98
+ return result.success && result.data ? result.data : false;
93
99
  }
94
100
  }
95
101
 
@@ -6,7 +6,7 @@
6
6
  */
7
7
 
8
8
  import type { UserCredential } from 'firebase/auth';
9
- import { authErrorConverter, type Result } from '../../../../../shared/domain/utils';
9
+ import { toErrorInfo, type Result } from '../../../../../shared/domain/utils';
10
10
 
11
11
  /**
12
12
  * Authentication result with user credential
@@ -45,7 +45,7 @@ export function isCancellationError(error: unknown): boolean {
45
45
  * Create failure result from error
46
46
  */
47
47
  export function createFailureResult(error: unknown): { success: false; error: { code: string; message: string } } {
48
- const errorInfo = authErrorConverter(error);
48
+ const errorInfo = toErrorInfo(error, 'auth/failed');
49
49
  return {
50
50
  success: false,
51
51
  error: errorInfo,
@@ -13,7 +13,8 @@ import {
13
13
  type User,
14
14
  } from "firebase/auth";
15
15
  import { getFirebaseAuth } from "../config/FirebaseAuthClient";
16
- import { executeOperation, failureResultFrom, successResult, toAuthErrorInfo, type Result, ERROR_MESSAGES } from "../../../../shared/domain/utils";
16
+ import { executeOperation, failureResultFrom, successResult, type Result, ERROR_MESSAGES } from "../../../../shared/domain/utils";
17
+ import { withAuth } from "../utils/auth-guard.util";
17
18
 
18
19
  export interface EmailCredentials {
19
20
  email: string;
@@ -30,22 +31,14 @@ export async function signInWithEmail(
30
31
  email: string,
31
32
  password: string
32
33
  ): Promise<EmailAuthResult> {
33
- const auth = getFirebaseAuth();
34
- if (!auth) {
35
- return failureResultFrom("auth/not-ready", ERROR_MESSAGES.AUTH.NOT_INITIALIZED);
36
- }
37
-
38
- try {
34
+ return withAuth(async (auth) => {
39
35
  const userCredential = await signInWithEmailAndPassword(
40
36
  auth,
41
37
  email.trim(),
42
38
  password
43
39
  );
44
- return { success: true, data: userCredential.user };
45
- } catch (error) {
46
- // FIX: Use toAuthErrorInfo() instead of unsafe cast
47
- return { success: false, error: toAuthErrorInfo(error) };
48
- }
40
+ return userCredential.user;
41
+ });
49
42
  }
50
43
 
51
44
  /**
@@ -55,12 +48,7 @@ export async function signInWithEmail(
55
48
  export async function signUpWithEmail(
56
49
  credentials: EmailCredentials
57
50
  ): Promise<EmailAuthResult> {
58
- const auth = getFirebaseAuth();
59
- if (!auth) {
60
- return failureResultFrom("auth/not-ready", ERROR_MESSAGES.AUTH.NOT_INITIALIZED);
61
- }
62
-
63
- try {
51
+ return withAuth(async (auth) => {
64
52
  const currentUser = auth.currentUser;
65
53
  const isAnonymous = currentUser?.isAnonymous ?? false;
66
54
  let userCredential;
@@ -84,7 +72,6 @@ export async function signUpWithEmail(
84
72
  // Update display name if provided (non-critical operation)
85
73
  if (credentials.displayName && userCredential.user) {
86
74
  const trimmedName = credentials.displayName.trim();
87
- // FIX: Only update if non-empty after trim
88
75
  if (trimmedName.length > 0) {
89
76
  try {
90
77
  await updateProfile(userCredential.user, {
@@ -98,11 +85,8 @@ export async function signUpWithEmail(
98
85
  }
99
86
  }
100
87
 
101
- return { success: true, data: userCredential.user };
102
- } catch (error) {
103
- // FIX: Use toAuthErrorInfo() instead of unsafe cast
104
- return { success: false, error: toAuthErrorInfo(error) };
105
- }
88
+ return userCredential.user;
89
+ });
106
90
  }
107
91
 
108
92
  /**
@@ -126,22 +110,18 @@ export async function linkAnonymousWithEmail(
126
110
  email: string,
127
111
  password: string
128
112
  ): Promise<EmailAuthResult> {
129
- const auth = getFirebaseAuth();
130
- if (!auth || !auth.currentUser) {
131
- return failureResultFrom("auth/not-ready", ERROR_MESSAGES.AUTH.NO_USER);
132
- }
113
+ return withAuth(async (auth) => {
114
+ const currentUser = auth.currentUser;
115
+ if (!currentUser) {
116
+ throw new Error(ERROR_MESSAGES.AUTH.NO_USER);
117
+ }
133
118
 
134
- const currentUser = auth.currentUser;
135
- if (!currentUser.isAnonymous) {
136
- return failureResultFrom("auth/invalid-action", ERROR_MESSAGES.AUTH.INVALID_USER);
137
- }
119
+ if (!currentUser.isAnonymous) {
120
+ throw new Error(ERROR_MESSAGES.AUTH.INVALID_USER);
121
+ }
138
122
 
139
- try {
140
123
  const credential = EmailAuthProvider.credential(email.trim(), password);
141
124
  const userCredential = await linkWithCredential(currentUser, credential);
142
- return { success: true, data: userCredential.user };
143
- } catch (error) {
144
- // FIX: Use toAuthErrorInfo() instead of unsafe cast
145
- return { success: false, error: toAuthErrorInfo(error) };
146
- }
125
+ return userCredential.user;
126
+ });
147
127
  }
@@ -9,8 +9,10 @@ import {
9
9
  type Auth,
10
10
  } from "firebase/auth";
11
11
  import type { GoogleAuthConfig, GoogleAuthResult } from "./google-auth.types";
12
- import { executeAuthOperation, isSuccess, type Result } from "../../../../shared/domain/utils";
12
+ import { executeAuthOperation, type Result } from "../../../../shared/domain/utils";
13
13
  import { ConfigurableService } from "../../../../shared/domain/utils/service-config.util";
14
+ import { isNewUser as checkIsNewUser } from "../../domain/utils/user-metadata.util";
15
+ import { convertToOAuthResult } from "./utils/auth-result-converter.util";
14
16
 
15
17
  /**
16
18
  * Google Auth Service
@@ -25,19 +27,7 @@ export class GoogleAuthService extends ConfigurableService<GoogleAuthConfig> {
25
27
  }
26
28
 
27
29
  private convertToGoogleAuthResult(result: Result<{ userCredential: any; isNewUser: boolean }>): GoogleAuthResult {
28
- // FIX: Use isSuccess() type guard instead of manual check
29
- if (isSuccess(result) && result.data) {
30
- return {
31
- success: true,
32
- userCredential: result.data.userCredential,
33
- isNewUser: result.data.isNewUser,
34
- };
35
- }
36
- return {
37
- success: false,
38
- error: result.error?.message ?? "Google sign-in failed",
39
- code: result.error?.code,
40
- };
30
+ return convertToOAuthResult(result, "Google sign-in failed");
41
31
  }
42
32
 
43
33
  async signInWithIdToken(
@@ -48,19 +38,8 @@ export class GoogleAuthService extends ConfigurableService<GoogleAuthConfig> {
48
38
  const credential = GoogleAuthProvider.credential(idToken);
49
39
  const userCredential = await signInWithCredential(auth, credential);
50
40
 
51
- // Check if user is new by comparing metadata timestamps
52
- // Convert to timestamps for reliable comparison (string comparison can be unreliable)
53
- const creationTime = userCredential.user.metadata.creationTime;
54
- const lastSignInTime = userCredential.user.metadata.lastSignInTime;
55
-
56
- // FIX: Add typeof validation for metadata timestamps
57
- const isNewUser =
58
- creationTime &&
59
- lastSignInTime &&
60
- typeof creationTime === 'string' &&
61
- typeof lastSignInTime === 'string'
62
- ? new Date(creationTime).getTime() === new Date(lastSignInTime).getTime()
63
- : false;
41
+ // Check if user is new using shared utility
42
+ const isNewUser = checkIsNewUser(userCredential);
64
43
 
65
44
  return {
66
45
  userCredential,