@umituz/web-firebase 1.0.5 → 2.1.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.
Files changed (69) hide show
  1. package/README.md +555 -0
  2. package/dist/application/index.d.mts +273 -0
  3. package/dist/application/index.d.ts +273 -0
  4. package/dist/application/index.js +490 -0
  5. package/dist/application/index.mjs +19 -0
  6. package/dist/chunk-34DL2QWQ.mjs +87 -0
  7. package/dist/chunk-4FP2ELQ5.mjs +96 -0
  8. package/dist/chunk-7TX3OU3O.mjs +721 -0
  9. package/dist/chunk-I6WGBPFB.mjs +439 -0
  10. package/dist/chunk-RZ4QR6TB.mjs +96 -0
  11. package/dist/chunk-U2XI4MGO.mjs +397 -0
  12. package/dist/domain/index.d.mts +325 -0
  13. package/dist/domain/index.d.ts +325 -0
  14. package/dist/domain/index.js +662 -0
  15. package/dist/domain/index.mjs +36 -0
  16. package/dist/file.repository.interface-v5vHgVsZ.d.mts +241 -0
  17. package/dist/file.repository.interface-v5vHgVsZ.d.ts +241 -0
  18. package/dist/firebase.entity-xvfEPjXZ.d.mts +15 -0
  19. package/dist/firebase.entity-xvfEPjXZ.d.ts +15 -0
  20. package/dist/index.d.mts +14 -96
  21. package/dist/index.d.ts +14 -96
  22. package/dist/index.js +1717 -78
  23. package/dist/index.mjs +88 -175
  24. package/dist/infrastructure/index.d.mts +170 -0
  25. package/dist/infrastructure/index.d.ts +170 -0
  26. package/dist/infrastructure/index.js +856 -0
  27. package/dist/infrastructure/index.mjs +46 -0
  28. package/dist/presentation/index.d.mts +25 -0
  29. package/dist/presentation/index.d.ts +25 -0
  30. package/dist/presentation/index.js +105 -0
  31. package/dist/presentation/index.mjs +6 -0
  32. package/dist/user.repository.interface-DS74TsJ5.d.mts +298 -0
  33. package/dist/user.repository.interface-DS74TsJ5.d.ts +298 -0
  34. package/package.json +37 -11
  35. package/src/application/dto/auth.dto.ts +69 -0
  36. package/src/application/dto/index.ts +7 -0
  37. package/src/application/dto/user.dto.ts +64 -0
  38. package/src/application/index.ts +7 -0
  39. package/src/application/use-cases/auth/reset-password.use-case.ts +66 -0
  40. package/src/application/use-cases/auth/sign-in-with-google.use-case.ts +86 -0
  41. package/src/application/use-cases/auth/sign-in.use-case.ts +77 -0
  42. package/src/application/use-cases/auth/sign-out.use-case.ts +22 -0
  43. package/src/application/use-cases/auth/sign-up.use-case.ts +99 -0
  44. package/src/application/use-cases/index.ts +12 -0
  45. package/src/application/use-cases/user/delete-account.use-case.ts +77 -0
  46. package/src/application/use-cases/user/update-profile.use-case.ts +98 -0
  47. package/src/domain/entities/file.entity.ts +151 -0
  48. package/src/domain/entities/timestamp.entity.ts +116 -0
  49. package/src/domain/entities/user.entity.ts +193 -0
  50. package/src/domain/errors/auth.errors.ts +115 -0
  51. package/src/domain/errors/repository.errors.ts +121 -0
  52. package/src/domain/index.ts +25 -2
  53. package/src/domain/interfaces/auth.repository.interface.ts +83 -0
  54. package/src/domain/interfaces/file.repository.interface.ts +143 -0
  55. package/src/domain/interfaces/user.repository.interface.ts +75 -0
  56. package/src/domain/value-objects/email.vo.ts +105 -0
  57. package/src/domain/value-objects/file-path.vo.ts +184 -0
  58. package/src/domain/value-objects/user-id.vo.ts +87 -0
  59. package/src/index.ts +19 -4
  60. package/src/infrastructure/firebase/auth.adapter.ts +220 -0
  61. package/src/infrastructure/firebase/client.ts +141 -0
  62. package/src/infrastructure/firebase/firestore.adapter.ts +190 -0
  63. package/src/infrastructure/firebase/storage.adapter.ts +323 -0
  64. package/src/infrastructure/index.ts +10 -5
  65. package/src/infrastructure/utils/storage.util.ts +3 -3
  66. package/src/presentation/hooks/useAuth.ts +108 -0
  67. package/src/presentation/hooks/useFirestore.ts +125 -0
  68. package/src/presentation/hooks/useStorage.ts +141 -0
  69. package/src/presentation/providers/FirebaseProvider.tsx +95 -0
