@umituz/react-native-firebase 2.6.1 → 2.6.3

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 (94) hide show
  1. package/package.json +1 -1
  2. package/src/application/auth/ports/AuthPort_part_aa +150 -0
  3. package/src/application/auth/ports/AuthPort_part_ab +14 -0
  4. package/src/application/auth/use-cases/SignInUseCaseHelpers.ts +0 -0
  5. package/src/application/auth/use-cases/SignInUseCaseMain.ts +0 -0
  6. package/src/application/auth/use-cases/SignInUseCase_part_aa +150 -0
  7. package/src/application/auth/use-cases/SignInUseCase_part_ab +103 -0
  8. package/src/application/auth/use-cases/SignOutUseCaseCleanup.ts +0 -0
  9. package/src/application/auth/use-cases/SignOutUseCaseMain.ts +0 -0
  10. package/src/application/auth/use-cases/SignOutUseCase_part_aa +150 -0
  11. package/src/application/auth/use-cases/SignOutUseCase_part_ab +138 -0
  12. package/src/domains/account-deletion/domain/services/UserValidationHelpers.ts.bak +181 -0
  13. package/src/domains/account-deletion/domain/services/UserValidationHelpers_part_aa +150 -0
  14. package/src/domains/account-deletion/domain/services/UserValidationHelpers_part_ab +31 -0
  15. package/src/domains/account-deletion/domain/services/{UserValidationService.ts → UserValidationService.ts.bak} +1 -10
  16. package/src/domains/account-deletion/domain/services/UserValidationService_part_aa +150 -0
  17. package/src/domains/account-deletion/domain/services/UserValidationService_part_ab +136 -0
  18. package/src/domains/account-deletion/infrastructure/services/AccountDeletionExecutor_part_aa +150 -0
  19. package/src/domains/account-deletion/infrastructure/services/AccountDeletionExecutor_part_ab +80 -0
  20. package/src/domains/account-deletion/infrastructure/services/AccountDeletionReauthHandler_part_aa +150 -0
  21. package/src/domains/account-deletion/infrastructure/services/AccountDeletionReauthHandler_part_ab +24 -0
  22. package/src/domains/account-deletion/infrastructure/services/AccountDeletionRepository_part_aa +150 -0
  23. package/src/domains/account-deletion/infrastructure/services/AccountDeletionRepository_part_ab +116 -0
  24. package/src/domains/account-deletion/infrastructure/services/reauthentication.service_part_aa +150 -0
  25. package/src/domains/account-deletion/infrastructure/services/reauthentication.service_part_ab +10 -0
  26. package/src/domains/auth/infrastructure_part_aa +150 -0
  27. package/src/domains/auth/infrastructure_part_ab +6 -0
  28. package/src/domains/auth/presentation/hooks/GoogleOAuthHelpers.ts +0 -0
  29. package/src/domains/auth/presentation/hooks/GoogleOAuthHookService_part_aa +150 -0
  30. package/src/domains/auth/presentation/hooks/GoogleOAuthHookService_part_ab +97 -0
  31. package/src/domains/auth/presentation/hooks/GoogleOAuthService.ts +0 -0
  32. package/src/domains/firestore/domain/entities/Collection.ts +31 -191
  33. package/src/domains/firestore/domain/entities/Collection.ts.bak +288 -0
  34. package/src/domains/firestore/domain/entities/CollectionFactory.ts +55 -0
  35. package/src/domains/firestore/domain/entities/CollectionHelpers.ts +143 -0
  36. package/src/domains/firestore/domain/entities/CollectionUtils.ts +72 -0
  37. package/src/domains/firestore/domain/entities/CollectionValidation.ts +138 -0
  38. package/src/domains/firestore/domain/entities/Collection_part_aa +150 -0
  39. package/src/domains/firestore/domain/entities/Collection_part_ab +138 -0
  40. package/src/domains/firestore/domain/entities/DocumentHelpers.ts +0 -0
  41. package/src/domains/firestore/domain/entities/DocumentMain.ts +0 -0
  42. package/src/domains/firestore/domain/entities/Document_part_aa +150 -0
  43. package/src/domains/firestore/domain/entities/Document_part_ab +83 -0
  44. package/src/domains/firestore/domain/index.ts +35 -8
  45. package/src/domains/firestore/domain/services/QueryServiceAnalysis_part_aa +150 -0
  46. package/src/domains/firestore/domain/services/QueryServiceAnalysis_part_ab +19 -0
  47. package/src/domains/firestore/domain/services/QueryServiceHelpers_part_aa +150 -0
  48. package/src/domains/firestore/domain/services/QueryServiceHelpers_part_ab +1 -0
  49. package/src/domains/firestore/domain/services/QueryService_part_aa +150 -0
  50. package/src/domains/firestore/domain/services/QueryService_part_ab +32 -0
  51. package/src/domains/firestore/domain/value-objects/QueryOptions.ts +20 -68
  52. package/src/domains/firestore/domain/value-objects/QueryOptions.ts.bak +6 -135
  53. package/src/domains/firestore/domain/value-objects/QueryOptionsFactory.ts +95 -0
  54. package/src/domains/firestore/domain/value-objects/QueryOptionsHelpers.ts +110 -0
  55. package/src/domains/firestore/domain/value-objects/QueryOptionsSerialization_part_aa +150 -0
  56. package/src/domains/firestore/domain/value-objects/QueryOptionsSerialization_part_ab +57 -0
  57. package/src/domains/firestore/domain/value-objects/QueryOptionsValidation_part_aa +150 -0
  58. package/src/domains/firestore/domain/value-objects/QueryOptionsValidation_part_ab +32 -0
  59. package/src/domains/firestore/domain/value-objects/QueryOptions_part_aa +150 -0
  60. package/src/domains/firestore/domain/value-objects/QueryOptions_part_ab +41 -0
  61. package/src/domains/firestore/domain/value-objects/WhereClause.ts +35 -205
  62. package/src/domains/firestore/domain/value-objects/WhereClause.ts.bak +299 -0
  63. package/src/domains/firestore/domain/value-objects/WhereClauseFactory.ts +44 -150
  64. package/src/domains/firestore/domain/value-objects/WhereClauseFactory.ts.bak +207 -0
  65. package/src/domains/firestore/domain/value-objects/WhereClauseFactory_part_aa +150 -0
  66. package/src/domains/firestore/domain/value-objects/WhereClauseFactory_part_ab +57 -0
  67. package/src/domains/firestore/domain/value-objects/WhereClauseHelpers.ts +123 -0
  68. package/src/domains/firestore/domain/value-objects/WhereClauseValidation.ts +83 -0
  69. package/src/domains/firestore/domain/value-objects/WhereClause_part_aa +150 -0
  70. package/src/domains/firestore/domain/value-objects/WhereClause_part_ab +149 -0
  71. package/src/shared/infrastructure/base/ErrorHandler_part_aa +150 -0
  72. package/src/shared/infrastructure/base/ErrorHandler_part_ab +39 -0
  73. package/src/shared/infrastructure/base/ServiceBase_part_aa +150 -0
  74. package/src/shared/infrastructure/base/ServiceBase_part_ab +70 -0
  75. package/src/shared/infrastructure/config/base/ServiceClientSingleton_part_aa +150 -0
  76. package/src/shared/infrastructure/config/base/ServiceClientSingleton_part_ab +5 -0
  77. /package/src/application/auth/ports/{AuthPort.ts → AuthPort.ts.bak} +0 -0
  78. /package/src/application/auth/use-cases/{SignInUseCase.ts → SignInUseCase.ts.bak} +0 -0
  79. /package/src/application/auth/use-cases/{SignOutUseCase.ts → SignOutUseCase.ts.bak} +0 -0
  80. /package/src/domains/account-deletion/infrastructure/services/{AccountDeletionExecutor.ts → AccountDeletionExecutor.ts.bak} +0 -0
  81. /package/src/domains/account-deletion/infrastructure/services/{AccountDeletionReauthHandler.ts → AccountDeletionReauthHandler.ts.bak} +0 -0
  82. /package/src/domains/account-deletion/infrastructure/services/{AccountDeletionRepository.ts → AccountDeletionRepository.ts.bak} +0 -0
  83. /package/src/domains/account-deletion/infrastructure/services/{reauthentication.service.ts → reauthentication.service.ts.bak} +0 -0
  84. /package/src/domains/auth/{infrastructure.ts → infrastructure.ts.bak} +0 -0
  85. /package/src/domains/auth/presentation/hooks/{GoogleOAuthHookService.ts → GoogleOAuthHookService.ts.bak} +0 -0
  86. /package/src/domains/firestore/domain/entities/{Document.ts → Document.ts.bak} +0 -0
  87. /package/src/domains/firestore/domain/services/{QueryService.ts → QueryService.ts.bak} +0 -0
  88. /package/src/domains/firestore/domain/services/{QueryServiceAnalysis.ts → QueryServiceAnalysis.ts.bak} +0 -0
  89. /package/src/domains/firestore/domain/services/{QueryServiceHelpers.ts → QueryServiceHelpers.ts.bak} +0 -0
  90. /package/src/domains/firestore/domain/value-objects/{QueryOptionsSerialization.ts → QueryOptionsSerialization.ts.bak} +0 -0
  91. /package/src/domains/firestore/domain/value-objects/{QueryOptionsValidation.ts → QueryOptionsValidation.ts.bak} +0 -0
  92. /package/src/shared/infrastructure/base/{ErrorHandler.ts → ErrorHandler.ts.bak} +0 -0
  93. /package/src/shared/infrastructure/base/{ServiceBase.ts → ServiceBase.ts.bak} +0 -0
  94. /package/src/shared/infrastructure/config/base/{ServiceClientSingleton.ts → ServiceClientSingleton.ts.bak} +0 -0
