@umituz/web-firebase 2.1.1 → 3.0.1
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 +12 -39
- package/src/domain/interfaces/auth.repository.interface.ts +4 -19
- package/src/domain/interfaces/file.repository.interface.ts +5 -102
- package/src/domain/interfaces/index.ts +7 -0
- package/src/domain/interfaces/user.repository.interface.ts +17 -44
- package/src/domains/auth/entities/index.ts +60 -0
- package/src/domains/auth/index.ts +13 -0
- package/src/domains/auth/services/auth.service.ts +245 -0
- package/src/domains/auth/services/index.ts +7 -0
- package/src/domains/auth/types/auth-service.interface.ts +72 -0
- package/src/domains/auth/types/index.ts +5 -0
- package/src/domains/firestore/entities/index.ts +82 -0
- package/src/domains/firestore/index.ts +13 -0
- package/src/domains/firestore/services/firestore.service.ts +191 -0
- package/src/domains/firestore/services/index.ts +7 -0
- package/src/domains/firestore/types/firestore-service.interface.ts +64 -0
- package/src/domains/firestore/types/index.ts +5 -0
- package/src/domains/storage/entities/index.ts +94 -0
- package/src/domains/storage/index.ts +13 -0
- package/src/domains/storage/services/index.ts +7 -0
- package/src/domains/storage/services/storage.service.ts +223 -0
- package/src/domains/storage/types/index.ts +5 -0
- package/src/domains/storage/types/storage-service.interface.ts +120 -0
- package/src/index.ts +12 -16
- package/src/presentation/hooks/useAuth.ts +69 -26
- package/src/presentation/providers/FirebaseProvider.tsx +9 -14
- package/dist/application/index.d.mts +0 -273
- package/dist/application/index.d.ts +0 -273
- package/dist/application/index.js +0 -490
- package/dist/application/index.mjs +0 -19
- package/dist/chunk-34DL2QWQ.mjs +0 -87
- package/dist/chunk-4FP2ELQ5.mjs +0 -96
- package/dist/chunk-7TX3OU3O.mjs +0 -721
- package/dist/chunk-I6WGBPFB.mjs +0 -439
- package/dist/chunk-RZ4QR6TB.mjs +0 -96
- package/dist/chunk-U2XI4MGO.mjs +0 -397
- package/dist/domain/index.d.mts +0 -325
- package/dist/domain/index.d.ts +0 -325
- package/dist/domain/index.js +0 -662
- package/dist/domain/index.mjs +0 -36
- package/dist/file.repository.interface-v5vHgVsZ.d.mts +0 -241
- package/dist/file.repository.interface-v5vHgVsZ.d.ts +0 -241
- package/dist/firebase.entity-xvfEPjXZ.d.mts +0 -15
- package/dist/firebase.entity-xvfEPjXZ.d.ts +0 -15
- package/dist/index.d.mts +0 -14
- package/dist/index.d.ts +0 -14
- package/dist/index.js +0 -1833
- package/dist/index.mjs +0 -98
- package/dist/infrastructure/index.d.mts +0 -170
- package/dist/infrastructure/index.d.ts +0 -170
- package/dist/infrastructure/index.js +0 -856
- package/dist/infrastructure/index.mjs +0 -46
- package/dist/presentation/index.d.mts +0 -25
- package/dist/presentation/index.d.ts +0 -25
- package/dist/presentation/index.js +0 -105
- package/dist/presentation/index.mjs +0 -6
- package/dist/user.repository.interface-DS74TsJ5.d.mts +0 -298
- package/dist/user.repository.interface-DS74TsJ5.d.ts +0 -298
package/package.json
CHANGED
|
@@ -1,49 +1,25 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/web-firebase",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "Comprehensive Firebase integration with
|
|
5
|
-
"main": "./
|
|
6
|
-
"
|
|
7
|
-
"types": "./dist/index.d.ts",
|
|
3
|
+
"version": "3.0.1",
|
|
4
|
+
"description": "Comprehensive Firebase integration with domain-based architecture for web applications",
|
|
5
|
+
"main": "./src/index.ts",
|
|
6
|
+
"types": "./src/index.ts",
|
|
8
7
|
"sideEffects": false,
|
|
9
8
|
"publishConfig": {
|
|
10
9
|
"access": "public"
|
|
11
10
|
},
|
|
12
11
|
"exports": {
|
|
13
|
-
".":
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
"./domain": {
|
|
19
|
-
"types": "./dist/domain/index.d.ts",
|
|
20
|
-
"import": "./dist/domain/index.mjs",
|
|
21
|
-
"require": "./dist/domain/index.js"
|
|
22
|
-
},
|
|
23
|
-
"./application": {
|
|
24
|
-
"types": "./dist/application/index.d.ts",
|
|
25
|
-
"import": "./dist/application/index.mjs",
|
|
26
|
-
"require": "./dist/application/index.js"
|
|
27
|
-
},
|
|
28
|
-
"./infrastructure": {
|
|
29
|
-
"types": "./dist/infrastructure/index.d.ts",
|
|
30
|
-
"import": "./dist/infrastructure/index.mjs",
|
|
31
|
-
"require": "./dist/infrastructure/index.js"
|
|
32
|
-
},
|
|
33
|
-
"./presentation": {
|
|
34
|
-
"types": "./dist/presentation/index.d.ts",
|
|
35
|
-
"import": "./dist/presentation/index.mjs",
|
|
36
|
-
"require": "./dist/presentation/index.js"
|
|
37
|
-
}
|
|
12
|
+
".": "./src/index.ts",
|
|
13
|
+
"./auth": "./src/domains/auth/index.ts",
|
|
14
|
+
"./firestore": "./src/domains/firestore/index.ts",
|
|
15
|
+
"./storage": "./src/domains/storage/index.ts",
|
|
16
|
+
"./package.json": "./package.json"
|
|
38
17
|
},
|
|
39
18
|
"files": [
|
|
40
|
-
"dist",
|
|
41
19
|
"src"
|
|
42
20
|
],
|
|
43
21
|
"scripts": {
|
|
44
|
-
"
|
|
45
|
-
"dev": "tsup src/index.ts src/domain/index.ts src/application/index.ts src/infrastructure/index.ts src/presentation/index.ts --format cjs,esm --dts --watch --external react --external firebase",
|
|
46
|
-
"lint": "tsc --noEmit"
|
|
22
|
+
"typecheck": "tsc --noEmit"
|
|
47
23
|
},
|
|
48
24
|
"keywords": [
|
|
49
25
|
"firebase",
|
|
@@ -57,10 +33,8 @@
|
|
|
57
33
|
"typescript",
|
|
58
34
|
"ddd",
|
|
59
35
|
"domain-driven-design",
|
|
60
|
-
"
|
|
61
|
-
"repository-pattern"
|
|
62
|
-
"use-cases",
|
|
63
|
-
"hexagonal-architecture"
|
|
36
|
+
"package-driven",
|
|
37
|
+
"repository-pattern"
|
|
64
38
|
],
|
|
65
39
|
"author": "umituz",
|
|
66
40
|
"license": "MIT",
|
|
@@ -71,7 +45,6 @@
|
|
|
71
45
|
"devDependencies": {
|
|
72
46
|
"@types/react": "^18.2.0",
|
|
73
47
|
"firebase": "^12.11.0",
|
|
74
|
-
"tsup": "^8.0.0",
|
|
75
48
|
"typescript": "^5.0.0"
|
|
76
49
|
}
|
|
77
50
|
}
|
|
@@ -1,15 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Authentication Repository Interface
|
|
3
|
-
* @description
|
|
3
|
+
* @description Generic contract for authentication operations
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import type { User as FirebaseUser
|
|
7
|
-
import type { User } from '../entities/user.entity'
|
|
6
|
+
import type { UserCredential, User as FirebaseUser } from 'firebase/auth'
|
|
8
7
|
|
|
9
|
-
/**
|
|
10
|
-
* Authentication Repository Interface
|
|
11
|
-
* Defines operations for user authentication and management
|
|
12
|
-
*/
|
|
13
8
|
export interface IAuthRepository {
|
|
14
9
|
/**
|
|
15
10
|
* Sign in with email and password
|
|
@@ -44,7 +39,7 @@ export interface IAuthRepository {
|
|
|
44
39
|
/**
|
|
45
40
|
* Update user profile (displayName, photoURL)
|
|
46
41
|
*/
|
|
47
|
-
updateProfile(updates:
|
|
42
|
+
updateProfile(updates: { displayName?: string; photoURL?: string }): Promise<void>
|
|
48
43
|
|
|
49
44
|
/**
|
|
50
45
|
* Update user email (requires password)
|
|
@@ -69,15 +64,5 @@ export interface IAuthRepository {
|
|
|
69
64
|
/**
|
|
70
65
|
* Subscribe to auth state changes
|
|
71
66
|
*/
|
|
72
|
-
onAuthStateChanged(callback: (user: FirebaseUser | null) => void): () => void
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Create user document in Firestore
|
|
76
|
-
*/
|
|
77
|
-
createUserDocument(userId: string, data: Partial<Omit<User, 'profile'>> & { email: string; displayName: string }): Promise<void>
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Update last login timestamp
|
|
81
|
-
*/
|
|
82
|
-
updateLastLogin(userId: string): Promise<void>
|
|
67
|
+
onAuthStateChanged(callback: (user: FirebaseUser | null) => void, onError?: (error: Error) => void): () => void
|
|
83
68
|
}
|
|
@@ -1,51 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* File Repository Interface
|
|
3
|
-
* @description
|
|
3
|
+
* @description Generic contract for file storage operations
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import type {
|
|
7
|
-
FileMetadata,
|
|
8
|
-
UploadResult,
|
|
9
|
-
UploadOptions,
|
|
10
|
-
FileFilters,
|
|
11
|
-
FileQueryResult,
|
|
12
|
-
StorageStats,
|
|
13
|
-
} from '../entities/file.entity'
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* File Repository Interface
|
|
17
|
-
* Defines operations for file storage and management
|
|
18
|
-
*/
|
|
19
6
|
export interface IFileRepository {
|
|
20
7
|
/**
|
|
21
8
|
* Upload file to storage
|
|
22
9
|
*/
|
|
23
|
-
uploadFile(
|
|
24
|
-
userId: string,
|
|
25
|
-
path: string,
|
|
26
|
-
file: File | Blob,
|
|
27
|
-
options?: UploadOptions
|
|
28
|
-
): Promise<UploadResult>
|
|
10
|
+
uploadFile(userId: string, path: string, file: File | Blob, options?: any): Promise<any>
|
|
29
11
|
|
|
30
12
|
/**
|
|
31
13
|
* Upload image with automatic categorization
|
|
32
14
|
*/
|
|
33
|
-
uploadImage(userId: string, file: File, filename?: string): Promise<
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Upload video with automatic categorization
|
|
37
|
-
*/
|
|
38
|
-
uploadVideo(userId: string, file: File, filename?: string): Promise<UploadResult>
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Upload document with automatic categorization
|
|
42
|
-
*/
|
|
43
|
-
uploadDocument(userId: string, file: File, filename?: string): Promise<UploadResult>
|
|
15
|
+
uploadImage(userId: string, file: File, filename?: string): Promise<any>
|
|
44
16
|
|
|
45
17
|
/**
|
|
46
18
|
* Upload profile picture
|
|
47
19
|
*/
|
|
48
|
-
uploadProfilePicture(userId: string, file: File): Promise<
|
|
20
|
+
uploadProfilePicture(userId: string, file: File): Promise<any>
|
|
49
21
|
|
|
50
22
|
/**
|
|
51
23
|
* Get download URL for a file
|
|
@@ -62,82 +34,13 @@ export interface IFileRepository {
|
|
|
62
34
|
*/
|
|
63
35
|
deleteUserFiles(userId: string): Promise<void>
|
|
64
36
|
|
|
65
|
-
/**
|
|
66
|
-
* Delete user image
|
|
67
|
-
*/
|
|
68
|
-
deleteImage(userId: string, filename: string): Promise<void>
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Delete user video
|
|
72
|
-
*/
|
|
73
|
-
deleteVideo(userId: string, filename: string): Promise<void>
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Delete profile picture
|
|
77
|
-
*/
|
|
78
|
-
deleteProfilePicture(userId: string, filename: string): Promise<void>
|
|
79
|
-
|
|
80
37
|
/**
|
|
81
38
|
* List user files
|
|
82
39
|
*/
|
|
83
40
|
listUserFiles(userId: string, path?: string): Promise<string[]>
|
|
84
41
|
|
|
85
|
-
/**
|
|
86
|
-
* List user images
|
|
87
|
-
*/
|
|
88
|
-
listUserImages(userId: string): Promise<string[]>
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* List user videos
|
|
92
|
-
*/
|
|
93
|
-
listUserVideos(userId: string): Promise<string[]>
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Get file metadata
|
|
97
|
-
*/
|
|
98
|
-
getFileMetadata(path: string): Promise<FileMetadata>
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* Query files with filters
|
|
102
|
-
*/
|
|
103
|
-
queryFiles(userId: string, filters?: FileFilters): Promise<FileQueryResult>
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Get storage statistics
|
|
107
|
-
*/
|
|
108
|
-
getStorageStats(userId: string): Promise<StorageStats>
|
|
109
|
-
|
|
110
42
|
/**
|
|
111
43
|
* Validate file before upload
|
|
112
44
|
*/
|
|
113
|
-
validateFile(file: File, options?:
|
|
114
|
-
maxSizeBytes?: number
|
|
115
|
-
maxSizeMB?: number
|
|
116
|
-
allowedTypes?: string[]
|
|
117
|
-
}): boolean
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* Check if file is an image
|
|
121
|
-
*/
|
|
122
|
-
isImageFile(file: File): boolean
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* Check if file is a video
|
|
126
|
-
*/
|
|
127
|
-
isVideoFile(file: File): boolean
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Check if file is a document
|
|
131
|
-
*/
|
|
132
|
-
isDocumentFile(file: File): boolean
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
* Generate unique filename
|
|
136
|
-
*/
|
|
137
|
-
generateUniqueFilename(originalName: string): string
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* Get file extension
|
|
141
|
-
*/
|
|
142
|
-
getFileExtension(filename: string): string
|
|
45
|
+
validateFile(file: File, options?: any): boolean
|
|
143
46
|
}
|
|
@@ -1,75 +1,48 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* User Repository Interface
|
|
3
|
-
* @description
|
|
3
|
+
* @description Generic contract for user data operations
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import type { User } from '../entities/user.entity'
|
|
7
6
|
import type { QueryConstraint } from 'firebase/firestore'
|
|
8
7
|
|
|
9
|
-
/**
|
|
10
|
-
* User Repository Interface
|
|
11
|
-
* Defines operations for user data management
|
|
12
|
-
*/
|
|
13
8
|
export interface IUserRepository {
|
|
14
9
|
/**
|
|
15
|
-
* Get
|
|
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
|
|
10
|
+
* Get document by ID
|
|
31
11
|
*/
|
|
32
|
-
|
|
12
|
+
getById<T>(userId: string): Promise<T | null>
|
|
33
13
|
|
|
34
14
|
/**
|
|
35
|
-
*
|
|
15
|
+
* Get document by field
|
|
36
16
|
*/
|
|
37
|
-
|
|
17
|
+
getByField<T>(collectionPath: string, field: string, value: any): Promise<T | null>
|
|
38
18
|
|
|
39
19
|
/**
|
|
40
|
-
*
|
|
20
|
+
* Create document
|
|
41
21
|
*/
|
|
42
|
-
|
|
22
|
+
create(userId: string, data: any): Promise<void>
|
|
43
23
|
|
|
44
24
|
/**
|
|
45
|
-
* Update
|
|
25
|
+
* Update document
|
|
46
26
|
*/
|
|
47
|
-
|
|
27
|
+
update(userId: string, data: any, options?: { merge?: boolean }): Promise<void>
|
|
48
28
|
|
|
49
29
|
/**
|
|
50
|
-
*
|
|
30
|
+
* Delete document
|
|
51
31
|
*/
|
|
52
|
-
|
|
53
|
-
userId: string,
|
|
54
|
-
subscription: Partial<User['subscription']>
|
|
55
|
-
): Promise<void>
|
|
32
|
+
delete(userId: string): Promise<void>
|
|
56
33
|
|
|
57
34
|
/**
|
|
58
|
-
*
|
|
35
|
+
* Query collection with constraints
|
|
59
36
|
*/
|
|
60
|
-
|
|
37
|
+
query<T>(collectionPath: string, constraints: QueryConstraint[]): Promise<T[]>
|
|
61
38
|
|
|
62
39
|
/**
|
|
63
|
-
*
|
|
40
|
+
* Subscribe to document changes
|
|
64
41
|
*/
|
|
65
|
-
|
|
42
|
+
subscribeToDoc<T>(docPath: string, callback: (data: T | null) => void): () => void
|
|
66
43
|
|
|
67
44
|
/**
|
|
68
|
-
* Subscribe to
|
|
45
|
+
* Subscribe to collection changes
|
|
69
46
|
*/
|
|
70
|
-
|
|
71
|
-
userId: string,
|
|
72
|
-
callback: (user: User | null) => void,
|
|
73
|
-
onError?: (error: Error) => void
|
|
74
|
-
): () => void
|
|
47
|
+
subscribeToCollection<T>(collectionPath: string, callback: (data: T[]) => void, constraints?: QueryConstraint[]): () => void
|
|
75
48
|
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auth Domain Entities
|
|
3
|
+
* @description Core authentication-related entities
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { User as FirebaseUser } from 'firebase/auth'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Auth User Entity
|
|
10
|
+
* Wrapper around Firebase User with application-specific data
|
|
11
|
+
*/
|
|
12
|
+
export interface AuthUser {
|
|
13
|
+
readonly uid: string
|
|
14
|
+
readonly email: string | null
|
|
15
|
+
readonly emailVerified: boolean
|
|
16
|
+
readonly displayName: string | null
|
|
17
|
+
readonly photoURL: string | null
|
|
18
|
+
readonly phoneNumber: string | null
|
|
19
|
+
readonly isAnonymous: boolean
|
|
20
|
+
readonly tenantId: string | null
|
|
21
|
+
readonly providerId: string
|
|
22
|
+
readonly metadata: {
|
|
23
|
+
readonly creationTime?: number
|
|
24
|
+
readonly lastSignInTime?: number
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Auth State Entity
|
|
30
|
+
* Current authentication state
|
|
31
|
+
*/
|
|
32
|
+
export interface AuthState {
|
|
33
|
+
readonly user: AuthUser | null
|
|
34
|
+
readonly isLoading: boolean
|
|
35
|
+
readonly isInitialized: boolean
|
|
36
|
+
readonly error: Error | null
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Convert Firebase User to Auth User
|
|
41
|
+
*/
|
|
42
|
+
export function toAuthUser(firebaseUser: FirebaseUser | null): AuthUser | null {
|
|
43
|
+
if (!firebaseUser) return null
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
uid: firebaseUser.uid,
|
|
47
|
+
email: firebaseUser.email,
|
|
48
|
+
emailVerified: firebaseUser.emailVerified,
|
|
49
|
+
displayName: firebaseUser.displayName,
|
|
50
|
+
photoURL: firebaseUser.photoURL,
|
|
51
|
+
phoneNumber: firebaseUser.phoneNumber,
|
|
52
|
+
isAnonymous: firebaseUser.isAnonymous,
|
|
53
|
+
tenantId: firebaseUser.tenantId,
|
|
54
|
+
providerId: firebaseUser.providerId,
|
|
55
|
+
metadata: {
|
|
56
|
+
creationTime: firebaseUser.metadata.creationTime ? new Date(firebaseUser.metadata.creationTime).getTime() : undefined,
|
|
57
|
+
lastSignInTime: firebaseUser.metadata.lastSignInTime ? new Date(firebaseUser.metadata.lastSignInTime).getTime() : undefined,
|
|
58
|
+
},
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auth Service
|
|
3
|
+
* @description Firebase Auth implementation of IAuthService
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
signInWithEmailAndPassword,
|
|
8
|
+
signInWithPopup,
|
|
9
|
+
createUserWithEmailAndPassword,
|
|
10
|
+
signOut as firebaseSignOut,
|
|
11
|
+
sendPasswordResetEmail,
|
|
12
|
+
sendEmailVerification,
|
|
13
|
+
updateProfile as updateAuthProfile,
|
|
14
|
+
updateEmail as updateAuthEmail,
|
|
15
|
+
updatePassword as updateAuthPassword,
|
|
16
|
+
reauthenticateWithCredential,
|
|
17
|
+
EmailAuthProvider,
|
|
18
|
+
UserCredential,
|
|
19
|
+
} from 'firebase/auth'
|
|
20
|
+
import { GoogleAuthProvider } from 'firebase/auth'
|
|
21
|
+
import { getFirebaseAuth } from '../../../infrastructure/firebase/client'
|
|
22
|
+
import type { IAuthService } from '../types'
|
|
23
|
+
import type { AuthUser } from '../entities'
|
|
24
|
+
|
|
25
|
+
class AuthService implements IAuthService {
|
|
26
|
+
private get auth() {
|
|
27
|
+
return getFirebaseAuth()
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Authentication Methods
|
|
31
|
+
|
|
32
|
+
async signIn(email: string, password: string): Promise<UserCredential> {
|
|
33
|
+
try {
|
|
34
|
+
return await signInWithEmailAndPassword(this.auth, email, password)
|
|
35
|
+
} catch (error) {
|
|
36
|
+
throw this.handleAuthError(error)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async signUp(email: string, password: string, displayName: string): Promise<UserCredential> {
|
|
41
|
+
try {
|
|
42
|
+
const result = await createUserWithEmailAndPassword(this.auth, email, password)
|
|
43
|
+
|
|
44
|
+
// Update profile
|
|
45
|
+
await updateAuthProfile(result.user, { displayName })
|
|
46
|
+
|
|
47
|
+
// Send email verification
|
|
48
|
+
await sendEmailVerification(result.user)
|
|
49
|
+
|
|
50
|
+
return result
|
|
51
|
+
} catch (error) {
|
|
52
|
+
throw this.handleAuthError(error)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async signInWithGoogle(): Promise<UserCredential> {
|
|
57
|
+
try {
|
|
58
|
+
const provider = new GoogleAuthProvider()
|
|
59
|
+
provider.addScope('profile')
|
|
60
|
+
provider.addScope('email')
|
|
61
|
+
|
|
62
|
+
return await signInWithPopup(this.auth, provider)
|
|
63
|
+
} catch (error) {
|
|
64
|
+
throw this.handleAuthError(error)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async signOut(): Promise<void> {
|
|
69
|
+
try {
|
|
70
|
+
await firebaseSignOut(this.auth)
|
|
71
|
+
} catch (error) {
|
|
72
|
+
throw new Error('Sign out failed')
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async sendPasswordReset(email: string): Promise<void> {
|
|
77
|
+
try {
|
|
78
|
+
await sendPasswordResetEmail(this.auth, email)
|
|
79
|
+
} catch (error) {
|
|
80
|
+
throw this.handleAuthError(error)
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async resendEmailVerification(): Promise<void> {
|
|
85
|
+
try {
|
|
86
|
+
const user = this.auth.currentUser
|
|
87
|
+
if (!user) {
|
|
88
|
+
throw new Error('No user logged in')
|
|
89
|
+
}
|
|
90
|
+
await sendEmailVerification(user)
|
|
91
|
+
} catch (error) {
|
|
92
|
+
throw new Error('Failed to resend verification')
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Profile Management
|
|
97
|
+
|
|
98
|
+
async updateProfile(updates: { displayName?: string; photoURL?: string }): Promise<void> {
|
|
99
|
+
try {
|
|
100
|
+
const user = this.auth.currentUser
|
|
101
|
+
if (!user) {
|
|
102
|
+
throw new Error('No user logged in')
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
await updateAuthProfile(user, updates)
|
|
106
|
+
} catch (error) {
|
|
107
|
+
throw new Error('Profile update failed')
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async updateEmail(newEmail: string, password: string): Promise<void> {
|
|
112
|
+
try {
|
|
113
|
+
const user = this.auth.currentUser
|
|
114
|
+
if (!user || !user.email) {
|
|
115
|
+
throw new Error('No user logged in')
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const credential = EmailAuthProvider.credential(user.email, password)
|
|
119
|
+
await reauthenticateWithCredential(user, credential)
|
|
120
|
+
await updateAuthEmail(user, newEmail)
|
|
121
|
+
} catch (error) {
|
|
122
|
+
throw new Error('Email update failed')
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async updatePassword(currentPassword: string, newPassword: string): Promise<void> {
|
|
127
|
+
try {
|
|
128
|
+
const user = this.auth.currentUser
|
|
129
|
+
if (!user || !user.email) {
|
|
130
|
+
throw new Error('No user logged in')
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const credential = EmailAuthProvider.credential(user.email, currentPassword)
|
|
134
|
+
await reauthenticateWithCredential(user, credential)
|
|
135
|
+
await updateAuthPassword(user, newPassword)
|
|
136
|
+
} catch (error) {
|
|
137
|
+
throw new Error('Password update failed')
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async deleteAccount(password: string): Promise<void> {
|
|
142
|
+
try {
|
|
143
|
+
const user = this.auth.currentUser
|
|
144
|
+
if (!user || !user.email) {
|
|
145
|
+
throw new Error('No user logged in')
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const credential = EmailAuthProvider.credential(user.email, password)
|
|
149
|
+
await reauthenticateWithCredential(user, credential)
|
|
150
|
+
await user.delete()
|
|
151
|
+
} catch (error) {
|
|
152
|
+
throw new Error('Account deletion failed')
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// State Management
|
|
157
|
+
|
|
158
|
+
getCurrentUser(): AuthUser | null {
|
|
159
|
+
const user = this.auth.currentUser
|
|
160
|
+
if (!user) return null
|
|
161
|
+
|
|
162
|
+
return {
|
|
163
|
+
uid: user.uid,
|
|
164
|
+
email: user.email,
|
|
165
|
+
emailVerified: user.emailVerified,
|
|
166
|
+
displayName: user.displayName,
|
|
167
|
+
photoURL: user.photoURL,
|
|
168
|
+
phoneNumber: user.phoneNumber,
|
|
169
|
+
isAnonymous: user.isAnonymous,
|
|
170
|
+
tenantId: user.tenantId,
|
|
171
|
+
providerId: user.providerId,
|
|
172
|
+
metadata: {
|
|
173
|
+
creationTime: user.metadata.creationTime ? new Date(user.metadata.creationTime).getTime() : undefined,
|
|
174
|
+
lastSignInTime: user.metadata.lastSignInTime ? new Date(user.metadata.lastSignInTime).getTime() : undefined,
|
|
175
|
+
},
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
onAuthStateChanged(
|
|
180
|
+
callback: (user: AuthUser | null) => void,
|
|
181
|
+
onError?: (error: Error) => void
|
|
182
|
+
): () => void {
|
|
183
|
+
return this.auth.onAuthStateChanged(
|
|
184
|
+
(user) => {
|
|
185
|
+
callback(
|
|
186
|
+
user
|
|
187
|
+
? {
|
|
188
|
+
uid: user.uid,
|
|
189
|
+
email: user.email,
|
|
190
|
+
emailVerified: user.emailVerified,
|
|
191
|
+
displayName: user.displayName,
|
|
192
|
+
photoURL: user.photoURL,
|
|
193
|
+
phoneNumber: user.phoneNumber,
|
|
194
|
+
isAnonymous: user.isAnonymous,
|
|
195
|
+
tenantId: user.tenantId,
|
|
196
|
+
providerId: user.providerId,
|
|
197
|
+
metadata: {
|
|
198
|
+
creationTime: user.metadata.creationTime ? new Date(user.metadata.creationTime).getTime() : undefined,
|
|
199
|
+
lastSignInTime: user.metadata.lastSignInTime ? new Date(user.metadata.lastSignInTime).getTime() : undefined,
|
|
200
|
+
},
|
|
201
|
+
}
|
|
202
|
+
: null
|
|
203
|
+
)
|
|
204
|
+
},
|
|
205
|
+
(error) => {
|
|
206
|
+
onError?.(error as Error)
|
|
207
|
+
}
|
|
208
|
+
)
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Handle Firebase Auth errors
|
|
213
|
+
*/
|
|
214
|
+
private handleAuthError(error: unknown): Error {
|
|
215
|
+
if (error instanceof Error && 'code' in error) {
|
|
216
|
+
const code = (error as { code: string }).code
|
|
217
|
+
|
|
218
|
+
switch (code) {
|
|
219
|
+
case 'auth/user-not-found':
|
|
220
|
+
case 'auth/wrong-password':
|
|
221
|
+
case 'auth/invalid-credential':
|
|
222
|
+
return new Error('Invalid credentials')
|
|
223
|
+
case 'auth/email-already-in-use':
|
|
224
|
+
return new Error('Email already in use')
|
|
225
|
+
case 'auth/weak-password':
|
|
226
|
+
return new Error('Password is too weak')
|
|
227
|
+
case 'auth/invalid-email':
|
|
228
|
+
return new Error('Invalid email')
|
|
229
|
+
case 'auth/user-disabled':
|
|
230
|
+
return new Error('Account disabled')
|
|
231
|
+
case 'auth/too-many-requests':
|
|
232
|
+
return new Error('Too many requests')
|
|
233
|
+
case 'auth/popup-closed-by-user':
|
|
234
|
+
return new Error('Sign in cancelled')
|
|
235
|
+
default:
|
|
236
|
+
return new Error(`Auth error: ${code}`)
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return new Error('Unknown auth error')
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Export class and singleton instance
|
|
245
|
+
export const authService = new AuthService()
|