@@ -0,0 +1,75 @@
1
+ /**
2
+ * User Repository Interface
3
+ * @description Defines contract for user data operations
4
+ */
5
+
6
+ import type { User } from '../entities/user.entity'
7
+ import type { QueryConstraint } from 'firebase/firestore'
8
+
9
+ /**
10
+ * User Repository Interface
11
+ * Defines operations for user data management
12
+ */
13
+ export interface IUserRepository {
14
+ /**
15
+ * Get user by ID
16
+ */
17
+ getUser(userId: string): Promise<User | null>
18
+
19
+ /**
20
+ * Get user by email
21
+ */
22
+ getUserByEmail(email: string): Promise<User | null>
23
+
24
+ /**
25
+ * Create user
26
+ */
27
+ createUser(userId: string, data: Partial<User>): Promise<void>
28
+
29
+ /**
30
+ * Update user
31
+ */
32
+ updateUser(userId: string, data: Partial<User>): Promise<void>
33
+
34
+ /**
35
+ * Delete user
36
+ */
37
+ deleteUser(userId: string): Promise<void>
38
+
39
+ /**
40
+ * Update user profile
41
+ */
42
+ updateProfile(userId: string, updates: Partial<Pick<User['profile'], 'displayName' | 'photoURL' | 'phoneNumber'>>): Promise<void>
43
+
44
+ /**
45
+ * Update user settings
46
+ */
47
+ updateSettings(userId: string, settings: Partial<User['settings']>): Promise<void>
48
+
49
+ /**
50
+ * Update user subscription
51
+ */
52
+ updateSubscription(
53
+ userId: string,
54
+ subscription: Partial<User['subscription']>
55
+ ): Promise<void>
56
+
57
+ /**
58
+ * Update last login timestamp
59
+ */
60
+ updateLastLogin(userId: string): Promise<void>
61
+
62
+ /**
63
+ * Query users with constraints
64
+ */
65
+ queryUsers(constraints: QueryConstraint[]): Promise<User[]>
66
+
67
+ /**
68
+ * Subscribe to user document changes
69
+ */
70
+ subscribeToUser(
71
+ userId: string,
72
+ callback: (user: User | null) => void,
73
+ onError?: (error: Error) => void
74
+ ): () => void
75
+ }
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Email Value Object
3
+ * @description Immutable email address value object with validation
4
+ */
5
+
6
+ export class Email {
7
+ private readonly value: string
8
+ private readonly validated: boolean
9
+
10
+ constructor(email: string) {
11
+ this.value = email.trim().toLowerCase()
12
+ this.validated = this.isValid()
13
+ if (!this.validated) {
14
+ throw new Error(`Invalid email address: ${email}`)
15
+ }
16
+ }
17
+
18
+ /**
19
+ * Validate email format
20
+ */
21
+ private isValid(): boolean {
22
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
23
+ return emailRegex.test(this.value)
24
+ }
25
+
26
+ /**
27
+ * Get email value
28
+ */
29
+ getValue(): string {
30
+ return this.value
31
+ }
32
+
33
+ /**
34
+ * Get local part (before @)
35
+ */
36
+ getLocalPart(): string {
37
+ return this.value.split('@')[0]
38
+ }
39
+
40
+ /**
41
+ * Get domain part (after @)
42
+ */
43
+ getDomain(): string {
44
+ return this.value.split('@')[1] || ''
45
+ }
46
+
47
+ /**
48
+ * Check if email is from a specific domain
49
+ */
50
+ isFromDomain(domain: string): boolean {
51
+ return this.getDomain() === domain.toLowerCase()
52
+ }
53
+
54
+ /**
55
+ * Check if email is from a corporate domain (not gmail, yahoo, etc.)
56
+ */
57
+ isCorporate(): boolean {
58
+ const freeDomains = [
59
+ 'gmail.com',
60
+ 'yahoo.com',
61
+ 'hotmail.com',
62
+ 'outlook.com',
63
+ 'aol.com',
64
+ 'icloud.com',
65
+ 'protonmail.com',
66
+ ]
67
+ return !freeDomains.includes(this.getDomain())
68
+ }
69
+
70
+ /**
71
+ * Mask email for display (e.g., u***@gmail.com)
72
+ */
73
+ mask(): string {
74
+ const [local, domain] = this.value.split('@')
75
+ if (local.length <= 2) {
76
+ return `${local[0]}***@${domain}`
77
+ }
78
+ return `${local[0]}${'*'.repeat(local.length - 2)}${local[local.length - 1]}@${domain}`
79
+ }
80
+
81
+ /**
82
+ * Convert to string
83
+ */
84
+ toString(): string {
85
+ return this.value
86
+ }
87
+
88
+ /**
89
+ * Check equality with another email
90
+ */
91
+ equals(other: Email): boolean {
92
+ return this.value === other.value
93
+ }
94
+
95
+ /**
96
+ * Create Email from string (returns null if invalid)
97
+ */
98
+ static create(email: string): Email | null {
99
+ try {
100
+ return new Email(email)
101
+ } catch {
102
+ return null
103
+ }
104
+ }
105
+ }
@@ -0,0 +1,184 @@
1
+ /**
2
+ * FilePath Value Object
3
+ * @description Immutable file path value object with validation for Firebase Storage paths
4
+ */
5
+
6
+ import { createRepositoryError, RepositoryErrorCode } from '../errors/repository.errors'
7
+
8
+ export class FilePath {
9
+ private readonly value: string
10
+ private readonly parts: string[]
11
+
12
+ constructor(path: string) {
13
+ this.value = path.trim()
14
+ this.parts = this.value.split('/').filter(p => p.length > 0)
15
+ this.validate()
16
+ }
17
+
18
+ /**
19
+ * Validate file path
20
+ */
21
+ private validate(): void {
22
+ if (!this.value) {
23
+ throw createRepositoryError(RepositoryErrorCode.INVALID_DATA, 'File path cannot be empty')
24
+ }
25
+
26
+ // Path should not start or end with /
27
+ if (this.value.startsWith('/') || this.value.endsWith('/')) {
28
+ throw createRepositoryError(
29
+ RepositoryErrorCode.INVALID_DATA,
30
+ 'File path should not start or end with /'
31
+ )
32
+ }
33
+
34
+ // Path should not contain //
35
+ if (this.value.includes('//')) {
36
+ throw createRepositoryError(RepositoryErrorCode.INVALID_DATA, 'File path should not contain //')
37
+ }
38
+
39
+ // Path segments should not be empty
40
+ if (this.parts.length === 0) {
41
+ throw createRepositoryError(RepositoryErrorCode.INVALID_DATA, 'File path must have at least one segment')
42
+ }
43
+
44
+ // Validate each segment
45
+ for (const part of this.parts) {
46
+ // Segments should not contain invalid characters
47
+ if (/[<>:"|?*]/.test(part)) {
48
+ throw createRepositoryError(
49
+ RepositoryErrorCode.INVALID_DATA,
50
+ `Invalid characters in path segment: ${part}`
51
+ )
52
+ }
53
+
54
+ // Segments should not be . or ..
55
+ if (part === '.' || part === '..') {
56
+ throw createRepositoryError(
57
+ RepositoryErrorCode.INVALID_DATA,
58
+ `Invalid path segment: ${part}`
59
+ )
60
+ }
61
+ }
62
+ }
63
+
64
+ /**
65
+ * Get file path value
66
+ */
67
+ getValue(): string {
68
+ return this.value
69
+ }
70
+
71
+ /**
72
+ * Get path parts
73
+ */
74
+ getParts(): string[] {
75
+ return [...this.parts]
76
+ }
77
+
78
+ /**
79
+ * Get filename (last part of path)
80
+ */
81
+ getFileName(): string {
82
+ return this.parts[this.parts.length - 1] || ''
83
+ }
84
+
85
+ /**
86
+ * Get directory path (all parts except last)
87
+ */
88
+ getDirectory(): string {
89
+ return this.parts.slice(0, -1).join('/')
90
+ }
91
+
92
+ /**
93
+ * Get file extension
94
+ */
95
+ getExtension(): string {
96
+ const fileName = this.getFileName()
97
+ const lastDot = fileName.lastIndexOf('.')
98
+ return lastDot > 0 ? fileName.substring(lastDot + 1) : ''
99
+ }
100
+
101
+ /**
102
+ * Get file name without extension
103
+ */
104
+ getFileNameWithoutExtension(): string {
105
+ const fileName = this.getFileName()
106
+ const lastDot = fileName.lastIndexOf('.')
107
+ return lastDot > 0 ? fileName.substring(0, lastDot) : fileName
108
+ }
109
+
110
+ /**
111
+ * Check if path is in a specific directory
112
+ */
113
+ isInDirectory(directory: string): boolean {
114
+ return this.value.startsWith(directory + '/')
115
+ }
116
+
117
+ /**
118
+ * Check if file has a specific extension
119
+ */
120
+ hasExtension(extension: string): boolean {
121
+ return this.getExtension().toLowerCase() === extension.toLowerCase()
122
+ }
123
+
124
+ /**
125
+ * Create a new path by appending segments
126
+ */
127
+ append(...segments: string[]): FilePath {
128
+ return new FilePath([...this.parts, ...segments].join('/'))
129
+ }
130
+
131
+ /**
132
+ * Create a new path in a parent directory
133
+ */
134
+ withParent(parent: string): FilePath {
135
+ return new FilePath([parent, ...this.parts].join('/'))
136
+ }
137
+
138
+ /**
139
+ * Create a new path with a different filename
140
+ */
141
+ withFileName(fileName: string): FilePath {
142
+ const dir = this.getDirectory()
143
+ return dir ? new FilePath(`${dir}/${fileName}`) : new FilePath(fileName)
144
+ }
145
+
146
+ /**
147
+ * Convert to string
148
+ */
149
+ toString(): string {
150
+ return this.value
151
+ }
152
+
153
+ /**
154
+ * Check equality with another file path
155
+ */
156
+ equals(other: FilePath): boolean {
157
+ return this.value === other.value
158
+ }
159
+
160
+ /**
161
+ * Create user path (users/{userId}/...)
162
+ */
163
+ static userPath(userId: string, ...segments: string[]): FilePath {
164
+ return new FilePath(['users', userId, ...segments].join('/'))
165
+ }
166
+
167
+ /**
168
+ * Create public path (public/...)
169
+ */
170
+ static publicPath(...segments: string[]): FilePath {
171
+ return new FilePath(['public', ...segments].join('/'))
172
+ }
173
+
174
+ /**
175
+ * Create FilePath from string (returns null if invalid)
176
+ */
177
+ static create(path: string): FilePath | null {
178
+ try {
179
+ return new FilePath(path)
180
+ } catch {
181
+ return null
182
+ }
183
+ }
184
+ }
@@ -0,0 +1,87 @@
1
+ /**
2
+ * UserId Value Object
3
+ * @description Immutable user ID value object with validation
4
+ */
5
+
6
+ import { createRepositoryError, RepositoryErrorCode } from '../errors/repository.errors'
7
+
8
+ export class UserId {
9
+ private readonly value: string
10
+
11
+ constructor(id: string) {
12
+ this.value = id
13
+ this.validate()
14
+ }
15
+
16
+ /**
17
+ * Validate user ID
18
+ */
19
+ private validate(): void {
20
+ if (!this.value) {
21
+ throw createRepositoryError(RepositoryErrorCode.INVALID_DATA, 'User ID cannot be empty')
22
+ }
23
+
24
+ // Firebase Auth UIDs are typically at least 20 characters
25
+ if (this.value.length < 20) {
26
+ throw createRepositoryError(
27
+ RepositoryErrorCode.INVALID_DATA,
28
+ 'User ID is too short (must be at least 20 characters)'
29
+ )
30
+ }
31
+
32
+ // Firebase UIDs contain only alphanumeric characters and certain special characters
33
+ const validPattern = /^[a-zA-Z0-9_-]+$/
34
+ if (!validPattern.test(this.value)) {
35
+ throw createRepositoryError(
36
+ RepositoryErrorCode.INVALID_DATA,
37
+ 'User ID contains invalid characters'
38
+ )
39
+ }
40
+ }
41
+
42
+ /**
43
+ * Get user ID value
44
+ */
45
+ getValue(): string {
46
+ return this.value
47
+ }
48
+
49
+ /**
50
+ * Check if this is a temporary ID (not yet persisted)
51
+ */
52
+ isTemporary(): boolean {
53
+ return this.value.startsWith('temp_')
54
+ }
55
+
56
+ /**
57
+ * Convert to string
58
+ */
59
+ toString(): string {
60
+ return this.value
61
+ }
62
+
63
+ /**
64
+ * Check equality with another user ID
65
+ */
66
+ equals(other: UserId): boolean {
67
+ return this.value === other.value
68
+ }
69
+
70
+ /**
71
+ * Generate a temporary user ID
72
+ */
73
+ static generateTemp(): UserId {
74
+ return new UserId(`temp_${Date.now()}_${Math.random().toString(36).substring(7)}`)
75
+ }
76
+
77
+ /**
78
+ * Create UserId from string (returns null if invalid)
79
+ */
80
+ static create(id: string): UserId | null {
81
+ try {
82
+ return new UserId(id)
83
+ } catch {
84
+ return null
85
+ }
86
+ }
87
+ }
package/src/index.ts CHANGED
@@ -1,8 +1,23 @@
1
1
  /**
2
2
  * @umituz/web-firebase
3
- * Universal Firebase utilities for web
3
+ * Comprehensive Firebase integration with DDD architecture
4
+ *
5
+ * ONEMLI: App'ler bu root barrel'i kullanMAMALI.
6
+ * Subpath import kullanin:
7
+ * - "@umituz/web-firebase/domain" - Domain entities, value objects, interfaces
8
+ * - "@umituz/web-firebase/application" - Use cases and DTOs
9
+ * - "@umituz/web-firebase/infrastructure" - Firebase adapters
10
+ * - "@umituz/web-firebase/presentation" - React hooks and providers
4
11
  */
5
12
 
6
- export * from './domain';
7
- export * from './infrastructure';
8
- export * from './presentation';
13
+ // Domain entities, value objects, interfaces, errors
14
+ export * from './domain'
15
+
16
+ // Application use cases and DTOs
17
+ export * from './application'
18
+
19
+ // Infrastructure Firebase adapters
20
+ export * from './infrastructure'
21
+
22
+ // Presentation hooks and providers
23
+ export * from './presentation'
@@ -0,0 +1,220 @@
1
+ /**
2
+ * Auth Adapter
3
+ * @description Firebase Auth implementation of IAuthRepository
4
+ * Migrated from: /Users/umituz/Desktop/github/umituz/apps/web/app-growth-factory/src/domains/firebase/services/auth.ts
5
+ */
6
+
7
+ import {
8
+ signInWithEmailAndPassword,
9
+ signInWithPopup,
10
+ createUserWithEmailAndPassword,
11
+ signOut as firebaseSignOut,
12
+ sendPasswordResetEmail,
13
+ sendEmailVerification,
14
+ updateProfile as updateAuthProfile,
15
+ updateEmail as updateAuthEmail,
16
+ updatePassword as updateAuthPassword,
17
+ reauthenticateWithCredential,
18
+ EmailAuthProvider,
19
+ User as FirebaseUser,
20
+ UserCredential,
21
+ } from 'firebase/auth'
22
+ import { GoogleAuthProvider } from 'firebase/auth'
23
+ import { getFirebaseAuth } from './client'
24
+ import type { IAuthRepository } from '../../domain/interfaces/auth.repository.interface'
25
+ import type { User } from '../../domain/entities/user.entity'
26
+ import { createAuthError, AuthErrorCode } from '../../domain/errors/auth.errors'
27
+
28
+ export class AuthAdapter implements IAuthRepository {
29
+ private get auth() {
30
+ return getFirebaseAuth()
31
+ }
32
+
33
+ // Authentication Methods
34
+
35
+ async signIn(email: string, password: string): Promise<UserCredential> {
36
+ try {
37
+ return await signInWithEmailAndPassword(this.auth, email, password)
38
+ } catch (error) {
39
+ throw this.handleAuthError(error)
40
+ }
41
+ }
42
+
43
+ async signUp(email: string, password: string, displayName: string): Promise<UserCredential> {
44
+ try {
45
+ const result = await createUserWithEmailAndPassword(this.auth, email, password)
46
+
47
+ // Update profile
48
+ await updateAuthProfile(result.user, { displayName })
49
+
50
+ // Send email verification
51
+ await sendEmailVerification(result.user)
52
+
53
+ return result
54
+ } catch (error) {
55
+ throw this.handleAuthError(error)
56
+ }
57
+ }
58
+
59
+ async signInWithGoogle(): Promise<UserCredential> {
60
+ try {
61
+ const provider = new GoogleAuthProvider()
62
+ provider.addScope('profile')
63
+ provider.addScope('email')
64
+
65
+ return await signInWithPopup(this.auth, provider)
66
+ } catch (error) {
67
+ throw this.handleAuthError(error)
68
+ }
69
+ }
70
+
71
+ async signOut(): Promise<void> {
72
+ try {
73
+ await firebaseSignOut(this.auth)
74
+ } catch (error) {
75
+ throw createAuthError(AuthErrorCode.SIGN_OUT_FAILED, 'Sign out failed', error)
76
+ }
77
+ }
78
+
79
+ async sendPasswordReset(email: string): Promise<void> {
80
+ try {
81
+ await sendPasswordResetEmail(this.auth, email)
82
+ } catch (error) {
83
+ throw this.handleAuthError(error)
84
+ }
85
+ }
86
+
87
+ async resendEmailVerification(): Promise<void> {
88
+ try {
89
+ const user = this.auth.currentUser
90
+ if (!user) {
91
+ throw createAuthError(AuthErrorCode.UNAUTHENTICATED, 'No user logged in')
92
+ }
93
+ await sendEmailVerification(user)
94
+ } catch (error) {
95
+ throw createAuthError(AuthErrorCode.EMAIL_VERIFICATION_FAILED, 'Failed to resend verification', error)
96
+ }
97
+ }
98
+
99
+ // Profile Management
100
+
101
+ async updateProfile(
102
+ updates: Partial<Pick<User['profile'], 'displayName' | 'photoURL'>>
103
+ ): Promise<void> {
104
+ try {
105
+ const user = this.auth.currentUser
106
+ if (!user) {
107
+ throw createAuthError(AuthErrorCode.UNAUTHENTICATED, 'No user logged in')
108
+ }
109
+
110
+ await updateAuthProfile(user, updates)
111
+ } catch (error) {
112
+ throw createAuthError(AuthErrorCode.PROFILE_UPDATE_FAILED, 'Profile update failed', error)
113
+ }
114
+ }
115
+
116
+ async updateEmail(newEmail: string, password: string): Promise<void> {
117
+ try {
118
+ const user = this.auth.currentUser
119
+ if (!user || !user.email) {
120
+ throw createAuthError(AuthErrorCode.UNAUTHENTICATED, 'No user logged in')
121
+ }
122
+
123
+ const credential = EmailAuthProvider.credential(user.email, password)
124
+ await reauthenticateWithCredential(user, credential)
125
+ await updateAuthEmail(user, newEmail)
126
+ } catch (error) {
127
+ throw createAuthError(AuthErrorCode.EMAIL_UPDATE_FAILED, 'Email update failed', error)
128
+ }
129
+ }
130
+
131
+ async updatePassword(currentPassword: string, newPassword: string): Promise<void> {
132
+ try {
133
+ const user = this.auth.currentUser
134
+ if (!user || !user.email) {
135
+ throw createAuthError(AuthErrorCode.UNAUTHENTICATED, 'No user logged in')
136
+ }
137
+
138
+ const credential = EmailAuthProvider.credential(user.email, currentPassword)
139
+ await reauthenticateWithCredential(user, credential)
140
+ await updateAuthPassword(user, newPassword)
141
+ } catch (error) {
142
+ throw createAuthError(AuthErrorCode.PASSWORD_UPDATE_FAILED, 'Password update failed', error)
143
+ }
144
+ }
145
+
146
+ async deleteAccount(password: string): Promise<void> {
147
+ try {
148
+ const user = this.auth.currentUser
149
+ if (!user || !user.email) {
150
+ throw createAuthError(AuthErrorCode.UNAUTHENTICATED, 'No user logged in')
151
+ }
152
+
153
+ const credential = EmailAuthProvider.credential(user.email, password)
154
+ await reauthenticateWithCredential(user, credential)
155
+ await user.delete()
156
+ } catch (error) {
157
+ throw createAuthError(AuthErrorCode.ACCOUNT_DELETE_FAILED, 'Account deletion failed', error)
158
+ }
159
+ }
160
+
161
+ // State Management
162
+
163
+ getCurrentUser(): FirebaseUser | null {
164
+ return this.auth.currentUser
165
+ }
166
+
167
+ onAuthStateChanged(callback: (user: FirebaseUser | null) => void) {
168
+ return this.auth.onAuthStateChanged(callback)
169
+ }
170
+
171
+ // Note: User document operations should be handled by UserAdapter
172
+ // These methods are part of IAuthRepository interface but should be implemented separately
173
+ async createUserDocument(
174
+ _userId: string,
175
+ _data: Partial<Omit<User, 'profile'>> & { email: string; displayName: string }
176
+ ): Promise<void> {
177
+ throw new Error('createUserDocument should be handled by UserAdapter')
178
+ }
179
+
180
+ async updateLastLogin(_userId: string): Promise<void> {
181
+ throw new Error('updateLastLogin should be handled by UserAdapter')
182
+ }
183
+
184
+ /**
185
+ * Handle Firebase Auth errors
186
+ */
187
+ private handleAuthError(error: unknown): Error {
188
+ if (error instanceof Error && 'code' in error) {
189
+ const code = (error as { code: string }).code
190
+
191
+ switch (code) {
192
+ case 'auth/user-not-found':
193
+ return createAuthError(AuthErrorCode.USER_NOT_FOUND, 'User not found', error)
194
+ case 'auth/wrong-password':
195
+ case 'auth/invalid-credential':
196
+ return createAuthError(AuthErrorCode.INVALID_CREDENTIALS, 'Invalid credentials', error)
197
+ case 'auth/email-already-in-use':
198
+ return createAuthError(AuthErrorCode.USER_ALREADY_EXISTS, 'Email already in use', error)
199
+ case 'auth/weak-password':
200
+ return createAuthError(AuthErrorCode.WEAK_PASSWORD, 'Password is too weak', error)
201
+ case 'auth/invalid-email':
202
+ return createAuthError(AuthErrorCode.INVALID_CREDENTIALS, 'Invalid email', error)
203
+ case 'auth/user-disabled':
204
+ return createAuthError(AuthErrorCode.USER_NOT_FOUND, 'Account disabled', error)
205
+ case 'auth/too-many-requests':
206
+ return createAuthError(AuthErrorCode.TOO_MANY_REQUESTS, 'Too many requests', error)
207
+ case 'auth/popup-closed-by-user':
208
+ return createAuthError(AuthErrorCode.OAUTH_CANCELLED, 'Sign in cancelled', error)
209
+ case 'auth/account-exists-with-different-credential':
210
+ return createAuthError(AuthErrorCode.OAUTH_ACCOUNT_EXISTS, 'Account exists with different provider', error)
211
+ case 'auth/requires-recent-login':
212
+ return createAuthError(AuthErrorCode.REAUTHENTICATION_REQUIRED, 'Please reauthenticate', error)
213
+ default:
214
+ return createAuthError(AuthErrorCode.UNKNOWN, `Auth error: ${code}`, error)
215
+ }
216
+ }
217
+
218
+ return createAuthError(AuthErrorCode.UNKNOWN, 'Unknown auth error', error)
219
+ }
220
+ }