@@ -0,0 +1,116 @@
1
+ }
2
+
3
+ /**
4
+ * Get account creation time
5
+ */
6
+ getCreationTime(user: User): Date | null {
7
+ if (!user.metadata.creationTime) {
8
+ return null;
9
+ }
10
+ return new Date(user.metadata.creationTime);
11
+ }
12
+
13
+ /**
14
+ * Get last sign-in time
15
+ */
16
+ getLastSignInTime(user: User): Date | null {
17
+ if (!user.metadata.lastSignInTime) {
18
+ return null;
19
+ }
20
+ return new Date(user.metadata.lastSignInTime);
21
+ }
22
+
23
+ /**
24
+ * Check if account is new (created within specified days)
25
+ */
26
+ isAccountNew(user: User, maxAgeDays: number = 1): boolean {
27
+ const creationTime = this.getCreationTime(user);
28
+ if (!creationTime) return false;
29
+
30
+ const ageMs = Date.now() - creationTime.getTime();
31
+ const maxAgeMs = maxAgeDays * 24 * 60 * 60 * 1000;
32
+
33
+ return ageMs <= maxAgeMs;
34
+ }
35
+
36
+ /**
37
+ * Check if user recently signed in
38
+ */
39
+ isRecentSignIn(user: User, maxAgeMinutes: number = 5): boolean {
40
+ const lastSignIn = this.getLastSignInTime(user);
41
+ if (!lastSignIn) return false;
42
+
43
+ const timeSinceSignIn = Date.now() - lastSignIn.getTime();
44
+ const maxAgeMs = maxAgeMinutes * 60 * 1000;
45
+
46
+ return timeSinceSignIn <= maxAgeMs;
47
+ }
48
+
49
+ /**
50
+ * Mark user as deleted in database
51
+ * Separate method for flexibility
52
+ */
53
+ async markUserDeleted(userId: string): Promise<Result<void>> {
54
+ return this.execute(async () => {
55
+ this.log('Marking user as deleted', { userId });
56
+
57
+ const marked = await markUserDeleted(userId);
58
+ if (!marked) {
59
+ this.logError('Failed to mark user as deleted in database');
60
+ }
61
+
62
+ return successResult();
63
+ }, 'account-deletion/mark-failed');
64
+ }
65
+
66
+ /**
67
+ * Cleanup user data
68
+ * Override in subclass for custom cleanup logic
69
+ */
70
+ protected async cleanupUserData(userId: string): Promise<Result<void>> {
71
+ return this.execute(async () => {
72
+ this.log('Cleaning up user data', { userId });
73
+ // Override in subclass for custom cleanup
74
+ return successResult();
75
+ }, 'account-deletion/cleanup-failed');
76
+ }
77
+
78
+ /**
79
+ * Complete deletion with cleanup
80
+ * Deletes account and cleans up user data
81
+ */
82
+ async deleteWithCleanup(user: User): Promise<Result<void>> {
83
+ return this.execute(async () => {
84
+ this.log('Starting deletion with cleanup', { userId: user.uid });
85
+
86
+ // Delete account (includes marking document as deleted)
87
+ const deleteResult = await this.deleteAccount(user);
88
+ if (!deleteResult.success) {
89
+ return deleteResult;
90
+ }
91
+
92
+ // Cleanup additional user data
93
+ const cleanupResult = await this.cleanupUserData(user.uid);
94
+ if (!cleanupResult.success) {
95
+ this.logError('Cleanup failed, but account was deleted', {
96
+ userId: user.uid,
97
+ error: cleanupResult.error,
98
+ });
99
+ }
100
+
101
+ this.log('Deletion with cleanup completed', { userId: user.uid });
102
+ }, 'account-deletion/complete-failed');
103
+ }
104
+ }
105
+
106
+ /**
107
+ * Factory function to create account deletion repository
108
+ */
109
+ export function createAccountDeletionRepository(): AccountDeletionRepository {
110
+ return new AccountDeletionRepository();
111
+ }
112
+
113
+ /**
114
+ * Default singleton instance
115
+ */
116
+ export const accountDeletionRepository = createAccountDeletionRepository();
@@ -0,0 +1,150 @@
1
+ /**
2
+ * Reauthentication Service
3
+ * Handles Firebase Auth reauthentication for sensitive operations
4
+ */
5
+
6
+ import {
7
+ reauthenticateWithCredential,
8
+ GoogleAuthProvider,
9
+ OAuthProvider,
10
+ EmailAuthProvider,
11
+ type User,
12
+ } from "firebase/auth";
13
+ import { Platform } from "react-native";
14
+
15
+ /**
16
+ * Lazy-loads expo-apple-authentication (optional peer dependency).
17
+ * Returns null if the package is not installed.
18
+ */
19
+ function getAppleAuthModule(): typeof import("expo-apple-authentication") | null {
20
+ try {
21
+ // Dynamic require needed for optional peer dependency
22
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
23
+ return require("expo-apple-authentication");
24
+ } catch {
25
+ return null;
26
+ }
27
+ }
28
+ import { generateNonce, hashNonce } from "../../../auth/infrastructure/services/crypto.util";
29
+ import { executeOperation, failureResultFrom, toErrorInfo, ERROR_MESSAGES } from "../../../../shared/domain/utils";
30
+ import { isCancelledError } from "../../../../shared/domain/utils/error-handlers/error-checkers";
31
+ import type {
32
+ ReauthenticationResult,
33
+ AuthProviderType,
34
+ ReauthCredentialResult
35
+ } from "../../application/ports/reauthentication.types";
36
+
37
+ /**
38
+ * Validates email format
39
+ */
40
+ function isValidEmail(email: string): boolean {
41
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
42
+ return emailRegex.test(email);
43
+ }
44
+
45
+ /**
46
+ * Validates password (Firebase minimum is 6 characters)
47
+ */
48
+ function isValidPassword(password: string): boolean {
49
+ return password.length >= 6;
50
+ }
51
+
52
+ export function getUserAuthProvider(user: User): AuthProviderType {
53
+ if (user.isAnonymous) return "anonymous";
54
+ const data = user.providerData;
55
+ if (!data?.length) return "unknown";
56
+ if (data.find(p => p.providerId === "google.com")) return "google.com";
57
+ if (data.find(p => p.providerId === "apple.com")) return "apple.com";
58
+ if (data.find(p => p.providerId === "password")) return "password";
59
+ return "unknown";
60
+ }
61
+
62
+ export async function reauthenticateWithGoogle(user: User, idToken: string): Promise<ReauthenticationResult> {
63
+ return executeOperation(async () => {
64
+ await reauthenticateWithCredential(user, GoogleAuthProvider.credential(idToken));
65
+ });
66
+ }
67
+
68
+ export async function reauthenticateWithPassword(user: User, pass: string): Promise<ReauthenticationResult> {
69
+ const email = user.email;
70
+ if (!email) {
71
+ return failureResultFrom("auth/no-email", ERROR_MESSAGES.AUTH.NO_USER);
72
+ }
73
+
74
+ // FIX: Add email validation
75
+ if (!isValidEmail(email)) {
76
+ return failureResultFrom("auth/invalid-email", ERROR_MESSAGES.AUTH.INVALID_EMAIL);
77
+ }
78
+
79
+ // FIX: Add password validation
80
+ if (!isValidPassword(pass)) {
81
+ return failureResultFrom("auth/invalid-password", ERROR_MESSAGES.AUTH.INVALID_PASSWORD);
82
+ }
83
+
84
+ return executeOperation(async () => {
85
+ await reauthenticateWithCredential(user, EmailAuthProvider.credential(email, pass));
86
+ });
87
+ }
88
+
89
+ export async function getAppleReauthCredential(): Promise<ReauthCredentialResult> {
90
+ if (Platform.OS !== "ios") {
91
+ return {
92
+ success: false,
93
+ error: { code: "auth/ios-only", message: "iOS only" }
94
+ };
95
+ }
96
+
97
+ const AppleAuthentication = getAppleAuthModule();
98
+ if (!AppleAuthentication) {
99
+ return {
100
+ success: false,
101
+ error: { code: "auth/unavailable", message: "expo-apple-authentication is not installed" }
102
+ };
103
+ }
104
+
105
+ try {
106
+ const isAvailable = await AppleAuthentication.isAvailableAsync();
107
+ if (!isAvailable) {
108
+ return {
109
+ success: false,
110
+ error: { code: "auth/unavailable", message: "Unavailable" }
111
+ };
112
+ }
113
+
114
+ const nonce = await generateNonce();
115
+ const hashed = await hashNonce(nonce);
116
+ const apple = await AppleAuthentication.signInAsync({
117
+ requestedScopes: [
118
+ AppleAuthentication.AppleAuthenticationScope.FULL_NAME,
119
+ AppleAuthentication.AppleAuthenticationScope.EMAIL
120
+ ],
121
+ nonce: hashed,
122
+ });
123
+
124
+ if (!apple.identityToken) {
125
+ return {
126
+ success: false,
127
+ error: { code: "auth/no-token", message: "No token" }
128
+ };
129
+ }
130
+
131
+ const credential = new OAuthProvider("apple.com").credential({
132
+ idToken: apple.identityToken,
133
+ rawNonce: nonce
134
+ });
135
+
136
+ return {
137
+ success: true,
138
+ credential
139
+ };
140
+ } catch (error: unknown) {
141
+ const errorInfo = toErrorInfo(error, 'auth/failed');
142
+ const code = isCancelledError(errorInfo) ? "auth/cancelled" : errorInfo.code;
143
+ return {
144
+ success: false,
145
+ error: { code, message: errorInfo.message }
146
+ };
147
+ }
148
+ }
149
+
150
+ export async function reauthenticateWithApple(user: User): Promise<ReauthenticationResult> {
@@ -0,0 +1,10 @@
1
+ const res = await getAppleReauthCredential();
2
+ if (!res.success || !res.credential) {
3
+ return { success: false, error: res.error ?? { code: "auth/failed", message: "Failed to get credential" } };
4
+ }
5
+
6
+ const credential = res.credential;
7
+ return executeOperation(async () => {
8
+ await reauthenticateWithCredential(user, credential);
9
+ });
10
+ }
@@ -0,0 +1,150 @@
1
+ /**
2
+ * Firebase Auth Infrastructure Layer
3
+ * Domain-Driven Design (DDD) - Infrastructure Exports
4
+ *
5
+ * Infrastructure implementation including:
6
+ * - Firebase Auth client configuration
7
+ * - Auth utilities and services
8
+ * - Social auth providers
9
+ * - Email/password authentication
10
+ * - User document management
11
+ */
12
+
13
+ // =============================================================================
14
+ // Firebase Auth Client
15
+ // =============================================================================
16
+
17
+ export {
18
+ getFirebaseAuth,
19
+ isFirebaseAuthInitialized,
20
+ getFirebaseAuthInitializationError,
21
+ resetFirebaseAuthClient,
22
+ firebaseAuthClient,
23
+ initializeFirebaseAuth,
24
+ } from './infrastructure/config/FirebaseAuthClient';
25
+
26
+ export type {
27
+ Auth,
28
+ } from './infrastructure/config/FirebaseAuthClient';
29
+
30
+ // =============================================================================
31
+ // Auth Utilities
32
+ // =============================================================================
33
+
34
+ export {
35
+ checkAuthState,
36
+ isAuthenticated,
37
+ isAnonymous,
38
+ getCurrentUserId,
39
+ getCurrentUser,
40
+ getCurrentUserIdFromGlobal,
41
+ getCurrentUserFromGlobal,
42
+ isCurrentUserAuthenticated,
43
+ isCurrentUserAnonymous,
44
+ verifyUserId,
45
+ isValidUser,
46
+ } from './infrastructure/services/auth-utils.service';
47
+
48
+ export type {
49
+ AuthCheckResult,
50
+ } from './infrastructure/services/auth-utils.service';
51
+
52
+ // =============================================================================
53
+ // Anonymous Auth Service
54
+ // =============================================================================
55
+
56
+ export {
57
+ AnonymousAuthService,
58
+ anonymousAuthService,
59
+ } from './infrastructure/services/anonymous-auth.service';
60
+
61
+ export type {
62
+ AnonymousAuthResult,
63
+ AnonymousAuthServiceInterface,
64
+ } from './infrastructure/services/anonymous-auth.service';
65
+
66
+ // =============================================================================
67
+ // Firestore Utilities
68
+ // =============================================================================
69
+
70
+ export {
71
+ shouldSkipFirestoreQuery,
72
+ createFirestoreQueryOptions,
73
+ } from './infrastructure/services/firestore-utils.service';
74
+
75
+ export type {
76
+ FirestoreQueryOptions,
77
+ FirestoreQueryResult,
78
+ FirestoreQuerySkipReason,
79
+ } from './infrastructure/services/firestore-utils.service';
80
+
81
+ // =============================================================================
82
+ // Social Auth Services
83
+ // =============================================================================
84
+
85
+ export {
86
+ GoogleAuthService,
87
+ googleAuthService,
88
+ } from './infrastructure/services/google-auth.service';
89
+ export type {
90
+ GoogleAuthConfig,
91
+ GoogleAuthResult,
92
+ } from './infrastructure/services/google-auth.types';
93
+
94
+ export {
95
+ GoogleOAuthService,
96
+ googleOAuthService,
97
+ } from './infrastructure/services/google-oauth.service';
98
+ export type {
99
+ GoogleOAuthConfig,
100
+ } from './infrastructure/services/google-oauth.service';
101
+
102
+ export {
103
+ AppleAuthService,
104
+ appleAuthService,
105
+ } from './infrastructure/services/apple-auth.service';
106
+ export type { AppleAuthResult } from './infrastructure/services/apple-auth.types';
107
+
108
+ // =============================================================================
109
+ // Email/Password Authentication
110
+ // =============================================================================
111
+
112
+ export {
113
+ signInWithEmail,
114
+ signUpWithEmail,
115
+ signOut,
116
+ linkAnonymousWithEmail,
117
+ } from './infrastructure/services/email-auth.service';
118
+ export type {
119
+ EmailCredentials,
120
+ EmailAuthResult,
121
+ } from './infrastructure/services/email-auth.service';
122
+
123
+ // =============================================================================
124
+ // Password Management
125
+ // =============================================================================
126
+
127
+ export {
128
+ updateUserPassword,
129
+ } from './infrastructure/services/password.service';
130
+
131
+ // =============================================================================
132
+ // Auth Listener
133
+ // =============================================================================
134
+
135
+ export {
136
+ setupAuthListener,
137
+ } from './infrastructure/services/auth-listener.service';
138
+ export type {
139
+ AuthListenerConfig,
140
+ AuthListenerResult,
141
+ } from './infrastructure/services/auth-listener.service';
142
+
143
+ // =============================================================================
144
+ // User Document Service
145
+ // =============================================================================
146
+
147
+ export {
148
+ ensureUserDocument,
149
+ markUserDeleted,
150
+ configureUserDocumentService,
@@ -0,0 +1,6 @@
1
+ } from './infrastructure/services/user-document.service';
2
+ export type {
3
+ UserDocumentUser,
4
+ UserDocumentConfig,
5
+ UserDocumentExtras,
6
+ } from './infrastructure/services/user-document.types';
@@ -0,0 +1,150 @@
1
+ /**
2
+ * Google OAuth Hook Service
3
+ * Single Responsibility: Handle Google OAuth business logic
4
+ *
5
+ * Service class that manages Google OAuth flow.
6
+ * Separates business logic from React hook concerns.
7
+ *
8
+ * Max lines: 150 (enforced for maintainability)
9
+ */
10
+
11
+ import type { Auth } from 'firebase/auth';
12
+ import { googleOAuthService } from '../../infrastructure/services/google-oauth.service';
13
+ import type { GoogleOAuthConfig } from '../../infrastructure/services/google-oauth.service';
14
+
15
+ // Conditional import for expo-auth-session
16
+ interface AuthSessionResponse {
17
+ type: string;
18
+ authentication?: { idToken?: string } | null;
19
+ }
20
+
21
+ interface ExpoAuthSessionModule {
22
+ useAuthRequest: (config: {
23
+ iosClientId: string;
24
+ webClientId: string;
25
+ androidClientId: string;
26
+ }) => [unknown, AuthSessionResponse | null, (() => Promise<AuthSessionResponse>) | null];
27
+ }
28
+
29
+ let ExpoAuthSession: ExpoAuthSessionModule | null = null;
30
+ let isExpoAuthAvailable = false;
31
+
32
+ try {
33
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
34
+ ExpoAuthSession = require('expo-auth-session/providers/google') as ExpoAuthSessionModule;
35
+ isExpoAuthAvailable = true;
36
+ } catch {
37
+ // expo-auth-session not available
38
+ }
39
+
40
+ /**
41
+ * Google OAuth hook service
42
+ * Manages OAuth flow, response handling, and errors
43
+ */
44
+ export class GoogleOAuthHookService {
45
+ private config: GoogleOAuthConfig | undefined;
46
+ private authRequest: [unknown, AuthSessionResponse | null, (() => Promise<AuthSessionResponse>) | null];
47
+
48
+ constructor(config?: GoogleOAuthConfig) {
49
+ this.config = config;
50
+ this.authRequest = this.initAuthRequest();
51
+ }
52
+
53
+ /**
54
+ * Initialize auth request
55
+ * Uses expo-auth-session if available
56
+ */
57
+ private initAuthRequest(): [unknown, AuthSessionResponse | null, (() => Promise<AuthSessionResponse>) | null] {
58
+ if (!isExpoAuthAvailable || !ExpoAuthSession) {
59
+ return [null, null, null];
60
+ }
61
+
62
+ return ExpoAuthSession.useAuthRequest({
63
+ iosClientId: this.config?.iosClientId ?? '',
64
+ webClientId: this.config?.webClientId ?? '',
65
+ androidClientId: this.config?.androidClientId ?? '',
66
+ });
67
+ }
68
+
69
+ /**
70
+ * Check if Google OAuth is available
71
+ */
72
+ isAvailable(): boolean {
73
+ return isExpoAuthAvailable;
74
+ }
75
+
76
+ /**
77
+ * Check if Google OAuth is configured
78
+ */
79
+ isConfigured(): boolean {
80
+ return googleOAuthService.isConfigured(this.config);
81
+ }
82
+
83
+ /**
84
+ * Update configuration
85
+ */
86
+ updateConfig(config: GoogleOAuthConfig | undefined): void {
87
+ this.config = config;
88
+ }
89
+
90
+ /**
91
+ * Get auth request tuple
92
+ */
93
+ getAuthRequest(): [unknown, AuthSessionResponse | null, (() => Promise<AuthSessionResponse>) | null] {
94
+ return this.authRequest;
95
+ }
96
+
97
+ /**
98
+ * Handle OAuth response
99
+ * Called when expo-auth-session returns a response
100
+ */
101
+ async handleResponse(response: AuthSessionResponse | null, auth: Auth | null): Promise<void> {
102
+ if (!response) return;
103
+
104
+ if (response.type === 'success' && response.authentication?.idToken) {
105
+ if (!auth) {
106
+ throw new Error('Firebase Auth not initialized');
107
+ }
108
+
109
+ await googleOAuthService.signInWithOAuth(
110
+ auth,
111
+ this.config,
112
+ async () => response
113
+ );
114
+ } else if (response.type === 'error') {
115
+ throw new Error('Google authentication failed');
116
+ }
117
+ }
118
+
119
+ /**
120
+ * Sign in with Google
121
+ * Initiates OAuth flow and returns result
122
+ */
123
+ async signIn(auth: Auth | null): Promise<{ success: boolean; isNewUser?: boolean; error?: string }> {
124
+ if (!this.isAvailable()) {
125
+ const error = 'expo-auth-session is not available. Please install expo-auth-session and expo-web-browser.';
126
+ throw new Error(error);
127
+ }
128
+
129
+ if (!this.isConfigured()) {
130
+ const error = 'Google Sign-In is not configured. Please provide valid client IDs.';
131
+ throw new Error(error);
132
+ }
133
+
134
+ const [, , promptAsync] = this.authRequest;
135
+
136
+ if (!promptAsync) {
137
+ throw new Error('Google Sign-In not ready');
138
+ }
139
+
140
+ if (!auth) {
141
+ throw new Error('Firebase Auth not initialized');
142
+ }
143
+
144
+ return await googleOAuthService.signInWithOAuth(auth, this.config, promptAsync);
145
+ }
146
+
147
+ /**
148
+ * Validate OAuth state
149
+ */
150
+ validate(): { valid: boolean; error?: string } {
@@ -0,0 +1,97 @@
1
+ if (!this.isAvailable()) {
2
+ return {
3
+ valid: false,
4
+ error: 'expo-auth-session is not available. Please install expo-auth-session and expo-web-browser.',
5
+ };
6
+ }
7
+
8
+ if (!this.isConfigured()) {
9
+ return {
10
+ valid: false,
11
+ error: 'Google Sign-In is not configured. Please provide valid client IDs.',
12
+ };
13
+ }
14
+
15
+ return { valid: true };
16
+ }
17
+
18
+ /**
19
+ * Get error message from error
20
+ */
21
+ getErrorMessage(error: unknown): string {
22
+ if (error instanceof Error) {
23
+ return error.message;
24
+ }
25
+ return 'Google sign-in failed';
26
+ }
27
+
28
+ /**
29
+ * Check if response is successful
30
+ */
31
+ isSuccessfulResponse(response: AuthSessionResponse | null): boolean {
32
+ return response?.type === 'success' && !!response.authentication?.idToken;
33
+ }
34
+
35
+ /**
36
+ * Check if response is error
37
+ */
38
+ isErrorResponse(response: AuthSessionResponse | null): boolean {
39
+ return response?.type === 'error';
40
+ }
41
+
42
+ /**
43
+ * Extract ID token from response
44
+ */
45
+ extractIdToken(response: AuthSessionResponse | null): string | null {
46
+ return response?.authentication?.idToken || null;
47
+ }
48
+
49
+ /**
50
+ * Create error result
51
+ */
52
+ createErrorResult(error: string): { success: false; error: string } {
53
+ return { success: false, error };
54
+ }
55
+
56
+ /**
57
+ * Check if auth request is ready
58
+ */
59
+ isReady(): boolean {
60
+ const [request, , promptAsync] = this.authRequest;
61
+ return request !== null && promptAsync !== null;
62
+ }
63
+
64
+ /**
65
+ * Get configuration
66
+ */
67
+ getConfig(): GoogleOAuthConfig | undefined {
68
+ return this.config;
69
+ }
70
+
71
+ /**
72
+ * Reset service state
73
+ */
74
+ reset(): void {
75
+ this.config = undefined;
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Factory function to create Google OAuth hook service
81
+ */
82
+ export function createGoogleOAuthHookService(config?: GoogleOAuthConfig): GoogleOAuthHookService {
83
+ return new GoogleOAuthHookService(config);
84
+ }
85
+
86
+ /**
87
+ * Check if expo-auth-session is available
88
+ * Useful for conditional rendering
89
+ */
90
+ export function isExpoAuthSessionAvailable(): boolean {
91
+ return isExpoAuthAvailable;
92
+ }
93
+
94
+ /**
95
+ * Re-export types for convenience
96
+ */
97
+ export type { AuthSessionResponse, ExpoAuthSessionModule };