@umituz/react-native-auth 3.6.91 → 3.7.0

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": "@umituz/react-native-auth",
3
- "version": "3.6.91",
3
+ "version": "3.7.0",
4
4
  "description": "Authentication service for React Native apps - Secure, type-safe, and production-ready. Provider-agnostic design with dependency injection, configurable validation, and comprehensive error handling.",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -32,10 +32,10 @@
32
32
  "url": "git+https://github.com/umituz/react-native-auth.git"
33
33
  },
34
34
  "peerDependencies": {
35
- "expo": ">=54.0.0",
36
35
  "@react-navigation/native": ">=6.0.0",
37
36
  "@react-navigation/stack": ">=6.0.0",
38
37
  "@tanstack/react-query": ">=5.0.0",
38
+ "expo": ">=54.0.0",
39
39
  "expo-auth-session": ">=5.0.0",
40
40
  "expo-web-browser": ">=12.0.0",
41
41
  "firebase": ">=11.0.0",
@@ -49,6 +49,7 @@
49
49
  "devDependencies": {
50
50
  "@expo/vector-icons": "^15.0.3",
51
51
  "@react-native-async-storage/async-storage": "^2.2.0",
52
+ "@react-native-community/datetimepicker": "^8.2.0",
52
53
  "@react-native-community/slider": "^5.1.1",
53
54
  "@react-navigation/bottom-tabs": "^7.9.0",
54
55
  "@react-navigation/native": "^7.1.26",
@@ -61,7 +62,6 @@
61
62
  "@typescript-eslint/eslint-plugin": "^7.0.0",
62
63
  "@typescript-eslint/parser": "^7.0.0",
63
64
  "@umituz/react-native-design-system": "latest",
64
- "@umituz/react-native-firebase": "latest",
65
65
  "@umituz/react-native-localization": "latest",
66
66
  "eslint": "^8.57.0",
67
67
  "expo-apple-authentication": "^6.0.0",
@@ -92,10 +92,9 @@
92
92
  "react-native-gesture-handler": "^2.0.0",
93
93
  "react-native-safe-area-context": "^5.6.2",
94
94
  "react-native-svg": "^15.15.1",
95
+ "rn-emoji-keyboard": "^1.7.0",
95
96
  "typescript": "^5.3.0",
96
- "zustand": "^5.0.0",
97
- "@react-native-community/datetimepicker": "^8.2.0",
98
- "rn-emoji-keyboard": "^1.7.0"
97
+ "zustand": "^5.0.0"
99
98
  },
100
99
  "publishConfig": {
101
100
  "access": "public"
@@ -104,5 +103,8 @@
104
103
  "src",
105
104
  "README.md",
106
105
  "LICENSE"
107
- ]
106
+ ],
107
+ "dependencies": {
108
+ "@umituz/react-native-firebase": "^1.13.154"
109
+ }
108
110
  }
@@ -1,6 +1,6 @@
1
1
 
2
2
  import type { AuthUser } from "../../domain/entities/AuthUser";
3
- import type { AuthCredentials, SignUpCredentials } from "./IAuthProvider";
3
+ import type { AuthCredentials, SignUpCredentials } from "../../infrastructure/repositories/AuthRepository";
4
4
 
