@umituz/react-native-auth 3.4.29 → 3.4.31

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.
@@ -0,0 +1,513 @@
1
+ # Application Layer
2
+
3
+ React Native Auth package application layer. Defines ports and interfaces for authentication operations.
4
+
5
+ ## Structure
6
+
7
+ ```
8
+ application/
9
+ └── ports/
10
+ ├── IAuthService.ts # Authentication service interface
11
+ └── IAuthProvider.ts # Auth provider interface
12
+ ```
13
+
14
+ ## Overview
15
+
16
+ The application layer defines the contracts (interfaces) for authentication operations. It follows the Hexagonal Architecture (Ports and Adapters) pattern:
17
+
18
+ - **Ports**: Interfaces that define what the application can do
19
+ - **Adapters**: Implementations that connect to external services (Firebase, etc.)
20
+
21
+ ---
22
+
23
+ # IAuthService
24
+
25
+ Authentication service interface defining all auth operations.
26
+
27
+ ## Interface
28
+
29
+ ```typescript
30
+ import type { IAuthService, SignUpParams, SignInParams } from '@umituz/react-native-auth';
31
+
32
+ interface IAuthService {
33
+ // Sign up new user
34
+ signUp(params: SignUpParams): Promise<AuthUser>;
35
+
36
+ // Sign in existing user
37
+ signIn(params: SignInParams): Promise<AuthUser>;
38
+
39
+ // Sign out current user
40
+ signOut(): Promise<void>;
41
+
42
+ // Get current user
43
+ getCurrentUser(): AuthUser | null;
44
+ }
45
+
46
+ interface SignUpParams {
47
+ email: string;
48
+ password: string;
49
+ displayName?: string;
50
+ }
51
+
52
+ interface SignInParams {
53
+ email: string;
54
+ password: string;
55
+ }
56
+ ```
57
+
58
+ ## Usage Example
59
+
60
+ ### Implementing IAuthService
61
+
62
+ ```typescript
63
+ import { IAuthService, SignUpParams, SignInParams } from '@umituz/react-native-auth';
64
+
65
+ class FirebaseAuthService implements IAuthService {
66
+ constructor(private firebaseAuth: Auth) {}
67
+
68
+ async signUp(params: SignUpParams): Promise<AuthUser> {
69
+ const userCredential = await createUserWithEmailAndPassword(
70
+ this.firebaseAuth,
71
+ params.email,
72
+ params.password
73
+ );
74
+
75
+ // Update profile if displayName provided
76
+ if (params.displayName) {
77
+ await updateProfile(userCredential.user, {
78
+ displayName: params.displayName,
79
+ });
80
+ }
81
+
82
+ return this.mapToAuthUser(userCredential.user);
83
+ }
84
+
85
+ async signIn(params: SignInParams): Promise<AuthUser> {
86
+ const userCredential = await signInWithEmailAndPassword(
87
+ this.firebaseAuth,
88
+ params.email,
89
+ params.password
90
+ );
91
+
92
+ return this.mapToAuthUser(userCredential.user);
93
+ }
94
+
95
+ async signOut(): Promise<void> {
96
+ await signOut(this.firebaseAuth);
97
+ }
98
+
99
+ getCurrentUser(): AuthUser | null {
100
+ const user = this.firebaseAuth.currentUser;
101
+ return user ? this.mapToAuthUser(user) : null;
102
+ }
103
+
104
+ private mapToAuthUser(firebaseUser: FirebaseUser): AuthUser {
105
+ return {
106
+ uid: firebaseUser.uid,
107
+ email: firebaseUser.email,
108
+ displayName: firebaseUser.displayName,
109
+ isAnonymous: firebaseUser.isAnonymous,
110
+ emailVerified: firebaseUser.emailVerified,
111
+ photoURL: firebaseUser.photoURL,
112
+ provider: firebaseUser.providerData[0]?.providerId || 'unknown',
113
+ };
114
+ }
115
+ }
116
+ ```
117
+
118
+ ### Using IAuthService
119
+
120
+ ```typescript
121
+ function LoginComponent({ authService }: { authService: IAuthService }) {
122
+ const [email, setEmail] = useState('');
123
+ const [password, setPassword] = useState('');
124
+
125
+ const handleSignIn = async () => {
126
+ try {
127
+ const user = await authService.signIn({ email, password });
128
+ console.log('Signed in:', user.displayName);
129
+ } catch (error) {
130
+ console.error('Sign in failed:', error);
131
+ }
132
+ };
133
+
134
+ return (
135
+ <View>
136
+ <TextInput value={email} onChangeText={setEmail} placeholder="Email" />
137
+ <TextInput
138
+ value={password}
139
+ onChangeText={setPassword}
140
+ placeholder="Password"
141
+ secureTextEntry
142
+ />
143
+ <Button onPress={handleSignIn}>Sign In</Button>
144
+ </View>
145
+ );
146
+ }
147
+ ```
148
+
149
+ ---
150
+
151
+ # IAuthProvider
152
+
153
+ Auth provider interface for different authentication methods (Firebase, custom backend, etc.).
154
+
155
+ ## Interface
156
+
157
+ ```typescript
158
+ import type { IAuthProvider, AuthCredentials, SignUpCredentials, SocialSignInResult } from '@umituz/react-native-auth';
159
+
160
+ interface IAuthProvider {
161
+ // Sign up with email/password
162
+ signUp(credentials: SignUpCredentials): Promise<AuthUser>;
163
+
164
+ // Sign in with email/password
165
+ signIn(credentials: AuthCredentials): Promise<AuthUser>;
166
+
167
+ // Sign in with social provider
168
+ signInWithSocial(provider: 'google' | 'apple'): Promise<SocialSignInResult>;
169
+
170
+ // Sign out
171
+ signOut(): Promise<void>;
172
+
173
+ // Get current user
174
+ getCurrentUser(): AuthUser | null;
175
+ }
176
+
177
+ interface AuthCredentials {
178
+ email: string;
179
+ password: string;
180
+ }
181
+
182
+ interface SignUpCredentials extends AuthCredentials {
183
+ displayName?: string;
184
+ }
185
+
186
+ interface SocialSignInResult {
187
+ success: boolean;
188
+ user?: AuthUser;
189
+ error?: string;
190
+ }
191
+ ```
192
+
193
+ ## Usage Example
194
+
195
+ ### Firebase Provider Implementation
196
+
197
+ ```typescript
198
+ import { IAuthProvider } from '@umituz/react-native-auth';
199
+ import { GoogleAuthProvider, signInWithPopup } from 'firebase/auth';
200
+
201
+ class FirebaseAuthProvider implements IAuthProvider {
202
+ constructor(private firebaseAuth: Auth) {}
203
+
204
+ async signUp(credentials: SignUpCredentials): Promise<AuthUser> {
205
+ const userCredential = await createUserWithEmailAndPassword(
206
+ this.firebaseAuth,
207
+ credentials.email,
208
+ credentials.password
209
+ );
210
+
211
+ if (credentials.displayName) {
212
+ await updateProfile(userCredential.user, {
213
+ displayName: credentials.displayName,
214
+ });
215
+ }
216
+
217
+ return this.mapUser(userCredential.user);
218
+ }
219
+
220
+ async signIn(credentials: AuthCredentials): Promise<AuthUser> {
221
+ const userCredential = await signInWithEmailAndPassword(
222
+ this.firebaseAuth,
223
+ credentials.email,
224
+ credentials.password
225
+ );
226
+
227
+ return this.mapUser(userCredential.user);
228
+ }
229
+
230
+ async signInWithSocial(provider: 'google' | 'apple'): Promise<SocialSignInResult> {
231
+ try {
232
+ let authProvider: GoogleAuthProvider | OAuthProvider;
233
+
234
+ if (provider === 'google') {
235
+ authProvider = new GoogleAuthProvider();
236
+ } else {
237
+ authProvider = new OAuthProvider('apple.com');
238
+ }
239
+
240
+ const result = await signInWithPopup(this.firebaseAuth, authProvider);
241
+ const user = this.mapUser(result.user);
242
+
243
+ return { success: true, user };
244
+ } catch (error) {
245
+ return {
246
+ success: false,
247
+ error: error instanceof Error ? error.message : 'Social sign in failed',
248
+ };
249
+ }
250
+ }
251
+
252
+ async signOut(): Promise<void> {
253
+ await signOut(this.firebaseAuth);
254
+ }
255
+
256
+ getCurrentUser(): AuthUser | null {
257
+ const user = this.firebaseAuth.currentUser;
258
+ return user ? this.mapUser(user) : null;
259
+ }
260
+
261
+ private mapUser(firebaseUser: FirebaseUser): AuthUser {
262
+ return {
263
+ uid: firebaseUser.uid,
264
+ email: firebaseUser.email,
265
+ displayName: firebaseUser.displayName,
266
+ isAnonymous: firebaseUser.isAnonymous,
267
+ emailVerified: firebaseUser.emailVerified,
268
+ photoURL: firebaseUser.photoURL,
269
+ provider: firebaseUser.providerData[0]?.providerId || 'unknown',
270
+ };
271
+ }
272
+ }
273
+ ```
274
+
275
+ ### Custom Backend Provider
276
+
277
+ ```typescript
278
+ import { IAuthProvider } from '@umituz/react-native-auth';
279
+
280
+ class BackendAuthProvider implements IAuthProvider {
281
+ constructor(private apiBaseUrl: string) {}
282
+
283
+ async signUp(credentials: SignUpCredentials): Promise<AuthUser> {
284
+ const response = await fetch(`${this.apiBaseUrl}/auth/signup`, {
285
+ method: 'POST',
286
+ headers: { 'Content-Type': 'application/json' },
287
+ body: JSON.stringify(credentials),
288
+ });
289
+
290
+ if (!response.ok) {
291
+ throw new Error('Sign up failed');
292
+ }
293
+
294
+ const data = await response.json();
295
+ return data.user; // Assuming API returns { user: AuthUser }
296
+ }
297
+
298
+ async signIn(credentials: AuthCredentials): Promise<AuthUser> {
299
+ const response = await fetch(`${this.apiBaseUrl}/auth/signin`, {
300
+ method: 'POST',
301
+ headers: { 'Content-Type': 'application/json' },
302
+ body: JSON.stringify(credentials),
303
+ });
304
+
305
+ if (!response.ok) {
306
+ throw new Error('Sign in failed');
307
+ }
308
+
309
+ const data = await response.json();
310
+ return data.user;
311
+ }
312
+
313
+ async signInWithSocial(provider: 'google' | 'apple'): Promise<SocialSignInResult> {
314
+ // Implement social sign-in with your backend
315
+ const response = await fetch(`${this.apiBaseUrl}/auth/social/${provider}`, {
316
+ method: 'POST',
317
+ });
318
+
319
+ const data = await response.json();
320
+ return data;
321
+ }
322
+
323
+ async signOut(): Promise<void> {
324
+ await fetch(`${this.apiBaseUrl}/auth/signout`, { method: 'POST' });
325
+ }
326
+
327
+ getCurrentUser(): AuthUser | null {
328
+ // Return cached user or fetch from backend
329
+ return null;
330
+ }
331
+ }
332
+ ```
333
+
334
+ ---
335
+
336
+ # Dependency Injection
337
+
338
+ Using interfaces allows for easy dependency injection and testing.
339
+
340
+ ## Provider Pattern
341
+
342
+ ```typescript
343
+ import { IAuthProvider } from '@umituz/react-native-auth';
344
+
345
+ interface AuthProviderContextType {
346
+ authProvider: IAuthProvider;
347
+ }
348
+
349
+ const AuthProviderContext = createContext<AuthProviderContextType | null>(null);
350
+
351
+ export function AuthProvider({ children, provider }: { children: ReactNode; provider: IAuthProvider }) {
352
+ return (
353
+ <AuthProviderContext.Provider value={{ authProvider: provider }}>
354
+ {children}
355
+ </AuthProviderContext.Provider>
356
+ );
357
+ }
358
+
359
+ export function useAuthProvider(): IAuthProvider {
360
+ const context = useContext(AuthProviderContext);
361
+ if (!context) {
362
+ throw new Error('useAuthProvider must be used within AuthProvider');
363
+ }
364
+ return context.authProvider;
365
+ }
366
+ ```
367
+
368
+ ## App Configuration
369
+
370
+ ```typescript
371
+ import { FirebaseAuthProvider } from './providers/FirebaseAuthProvider';
372
+ import { BackendAuthProvider } from './providers/BackendAuthProvider';
373
+
374
+ // Choose provider based on environment
375
+ const authProvider = __DEV__
376
+ ? new BackendAuthProvider('https://dev-api.example.com')
377
+ : new FirebaseAuthProvider(getAuth());
378
+
379
+ function App() {
380
+ return (
381
+ <AuthProvider provider={authProvider}>
382
+ <AppNavigator />
383
+ </AuthProvider>
384
+ );
385
+ }
386
+ ```
387
+
388
+ ---
389
+
390
+ # Testing with Mocks
391
+
392
+ ## Mock Implementation
393
+
394
+ ```typescript
395
+ import { IAuthProvider } from '@umituz/react-native-auth';
396
+
397
+ class MockAuthProvider implements IAuthProvider {
398
+ private mockUser: AuthUser | null = null;
399
+
400
+ async signUp(credentials: SignUpCredentials): Promise<AuthUser> {
401
+ this.mockUser = {
402
+ uid: 'mock-123',
403
+ email: credentials.email,
404
+ displayName: credentials.displayName || 'Mock User',
405
+ isAnonymous: false,
406
+ emailVerified: false,
407
+ photoURL: null,
408
+ provider: 'password',
409
+ };
410
+ return this.mockUser;
411
+ }
412
+
413
+ async signIn(credentials: AuthCredentials): Promise<AuthUser> {
414
+ if (credentials.email === 'test@example.com' && credentials.password === 'password') {
415
+ this.mockUser = {
416
+ uid: 'mock-123',
417
+ email: credentials.email,
418
+ displayName: 'Test User',
419
+ isAnonymous: false,
420
+ emailVerified: true,
421
+ photoURL: null,
422
+ provider: 'password',
423
+ };
424
+ return this.mockUser;
425
+ }
426
+ throw new Error('Invalid credentials');
427
+ }
428
+
429
+ async signInWithSocial(provider: 'google' | 'apple'): Promise<SocialSignInResult> {
430
+ this.mockUser = {
431
+ uid: `mock-${provider}-123`,
432
+ email: `${provider}@example.com`,
433
+ displayName: `${provider} User`,
434
+ isAnonymous: false,
435
+ emailVerified: true,
436
+ photoURL: null,
437
+ provider: provider === 'google' ? 'google.com' : 'apple.com',
438
+ };
439
+ return { success: true, user: this.mockUser };
440
+ }
441
+
442
+ async signOut(): Promise<void> {
443
+ this.mockUser = null;
444
+ }
445
+
446
+ getCurrentUser(): AuthUser | null {
447
+ return this.mockUser;
448
+ }
449
+ }
450
+
451
+ // Usage in tests
452
+ const mockProvider = new MockAuthProvider();
453
+ render(
454
+ <AuthProvider provider={mockProvider}>
455
+ <LoginComponent />
456
+ </AuthProvider>
457
+ );
458
+ ```
459
+
460
+ ---
461
+
462
+ # Best Practices
463
+
464
+ ## 1. Always Use Interfaces
465
+
466
+ ```typescript
467
+ // ✅ Good
468
+ function authenticateUser(authProvider: IAuthProvider, credentials: AuthCredentials) {
469
+ return authProvider.signIn(credentials);
470
+ }
471
+
472
+ // ❌ Bad - couples to Firebase
473
+ function authenticateUser(auth: Auth, credentials: AuthCredentials) {
474
+ return signInWithEmailAndPassword(auth, credentials.email, credentials.password);
475
+ }
476
+ ```
477
+
478
+ ## 2. Dependency Injection
479
+
480
+ ```typescript
481
+ // ✅ Good - injectable
482
+ class UserService {
483
+ constructor(private authProvider: IAuthProvider) {}
484
+ }
485
+
486
+ // ❌ Bad - tight coupling
487
+ import { getAuth } from 'firebase/auth';
488
+ class UserService {
489
+ private auth = getAuth();
490
+ }
491
+ ```
492
+
493
+ ## 3. Error Handling
494
+
495
+ ```typescript
496
+ // ✅ Good - abstract errors
497
+ async function signUp(authProvider: IAuthProvider, params: SignUpParams) {
498
+ try {
499
+ return await authProvider.signUp(params);
500
+ } catch (error) {
501
+ if (error instanceof AuthEmailAlreadyInUseError) {
502
+ // Handle specific error
503
+ }
504
+ throw error;
505
+ }
506
+ }
507
+ ```
508
+
509
+ ## Related Modules
510
+
511
+ - **[Domain](../domain/README.md)** - Domain entities and value objects
512
+ - **[Infrastructure](../infrastructure/README.md)** - Infrastructure implementations
513
+ - **[Presentation](../presentation/README.md)** - UI components and hooks