@umituz/react-native-auth 3.2.14 → 3.2.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-auth",
3
- "version": "3.2.14",
3
+ "version": "3.2.15",
4
4
  "description": "Authentication service for React Native apps - Secure, type-safe, and production-ready. Provider-agnostic design with dependency injection, configurable validation, and comprehensive error handling.",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
package/src/index.ts CHANGED
@@ -87,6 +87,17 @@ export {
87
87
  resetAuthService,
88
88
  } from './infrastructure/services/AuthService';
89
89
 
90
+ export {
91
+ ensureUserDocument,
92
+ markUserDeleted,
93
+ configureUserDocumentService,
94
+ } from './infrastructure/services/UserDocumentService';
95
+
96
+ export type {
97
+ UserDocumentConfig,
98
+ UserDocumentExtras,
99
+ } from './infrastructure/services/UserDocumentService';
100
+
90
101
  // =============================================================================
91
102
  // INFRASTRUCTURE LAYER - Validation
92
103
  // =============================================================================
@@ -0,0 +1,221 @@
1
+ /**
2
+ * User Document Service
3
+ * Generic service for creating/updating user documents in Firestore
4
+ * Called automatically on auth state changes
5
+ */
6
+
7
+ import { doc, getDoc, setDoc, serverTimestamp } from "firebase/firestore";
8
+ import { getFirestore } from "@umituz/react-native-firebase";
9
+ import type { AuthUser } from "../../domain/entities/AuthUser";
10
+
11
+ declare const __DEV__: boolean;
12
+
13
+ /**
14
+ * Configuration for user document service
15
+ */
16
+ export interface UserDocumentConfig {
17
+ /** Firestore collection name (default: "users") */
18
+ collectionName?: string;
19
+ /** Additional fields to store with user document */
20
+ extraFields?: Record<string, unknown>;
21
+ /** Callback to collect device/app info */
22
+ collectExtras?: () => Promise<Record<string, unknown>>;
23
+ }
24
+
25
+ /**
26
+ * User document extras from device/app
27
+ */
28
+ export interface UserDocumentExtras {
29
+ deviceId?: string;
30
+ platform?: string;
31
+ deviceModel?: string;
32
+ deviceBrand?: string;
33
+ osVersion?: string;
34
+ appVersion?: string;
35
+ buildNumber?: string;
36
+ locale?: string;
37
+ timezone?: string;
38
+ previousAnonymousUserId?: string;
39
+ signUpMethod?: string;
40
+ }
41
+
42
+ let userDocumentConfig: UserDocumentConfig = {};
43
+
44
+ /**
45
+ * Configure user document service
46
+ */
47
+ export function configureUserDocumentService(config: UserDocumentConfig): void {
48
+ userDocumentConfig = { ...config };
49
+ }
50
+
51
+ /**
52
+ * Get sign-up method from auth user
53
+ */
54
+ function getSignUpMethod(user: AuthUser): string | undefined {
55
+ if (user.isAnonymous) return "anonymous";
56
+ if (user.email) {
57
+ const providerData = (
58
+ user as unknown as { providerData?: { providerId: string }[] }
59
+ ).providerData;
60
+ if (providerData && providerData.length > 0) {
61
+ const providerId = providerData[0].providerId;
62
+ if (providerId === "google.com") return "google";
63
+ if (providerId === "apple.com") return "apple";
64
+ if (providerId === "password") return "email";
65
+ }
66
+ return "email";
67
+ }
68
+ return undefined;
69
+ }
70
+
71
+ /**
72
+ * Build base user data from auth user
73
+ */
74
+ function buildBaseData(
75
+ user: AuthUser,
76
+ extras?: UserDocumentExtras,
77
+ ): Record<string, unknown> {
78
+ const data: Record<string, unknown> = {
79
+ displayName: user.displayName,
80
+ email: user.email,
81
+ photoURL: user.photoURL,
82
+ isAnonymous: user.isAnonymous,
83
+ };
84
+
85
+ if (extras?.deviceId) data.deviceId = extras.deviceId;
86
+ if (extras?.platform) data.platform = extras.platform;
87
+ if (extras?.deviceModel) data.deviceModel = extras.deviceModel;
88
+ if (extras?.deviceBrand) data.deviceBrand = extras.deviceBrand;
89
+ if (extras?.osVersion) data.osVersion = extras.osVersion;
90
+ if (extras?.appVersion) data.appVersion = extras.appVersion;
91
+ if (extras?.buildNumber) data.buildNumber = extras.buildNumber;
92
+ if (extras?.locale) data.locale = extras.locale;
93
+ if (extras?.timezone) data.timezone = extras.timezone;
94
+
95
+ return data;
96
+ }
97
+
98
+ /**
99
+ * Build create data for new user document
100
+ */
101
+ function buildCreateData(
102
+ baseData: Record<string, unknown>,
103
+ extras?: UserDocumentExtras,
104
+ ): Record<string, unknown> {
105
+ const createData: Record<string, unknown> = {
106
+ ...baseData,
107
+ ...userDocumentConfig.extraFields,
108
+ createdAt: serverTimestamp(),
109
+ updatedAt: serverTimestamp(),
110
+ lastLoginAt: serverTimestamp(),
111
+ };
112
+
113
+ if (extras?.previousAnonymousUserId) {
114
+ createData.previousAnonymousUserId = extras.previousAnonymousUserId;
115
+ createData.convertedFromAnonymous = true;
116
+ createData.convertedAt = serverTimestamp();
117
+ }
118
+
119
+ if (extras?.signUpMethod) {
120
+ createData.signUpMethod = extras.signUpMethod;
121
+ }
122
+
123
+ return createData;
124
+ }
125
+
126
+ /**
127
+ * Build update data for existing user document
128
+ */
129
+ function buildUpdateData(
130
+ baseData: Record<string, unknown>,
131
+ extras?: UserDocumentExtras,
132
+ ): Record<string, unknown> {
133
+ const updateData: Record<string, unknown> = {
134
+ ...baseData,
135
+ lastLoginAt: serverTimestamp(),
136
+ updatedAt: serverTimestamp(),
137
+ };
138
+
139
+ if (extras?.previousAnonymousUserId) {
140
+ updateData.previousAnonymousUserId = extras.previousAnonymousUserId;
141
+ updateData.convertedFromAnonymous = true;
142
+ updateData.convertedAt = serverTimestamp();
143
+ if (extras?.signUpMethod) {
144
+ updateData.signUpMethod = extras.signUpMethod;
145
+ }
146
+ }
147
+
148
+ return updateData;
149
+ }
150
+
151
+ /**
152
+ * Ensure user document exists in Firestore
153
+ * Creates new document or updates existing one
154
+ */
155
+ export async function ensureUserDocument(
156
+ user: AuthUser,
157
+ extras?: UserDocumentExtras,
158
+ ): Promise<boolean> {
159
+ const db = getFirestore();
160
+ if (!db || !user.uid) return false;
161
+
162
+ try {
163
+ // Collect additional extras if configured
164
+ let allExtras = extras || {};
165
+ if (userDocumentConfig.collectExtras) {
166
+ const collectedExtras = await userDocumentConfig.collectExtras();
167
+ allExtras = { ...collectedExtras, ...allExtras };
168
+ }
169
+
170
+ // Add sign-up method if not provided
171
+ if (!allExtras.signUpMethod) {
172
+ allExtras.signUpMethod = getSignUpMethod(user);
173
+ }
174
+
175
+ const collectionName = userDocumentConfig.collectionName || "users";
176
+ const userRef = doc(db, collectionName, user.uid);
177
+ const userDoc = await getDoc(userRef);
178
+ const baseData = buildBaseData(user, allExtras as UserDocumentExtras);
179
+
180
+ if (!userDoc.exists()) {
181
+ const createData = buildCreateData(baseData, allExtras as UserDocumentExtras);
182
+ await setDoc(userRef, createData);
183
+ } else {
184
+ const updateData = buildUpdateData(baseData, allExtras as UserDocumentExtras);
185
+ await setDoc(userRef, updateData, { merge: true });
186
+ }
187
+
188
+ return true;
189
+ } catch (error) {
190
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
191
+ // eslint-disable-next-line no-console
192
+ console.error("[UserDocumentService] Failed:", error);
193
+ }
194
+ return false;
195
+ }
196
+ }
197
+
198
+ /**
199
+ * Mark user as deleted (soft delete)
200
+ */
201
+ export async function markUserDeleted(userId: string): Promise<boolean> {
202
+ const db = getFirestore();
203
+ if (!db || !userId) return false;
204
+
205
+ try {
206
+ const collectionName = userDocumentConfig.collectionName || "users";
207
+ const userRef = doc(db, collectionName, userId);
208
+ await setDoc(
209
+ userRef,
210
+ {
211
+ isDeleted: true,
212
+ deletedAt: serverTimestamp(),
213
+ updatedAt: serverTimestamp(),
214
+ },
215
+ { merge: true },
216
+ );
217
+ return true;
218
+ } catch {
219
+ return false;
220
+ }
221
+ }