5
5
  export interface IAuthRepository {
6
6
  signUp(params: SignUpCredentials): Promise<AuthUser>;
package/src/index.ts CHANGED
@@ -19,13 +19,12 @@ export { DEFAULT_AUTH_CONFIG, DEFAULT_PASSWORD_CONFIG, DEFAULT_SOCIAL_CONFIG } f
19
19
  // APPLICATION LAYER
20
20
  // =============================================================================
21
21
 
22
- export type { IAuthProvider, AuthCredentials, SignUpCredentials, SocialSignInResult } from './application/ports/IAuthProvider';
22
+ export type { AuthCredentials, SignUpCredentials } from './infrastructure/repositories/AuthRepository';
23
23
 
24
24
  // =============================================================================
25
25
  // INFRASTRUCTURE LAYER
26
26
  // =============================================================================
27
27
 
28
- export { FirebaseAuthProvider } from './infrastructure/providers/FirebaseAuthProvider';
29
28
  export { AuthService, initializeAuthService, getAuthService, resetAuthService } from './infrastructure/services/AuthService';
30
29
  export type { IStorageProvider } from './infrastructure/types/Storage.types';
31
30
  export { createStorageProvider, StorageProviderAdapter } from './infrastructure/adapters/StorageProviderAdapter';
@@ -1,17 +1,24 @@
1
1
  /**
2
2
  * Auth Repository
3
- * Implementation of the Auth Repository Port
4
- * Handles data access for authentication
3
+ * Handles authentication with validation
5
4
  */
6
5
 
7
6
  import type { IAuthRepository } from "../../application/ports/IAuthRepository";
8
- import type { IAuthProvider } from "../../application/ports/IAuthProvider";
9
7
  import type { AuthUser } from "../../domain/entities/AuthUser";
10
- import type { AuthCredentials, SignUpCredentials } from "../../application/ports/IAuthProvider";
8
+ import type { User } from "firebase/auth";
9
+ import {
10
+ signInWithEmail,
11
+ signUpWithEmail,
12
+ signOut as firebaseSignOut,
13
+ getCurrentUserFromGlobal,
14
+ setupAuthListener,
15
+ type EmailCredentials,
16
+ } from "@umituz/react-native-firebase";
11
17
  import {
12
18
  AuthValidationError,
13
19
  AuthWeakPasswordError,
14
20
  AuthInvalidEmailError,
21
+ AuthError,
15
22
  } from "../../domain/errors/AuthError";
16
23
  import {
17
24
  validateEmail,
@@ -21,13 +28,23 @@ import {
21
28
  } from "../utils/AuthValidation";
22
29
  import type { AuthConfig } from "../../domain/value-objects/AuthConfig";
23
30
  import { sanitizeEmail, sanitizeName, sanitizePassword } from "../utils/validation/sanitization";
31
+ import { mapToAuthUser } from "../utils/UserMapper";
32
+
33
+ export interface SignUpCredentials {
34
+ email: string;
35
+ password: string;
36
+ displayName?: string;
37
+ }
38
+
39
+ export interface AuthCredentials {
40
+ email: string;
41
+ password: string;
42
+ }
24
43
 
25
44
  export class AuthRepository implements IAuthRepository {
26
- private provider: IAuthProvider;
27
45
  private config: AuthConfig;
28
46
 
29
- constructor(provider: IAuthProvider, config: AuthConfig) {
30
- this.provider = provider;
47
+ constructor(config: AuthConfig) {
31
48
  this.config = config;
32
49
  }
33
50
 
@@ -36,13 +53,11 @@ export class AuthRepository implements IAuthRepository {
36
53
  const password = sanitizePassword(params.password);
37
54
  const displayName = params.displayName ? sanitizeName(params.displayName) : undefined;
38
55
 
39
- // Validate email
40
56
  const emailResult = validateEmail(email);
41
57
  if (!emailResult.isValid) {
42
58
  throw new AuthInvalidEmailError(emailResult.error);
43
59
  }
44
60
 
45
- // Validate display name if provided
46
61
  if (displayName) {
47
62
  const nameResult = validateDisplayName(displayName);
48
63
  if (!nameResult.isValid) {
@@ -50,43 +65,68 @@ export class AuthRepository implements IAuthRepository {
50
65
  }
51
66
  }
52
67
 
53
- // Validate password strength for registration
54
68
  const passwordResult = validatePasswordForRegister(password, this.config.password);
55
69
  if (!passwordResult.isValid) {
56
70
  throw new AuthWeakPasswordError(passwordResult.error);
57
71
  }
58
72
 
59
- return this.provider.signUp({ email, password, displayName });
73
+ const result = await signUpWithEmail({ email, password, displayName });
74
+ if (!result.success || !result.data) {
75
+ throw new AuthError(result.error?.message || "Sign up failed");
76
+ }
77
+
78
+ const authUser = mapToAuthUser(result.data);
79
+ if (!authUser) {
80
+ throw new AuthError("Failed to map user");
81
+ }
82
+ return authUser;
60
83
  }
61
84
 
62
85
  async signIn(params: AuthCredentials): Promise<AuthUser> {
63
86
  const email = sanitizeEmail(params.email);
64
87
  const password = sanitizePassword(params.password);
65
88
 
66
- // Validate email format
67
89
  const emailResult = validateEmail(email);
68
90
  if (!emailResult.isValid) {
69
91
  throw new AuthInvalidEmailError(emailResult.error);
70
92
  }
71
93
 
72
- // For login, only check if password is provided (no strength requirements)
73
94
  const passwordResult = validatePasswordForLogin(password);
74
95
  if (!passwordResult.isValid) {
75
96
  throw new AuthValidationError(passwordResult.error || "Password is required", "password");
76
97
  }
77
98
 
78
- return this.provider.signIn({ email, password });
99
+ const result = await signInWithEmail(email, password);
100
+ if (!result.success || !result.data) {
101
+ throw new AuthError(result.error?.message || "Sign in failed");
102
+ }
103
+
104
+ const authUser = mapToAuthUser(result.data);
105
+ if (!authUser) {
106
+ throw new AuthError("Failed to map user");
107
+ }
108
+ return authUser;
79
109
  }
80
110
 
81
111
  async signOut(): Promise<void> {
82
- await this.provider.signOut();
112
+ const result = await firebaseSignOut();
113
+ if (!result.success) {
114
+ throw new AuthError(result.error?.message || "Sign out failed");
115
+ }
83
116
  }
84
117
 
85
118
  getCurrentUser(): AuthUser | null {
86
- return this.provider.getCurrentUser();
119
+ const user = getCurrentUserFromGlobal();
120
+ return user ? mapToAuthUser(user) : null;
87
121
  }
88
122
 
89
123
  onAuthStateChange(callback: (user: AuthUser | null) => void): () => void {
90
- return this.provider.onAuthStateChange(callback);
124
+ const result = setupAuthListener({
125
+ onAuthStateChange: (user) => {
126
+ const authUser = user ? mapToAuthUser(user) : null;
127
+ callback(authUser);
128
+ },
129
+ });
130
+ return result.success && result.unsubscribe ? result.unsubscribe : () => {};
91
131
  }
92
132
  }
@@ -3,7 +3,6 @@
3
3
  * Handles anonymous mode functionality
4
4
  */
5
5
 
6
- import type { IAuthProvider } from "../../application/ports/IAuthProvider";
7
6
  import type { AuthUser } from "../../domain/entities/AuthUser";
8
7
  import { emitAnonymousModeEnabled } from "./AuthEventService";
9
8
  import type { IStorageProvider } from "../types/Storage.types";
@@ -46,19 +45,7 @@ export class AnonymousModeService {
46
45
  }
47
46
  }
48
47
 
49
- async enable(storageProvider: IStorageProvider, provider?: IAuthProvider): Promise<void> {
50
- // Sign out from provider if logged in
51
- if (provider?.getCurrentUser()) {
52
- try {
53
- await provider.signOut();
54
- } catch (error) {
55
- // Log error but don't throw - allow anonymous mode to be enabled
56
- // even if sign-out fails (user might be in inconsistent state but
57
- // subsequent auth operations will resolve it)
58
- console.warn("[AnonymousModeService] Sign-out failed during anonymous mode enable:", error);
59
- }
60
- }
61
-
48
+ async enable(storageProvider: IStorageProvider): Promise<void> {
62
49
  this.isAnonymousMode = true;
63
50
  await this.save(storageProvider);
64
51
  emitAnonymousModeEnabled();
@@ -1,16 +1,12 @@
1
1
  /**
2
2
  * Auth Service
3
- * Orchestrates authentication operations using composition
3
+ * Orchestrates authentication operations
4
4
  */
5
5
 
6
- import type { Auth } from "firebase/auth";
7
- import type { AuthCredentials, SignUpCredentials } from "../../application/ports/IAuthProvider";
8
- import type { IAuthProvider } from "../../application/ports/IAuthProvider";
9
- import { FirebaseAuthProvider } from "../providers/FirebaseAuthProvider";
10
6
  import type { AuthUser } from "../../domain/entities/AuthUser";
11
7
  import type { AuthConfig } from "../../domain/value-objects/AuthConfig";
12
8
  import { sanitizeAuthConfig } from "../../domain/value-objects/AuthConfig";
13
- import { AuthRepository } from "../repositories/AuthRepository";
9
+ import { AuthRepository, type SignUpCredentials, type AuthCredentials } from "../repositories/AuthRepository";
14
10
  import { AnonymousModeService } from "./AnonymousModeService";
15
11
  import { authEventService } from "./AuthEventService";
16
12
  import type { IStorageProvider } from "../types/Storage.types";
@@ -33,33 +29,10 @@ export class AuthService {
33
29
  return this.repository;
34
30
  }
35
31
 
36
- private isFirebaseAuth(obj: IAuthProvider | Auth): obj is Auth {
37
- return (
38
- typeof obj === "object" &&
39
- obj !== null &&
40
- "currentUser" in obj &&
41
- "onAuthStateChanged" in obj &&
42
- "signInWithEmailAndPassword" in obj &&
43
- "createUserWithEmailAndPassword" in obj &&
44
- typeof obj.onAuthStateChanged === "function"
45
- );
46
- }
47
-
48
- async initialize(providerOrAuth: IAuthProvider | Auth): Promise<void> {
32
+ async initialize(): Promise<void> {
49
33
  if (this.initialized) return;
50
34
 
51
- let provider: IAuthProvider;
52
-
53
- if (this.isFirebaseAuth(providerOrAuth)) {
54
- const firebaseProvider = new FirebaseAuthProvider(providerOrAuth);
55
- await firebaseProvider.initialize();
56
- provider = firebaseProvider;
57
- } else {
58
- provider = providerOrAuth;
59
- await provider.initialize();
60
- }
61
-
62
- this.repository = new AuthRepository(provider, this.config);
35
+ this.repository = new AuthRepository(this.config);
63
36
 
64
37
  if (this.storageProvider) {
65
38
  await this.anonymousModeService.load(this.storageProvider);
@@ -122,14 +95,13 @@ export class AuthService {
122
95
  let authServiceInstance: AuthService | null = null;
123
96
 
124
97
  export async function initializeAuthService(
125
- providerOrAuth: IAuthProvider | Auth,
126
98
  config?: Partial<AuthConfig>,
127
99
  storageProvider?: IStorageProvider
128
100
  ): Promise<AuthService> {
129
101
  if (!authServiceInstance) {
130
102
  authServiceInstance = new AuthService(config, storageProvider);
131
103
  }
132
- await authServiceInstance.initialize(providerOrAuth);
104
+ await authServiceInstance.initialize();
133
105
  return authServiceInstance;
134
106
  }
135
107
 
@@ -1,13 +1,12 @@
1
1
  /**
2
2
  * Unified Auth Initialization
3
- * Initializes Firebase auth with user document sync and conversion tracking
3
+ * Initializes auth with user document sync and conversion tracking
4
4
  */
5
5
 
6
- import type { Auth, User } from "firebase/auth";
7
- import { getFirebaseAuth } from "@umituz/react-native-firebase";
6
+ import type { User } from "firebase/auth";
7
+ import { getFirebaseAuth, configureUserDocumentService } from "@umituz/react-native-firebase";
8
+ import type { UserDocumentExtras } from "@umituz/react-native-firebase";
8
9
  import { initializeAuthService } from "./AuthService";
9
- import { configureUserDocumentService } from "./UserDocumentService";
10
- import type { UserDocumentExtras } from "./UserDocumentService";
11
10
  import { collectDeviceExtras } from "@umituz/react-native-design-system";
12
11
  import { initializeAuthListener } from "../../presentation/stores/initializeAuthListener";
13
12
  import { createAuthStateHandler } from "../utils/authStateHandler";
@@ -27,7 +26,7 @@ export interface InitializeAuthOptions {
27
26
  }
28
27
 
29
28
  let isInitialized = false;
30
- let initializationPromise: Promise<{ success: boolean; auth: Auth | null }> | null = null;
29
+ let initializationPromise: Promise<{ success: boolean }> | null = null;
31
30
  const conversionState: { current: ConversionState } = {
32
31
  current: { previousUserId: null, wasAnonymous: false },
33
32
  };
@@ -37,9 +36,9 @@ const conversionState: { current: ConversionState } = {
37
36
  */
38
37
  export async function initializeAuth(
39
38
  options: InitializeAuthOptions = {}
40
- ): Promise<{ success: boolean; auth: Auth | null }> {
39
+ ): Promise<{ success: boolean }> {
41
40
  if (isInitialized) {
42
- return { success: true, auth: getFirebaseAuth() };
41
+ return { success: true };
43
42
  }
44
43
  // Prevent race condition: return existing promise if initialization is in progress
45
44
  if (initializationPromise) {
@@ -55,7 +54,7 @@ export async function initializeAuth(
55
54
 
56
55
  async function doInitializeAuth(
57
56
  options: InitializeAuthOptions
58
- ): Promise<{ success: boolean; auth: Auth | null }> {
57
+ ): Promise<{ success: boolean }> {
59
58
 
60
59
  const {
61
60
  userCollection = "users",
@@ -69,7 +68,7 @@ async function doInitializeAuth(
69
68
  } = options;
70
69
 
71
70
  const auth = getFirebaseAuth();
72
- if (!auth) return { success: false, auth: null };
71
+ if (!auth) return { success: false };
73
72
 
74
73
  configureUserDocumentService({
75
74
  collectionName: userCollection,
@@ -79,7 +78,7 @@ async function doInitializeAuth(
79
78
 
80
79
  let authServiceInitFailed = false;
81
80
  try {
82
- await initializeAuthService(auth, authConfig, storageProvider);
81
+ await initializeAuthService(authConfig, storageProvider);
83
82
  } catch {
84
83
  authServiceInitFailed = true;
85
84
  }
@@ -97,7 +96,7 @@ async function doInitializeAuth(
97
96
  });
98
97
 
99
98
  isInitialized = true;
100
- return { success: !authServiceInitFailed, auth };
99
+ return { success: !authServiceInitFailed };
101
100
  }
102
101
 
103
102
  export function isAuthInitialized(): boolean {
@@ -4,7 +4,7 @@
4
4
  */
5
5
 
6
6
  import type { User } from "firebase/auth";
7
- import { ensureUserDocument } from "../services/UserDocumentService";
7
+ import { ensureUserDocument } from "@umituz/react-native-firebase";
8
8
  import { detectConversion, type ConversionState } from "./authConversionDetector";
9
9
 
10
10
  export interface AuthStateHandlerOptions {
@@ -5,7 +5,7 @@
5
5
 
6
6
  import { useMutation } from "@umituz/react-native-design-system";
7
7
  import { getAuthService } from "../../../infrastructure/services/AuthService";
8
- import type { AuthCredentials, SignUpCredentials } from "../../../application/ports/IAuthProvider";
8
+ import type { AuthCredentials, SignUpCredentials } from "../../../infrastructure/repositories/AuthRepository";
9
9
  import type { AuthUser } from "../../../domain/entities/AuthUser";
10
10
 
11
11
  export const useSignUpMutation = () => {
@@ -6,7 +6,7 @@
6
6
  import { useCallback, useState } from "react";
7
7
  import { Alert } from "react-native";
8
8
  import { useAuth } from "./useAuth";
9
- import { handleAccountDeletion } from "../utils/accountDeleteHandler.util";
9
+ import { deleteCurrentUser } from "@umituz/react-native-firebase";
10
10
 
11
11
  export interface UseAccountManagementOptions {
12
12
  /**
@@ -111,14 +111,56 @@ export const useAccountManagement = (
111
111
  setIsDeletingAccount(true);
112
112
 
113
113
  try {
114
- await handleAccountDeletion({
115
- onReauthRequired,
116
- onPasswordRequired: passwordHandler,
117
- });
114
+ // First attempt - Firebase will check if reauthentication is needed
115
+ const result = await deleteCurrentUser({ autoReauthenticate: true });
116
+
117
+ if (result.success) {
118
+ return;
119
+ }
120
+
121
+ // Handle password reauthentication
122
+ if (result.error?.code === "auth/password-reauth") {
123
+ const password = await passwordHandler();
124
+ if (!password) {
125
+ throw new Error("Password required to delete account");
126
+ }
127
+
128
+ // Retry with password
129
+ const retryResult = await deleteCurrentUser({
130
+ autoReauthenticate: true,
131
+ password,
132
+ });
133
+
134
+ if (!retryResult.success) {
135
+ throw new Error(retryResult.error?.message || "Failed to delete account");
136
+ }
137
+
138
+ return;
139
+ }
140
+
141
+ // Handle social auth reauthentication
142
+ if (result.requiresReauth && onReauthRequired) {
143
+ const reauthSuccess = await onReauthRequired();
144
+ if (!reauthSuccess) {
145
+ throw new Error("Reauthentication required to delete account");
146
+ }
147
+
148
+ // Retry after social reauth
149
+ const retryResult = await deleteCurrentUser({ autoReauthenticate: true });
150
+
151
+ if (!retryResult.success) {
152
+ throw new Error(retryResult.error?.message || "Failed to delete account");
153
+ }
154
+
155
+ return;
156
+ }
157
+
158
+ // Other errors
159
+ throw new Error(result.error?.message || "Failed to delete account");
118
160
  } finally {
119
161
  setIsDeletingAccount(false);
120
162
  }
121
- }, [user, onReauthRequired, passwordHandler]);
163
+ }, [user, passwordHandler, onReauthRequired]);
122
164
 
123
165
  return {
124
166
  logout,
@@ -1,78 +0,0 @@
1
- /**
2
- * Auth Provider Interface
3
- * Port for provider-agnostic authentication (Firebase, Supabase, etc.)
4
- */
5
-
6
- import type { AuthUser } from "../../domain/entities/AuthUser";
7
- import type { SocialAuthProvider } from "../../domain/value-objects/AuthConfig";
8
-
9
- export interface AuthCredentials {
10
- email: string;
11
- password: string;
12
- }
13
-
14
- export interface SignUpCredentials extends AuthCredentials {
15
- displayName?: string;
16
- }
17
-
18
- /**
19
- * Result from social sign-in operations
20
- */
21
- export interface SocialSignInResult {
22
- user: AuthUser;
23
- isNewUser: boolean;
24
- }
25
-
26
- export interface IAuthProvider {
27
- /**
28
- * Initialize the auth provider
29
- */
30
- initialize(): Promise<void>;
31
-
32
- /**
33
- * Check if provider is initialized
34
- */
35
- isInitialized(): boolean;
36
-
37
- /**
38
- * Sign in with email and password
39
- */
40
- signIn(credentials: AuthCredentials): Promise<AuthUser>;
41
-
42
- /**
43
- * Sign up with email, password, and optional display name
44
- */
45
- signUp(credentials: SignUpCredentials): Promise<AuthUser>;
46
-
47
- /**
48
- * Sign in with Google
49
- * @returns User and whether this is a new user
50
- */
51
- signInWithGoogle?(): Promise<SocialSignInResult>;
52
-
53
- /**
54
- * Sign in with Apple
55
- * @returns User and whether this is a new user
56
- */
57
- signInWithApple?(): Promise<SocialSignInResult>;
58
-
59
- /**
60
- * Check if a social provider is supported
61
- */
62
- isSocialProviderSupported?(provider: SocialAuthProvider): boolean;
63
-
64
- /**
65
- * Sign out current user
66
- */
67
- signOut(): Promise<void>;
68
-
69
- /**
70
- * Get current authenticated user
71
- */
72
- getCurrentUser(): AuthUser | null;
73
-
74
- /**
75
- * Subscribe to auth state changes
76
- */
77
- onAuthStateChange(callback: (user: AuthUser | null) => void): () => void;
78
- }
@@ -1,117 +0,0 @@
1
- /**
2
- * Firebase Auth Provider
3
- * IAuthProvider implementation for Firebase Authentication
4
- */
5
-
6
- import {
7
- createUserWithEmailAndPassword,
8
- signInWithEmailAndPassword,
9
- signOut as firebaseSignOut,
10
- onAuthStateChanged,
11
- updateProfile,
12
- EmailAuthProvider,
13
- linkWithCredential,
14
- type Auth,
15
- } from "firebase/auth";
16
- import type { IAuthProvider, AuthCredentials, SignUpCredentials } from "../../application/ports/IAuthProvider";
17
- import type { AuthUser } from "../../domain/entities/AuthUser";
18
- import { mapFirebaseAuthError } from "../utils/AuthErrorMapper";
19
- import { mapToAuthUser } from "../utils/UserMapper";
20
-
21
- export class FirebaseAuthProvider implements IAuthProvider {
22
- private auth: Auth | null = null;
23
-
24
- constructor(auth?: Auth) {
25
- if (auth) this.auth = auth;
26
- }
27
-
28
- initialize(): Promise<void> {
29
- if (!this.auth) throw new Error("Firebase Auth instance must be provided");
30
- return Promise.resolve();
31
- }
32
-
33
- setAuth(auth: Auth): void {
34
- this.auth = auth;
35
- }
36
-
37
- isInitialized(): boolean {
38
- return this.auth !== null;
39
- }
40
-
41
- async signIn(credentials: AuthCredentials): Promise<AuthUser> {
42
- if (!this.auth) throw new Error("Firebase Auth is not initialized");
43
-
44
- try {
45
- const userCredential = await signInWithEmailAndPassword(this.auth, credentials.email.trim(), credentials.password);
46
- const user = mapToAuthUser(userCredential.user);
47
- if (!user) throw new Error("Failed to sign in");
48
- return user;
49
- } catch (error: unknown) {
50
- throw mapFirebaseAuthError(error);
51
- }
52
- }
53
-
54
- async signUp(credentials: SignUpCredentials): Promise<AuthUser> {
55
- if (!this.auth) throw new Error("Firebase Auth is not initialized");
56
-
57
- try {
58
- const currentUser = this.auth.currentUser;
59
- const isAnonymous = currentUser?.isAnonymous ?? false;
60
- let userCredential;
61
-
62
- if (currentUser && isAnonymous) {
63
- // Try to reload to get fresh user data, but don't fail if it doesn't work
64
- try {
65
- await currentUser.reload();
66
- } catch (reloadError) {
67
- // Log the reload error but proceed - the user might still be valid
68
- console.warn("[FirebaseAuthProvider] User reload failed during anonymous upgrade:", reloadError);
69
- }
70
- const credential = EmailAuthProvider.credential(credentials.email.trim(), credentials.password);
71
- userCredential = await linkWithCredential(currentUser, credential);
72
- } else {
73
- userCredential = await createUserWithEmailAndPassword(this.auth, credentials.email.trim(), credentials.password);
74
- }
75
-
76
- if (credentials.displayName && userCredential.user) {
77
- try {
78
- await updateProfile(userCredential.user, { displayName: credentials.displayName.trim() });
79
- } catch (profileError) {
80
- // Display name update failed, but account was created successfully
81
- // Log this for debugging but don't fail the entire operation
82
- console.warn("[FirebaseAuthProvider] Display name update failed:", profileError);
83
- }
84
- }
85
-
86
- const user = mapToAuthUser(userCredential.user);
87
- if (!user) throw new Error("Failed to create user account");
88
- return user;
89
- } catch (error: unknown) {
90
- throw mapFirebaseAuthError(error);
91
- }
92
- }
93
-
94
- async signOut(): Promise<void> {
95
- if (!this.auth) return;
96
- try {
97
- await firebaseSignOut(this.auth);
98
- } catch (error: unknown) {
99
- throw mapFirebaseAuthError(error);
100
- }
101
- }
102
-
103
- getCurrentUser(): AuthUser | null {
104
- if (!this.auth) return null;
105
- const currentUser = this.auth.currentUser;
106
- if (!currentUser) return null;
107
- return mapToAuthUser(currentUser);
108
- }
109
-
110
- onAuthStateChange(callback: (user: AuthUser | null) => void): () => void {
111
- if (!this.auth) {
112
- callback(null);
113
- return () => {};
114
- }
115
- return onAuthStateChanged(this.auth, (user) => callback(mapToAuthUser(user)));
116
- }
117
- }
@@ -1,60 +0,0 @@
1
- /**
2
- * User Document Types and Configuration
3
- */
4
-
5
- /**
6
- * Minimal user interface for document creation
7
- * Compatible with both Firebase User and AuthUser
8
- */
9
- export interface UserDocumentUser {
10
- uid: string;
11
- displayName?: string | null;
12
- email?: string | null;
13
- photoURL?: string | null;
14
- isAnonymous?: boolean;
15
- }
16
-
17
- /**
18
- * Configuration for user document service
19
- */
20
- export interface UserDocumentConfig {
21
- /** Firestore collection name (default: "users") */
22
- collectionName?: string;
23
- /** Additional fields to store with user document */
24
- extraFields?: Record<string, unknown>;
25
- /** Callback to collect device/app info */
26
- collectExtras?: () => Promise<UserDocumentExtras>;
27
- }
28
-
29
- /**
30
- * User document extras from device/app
31
- */
32
- export interface UserDocumentExtras {
33
- [key: string]: string | number | boolean | null | undefined;
34
- deviceId?: string;
35
- persistentDeviceId?: string;
36
- nativeDeviceId?: string;
37
- platform?: string;
38
- deviceModel?: string;
39
- deviceBrand?: string;
40
- deviceName?: string;
41
- deviceType?: number | string;
42
- deviceYearClass?: number | string;
43
- isDevice?: boolean;
44
- osName?: string;
45
- osVersion?: string;
46
- osBuildId?: string;
47
- totalMemory?: number | string;
48
- appVersion?: string;
49
- buildNumber?: string;
50
- locale?: string;
51
- region?: string;
52
- timezone?: string;
53
- screenWidth?: number;
54
- screenHeight?: number;
55
- screenScale?: number;
56
- fontScale?: number;
57
- isLandscape?: boolean;
58
- previousAnonymousUserId?: string;
59
- signUpMethod?: string;
60
- }
@@ -1,85 +0,0 @@
1
- /**
2
- * User Document Service
3
- * Generic service for creating/updating user documents in Firestore
4
- */
5
-
6
- import { doc, getDoc, setDoc, serverTimestamp } from "firebase/firestore";
7
- import type { User } from "firebase/auth";
8
- import { getFirestore } from "@umituz/react-native-firebase";
9
- import type {
10
- UserDocumentUser,
11
- UserDocumentConfig,
12
- UserDocumentExtras,
13
- } from "./UserDocument.types";
14
- import {
15
- getSignUpMethod,
16
- buildBaseData,
17
- buildCreateData,
18
- buildUpdateData,
19
- } from "../utils/userDocumentBuilder.util";
20
-
21
- export type {
22
- UserDocumentUser,
23
- UserDocumentConfig,
24
- UserDocumentExtras,
25
- } from "./UserDocument.types";
26
-
27
- declare const __DEV__: boolean;
28
-
29
- let userDocumentConfig: UserDocumentConfig = {};
30
-
31
- export function configureUserDocumentService(config: UserDocumentConfig): void {
32
- userDocumentConfig = { ...config };
33
- }
34
-
35
- export async function ensureUserDocument(
36
- user: UserDocumentUser | User,
37
- extras?: UserDocumentExtras,
38
- ): Promise<boolean> {
39
- const db = getFirestore();
40
- if (!db || !user.uid) return false;
41
-
42
- try {
43
- let allExtras = extras || {};
44
- if (userDocumentConfig.collectExtras) {
45
- const collectedExtras = await userDocumentConfig.collectExtras();
46
- allExtras = { ...collectedExtras, ...allExtras };
47
- }
48
-
49
- if (!allExtras.signUpMethod) allExtras.signUpMethod = getSignUpMethod(user);
50
-
51
- const collectionName = userDocumentConfig.collectionName || "users";
52
- const userRef = doc(db, collectionName, user.uid);
53
- const userDoc = await getDoc(userRef);
54
- const baseData = buildBaseData(user, allExtras);
55
-
56
- const docData = !userDoc.exists()
57
- ? buildCreateData(baseData, userDocumentConfig.extraFields, allExtras)
58
- : buildUpdateData(baseData, allExtras);
59
-
60
- await setDoc(userRef, docData, { merge: true });
61
- return true;
62
- } catch (error) {
63
- if (typeof __DEV__ !== "undefined" && __DEV__) {
64
- console.error("[UserDocumentService] Failed:", error);
65
- }
66
- return false;
67
- }
68
- }
69
-
70
- export async function markUserDeleted(userId: string): Promise<boolean> {
71
- const db = getFirestore();
72
- if (!db || !userId) return false;
73
-
74
- try {
75
- const userRef = doc(db, userDocumentConfig.collectionName || "users", userId);
76
- await setDoc(userRef, {
77
- isDeleted: true,
78
- deletedAt: serverTimestamp(),
79
- updatedAt: serverTimestamp(),
80
- }, { merge: true });
81
- return true;
82
- } catch {
83
- return false;
84
- }
85
- }
@@ -1,110 +0,0 @@
1
- /**
2
- * User Document Builder Utility
3
- * Builds user document data for Firestore (max 100 lines)
4
- */
5
-
6
- import { serverTimestamp } from "firebase/firestore";
7
- import type {
8
- UserDocumentUser,
9
- UserDocumentExtras,
10
- } from "../services/UserDocument.types";
11
-
12
- /**
13
- * Gets the sign-up method from user provider data
14
- */
15
- export function getSignUpMethod(user: UserDocumentUser): string | undefined {
16
- if (user.isAnonymous) return "anonymous";
17
- if (user.email) {
18
- const providerData = (
19
- user as unknown as { providerData?: { providerId: string }[] }
20
- ).providerData;
21
- if (providerData && providerData.length > 0) {
22
- const providerId = providerData[0]?.providerId;
23
- if (providerId === "google.com") return "google";
24
- if (providerId === "apple.com") return "apple";
25
- if (providerId === "password") return "email";
26
- }
27
- return "email";
28
- }
29
- return undefined;
30
- }
31
-
32
- /**
33
- * Builds base user data from user object and extras
34
- */
35
- export function buildBaseData(
36
- user: UserDocumentUser,
37
- extras?: UserDocumentExtras,
38
- ): Record<string, unknown> {
39
- const data: Record<string, unknown> = {
40
- uid: user.uid,
41
- displayName: user.displayName,
42
- email: user.email,
43
- photoURL: user.photoURL,
44
- isAnonymous: user.isAnonymous,
45
- };
46
-
47
- if (extras) {
48
- const internalFields = ["signUpMethod", "previousAnonymousUserId"];
49
- Object.keys(extras).forEach((key) => {
50
- if (!internalFields.includes(key)) {
51
- const val = extras[key];
52
- if (val !== undefined) {
53
- data[key] = val;
54
- }
55
- }
56
- });
57
- }
58
-
59
- return data;
60
- }
61
-
62
- /**
63
- * Builds user document data for creation
64
- */
65
- export function buildCreateData(
66
- baseData: Record<string, unknown>,
67
- extraFields: Record<string, unknown> | undefined,
68
- extras?: UserDocumentExtras,
69
- ): Record<string, unknown> {
70
- const createData: Record<string, unknown> = {
71
- ...baseData,
72
- ...extraFields,
73
- createdAt: serverTimestamp(),
74
- updatedAt: serverTimestamp(),
75
- lastLoginAt: serverTimestamp(),
76
- };
77
-
78
- if (extras?.previousAnonymousUserId) {
79
- createData.previousAnonymousUserId = extras.previousAnonymousUserId;
80
- createData.convertedFromAnonymous = true;
81
- createData.convertedAt = serverTimestamp();
82
- }
83
-
84
- if (extras?.signUpMethod) createData.signUpMethod = extras.signUpMethod;
85
-
86
- return createData;
87
- }
88
-
89
- /**
90
- * Builds user document data for update
91
- */
92
- export function buildUpdateData(
93
- baseData: Record<string, unknown>,
94
- extras?: UserDocumentExtras,
95
- ): Record<string, unknown> {
96
- const updateData: Record<string, unknown> = {
97
- ...baseData,
98
- lastLoginAt: serverTimestamp(),
99
- updatedAt: serverTimestamp(),
100
- };
101
-
102
- if (extras?.previousAnonymousUserId) {
103
- updateData.previousAnonymousUserId = extras.previousAnonymousUserId;
104
- updateData.convertedFromAnonymous = true;
105
- updateData.convertedAt = serverTimestamp();
106
- if (extras?.signUpMethod) updateData.signUpMethod = extras.signUpMethod;
107
- }
108
-
109
- return updateData;
110
- }
@@ -1,96 +0,0 @@
1
- /**
2
- * Account Delete Handler Utility
3
- * Handles reauthentication flow for account deletion
4
- */
5
-
6
- import { deleteCurrentUser } from "@umituz/react-native-firebase";
7
-
8
- export interface DeleteAccountCallbacks {
9
- onReauthRequired?: () => Promise<boolean>;
10
- onPasswordRequired?: () => Promise<string | null>;
11
- }
12
-
13
- export interface DeleteAccountOptions {
14
- autoReauthenticate: boolean;
15
- password?: string;
16
- }
17
-
18
- /**
19
- * Handles account deletion with reauthentication retry logic
20
- */
21
- export async function handleAccountDeletion(
22
- callbacks: DeleteAccountCallbacks
23
- ): Promise<void> {
24
- const result = await deleteCurrentUser({ autoReauthenticate: true });
25
-
26
- if (result.success) {
27
- return;
28
- }
29
-
30
- if (result.error?.requiresReauth) {
31
- await handleReauthentication(result, callbacks);
32
- return;
33
- }
34
-
35
- if (result.error) {
36
- throw new Error(result.error.message);
37
- }
38
- }
39
-
40
- async function handleReauthentication(
41
- initialResult: Awaited<ReturnType<typeof deleteCurrentUser>>,
42
- callbacks: DeleteAccountCallbacks
43
- ): Promise<void> {
44
- const { onReauthRequired, onPasswordRequired } = callbacks;
45
-
46
- // Handle password reauth
47
- if (initialResult.error?.code === "auth/password-reauth" && onPasswordRequired) {
48
- await retryWithPassword(onPasswordRequired);
49
- return;
50
- }
51
-
52
- // Handle social auth reauth
53
- if (onReauthRequired) {
54
- await retryWithSocialAuth(onReauthRequired);
55
- return;
56
- }
57
- }
58
-
59
- async function retryWithPassword(onPasswordRequired: () => Promise<string | null>): Promise<void> {
60
- const password = await onPasswordRequired();
61
-
62
- if (!password) {
63
- throw new Error("Password required to delete account");
64
- }
65
-
66
- const result = await deleteCurrentUser({
67
- autoReauthenticate: false,
68
- password,
69
- });
70
-
71
- if (result.success) {
72
- return;
73
- }
74
-
75
- if (result.error) {
76
- throw new Error(result.error.message);
77
- }
78
- }
79
-
80
- async function retryWithSocialAuth(onReauthRequired: () => Promise<boolean>): Promise<void> {
81
- const reauthSuccess = await onReauthRequired();
82
-
83
- if (!reauthSuccess) {
84
- throw new Error("Reauthentication required to delete account");
85
- }
86
-
87
- const result = await deleteCurrentUser({ autoReauthenticate: false });
88
-
89
- if (result.success) {
90
- return;
91
- }
92
-
93
- if (result.error) {
94
- throw new Error(result.error.message);
95
- }
96
- }