@umituz/react-native-auth 3.2.14 → 3.2.16

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.16",
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,18 @@ 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
+ UserDocumentUser,
100
+ } from './infrastructure/services/UserDocumentService';
101
+
90
102
  // =============================================================================
91
103
  // INFRASTRUCTURE LAYER - Validation
92
104
  // =============================================================================
@@ -0,0 +1,233 @@
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 type { User } from "firebase/auth";
9
+ import { getFirestore } from "@umituz/react-native-firebase";
10
+
11
+ declare const __DEV__: boolean;
12
+
13
+ /**
14
+ * Minimal user interface for document creation
15
+ * Compatible with both Firebase User and AuthUser
16
+ */
17
+ export interface UserDocumentUser {
18
+ uid: string;
19
+ displayName?: string | null;
20
+ email?: string | null;
21
+ photoURL?: string | null;
22
+ isAnonymous?: boolean;
23
+ }
24
+
25
+ /**
26
+ * Configuration for user document service
27
+ */
28
+ export interface UserDocumentConfig {
29
+ /** Firestore collection name (default: "users") */
30
+ collectionName?: string;
31
+ /** Additional fields to store with user document */
32
+ extraFields?: Record<string, unknown>;
33
+ /** Callback to collect device/app info */
34
+ collectExtras?: () => Promise<Record<string, unknown>>;
35
+ }
36
+
37
+ /**
38
+ * User document extras from device/app
39
+ */
40
+ export interface UserDocumentExtras {
41
+ deviceId?: string;
42
+ platform?: string;
43
+ deviceModel?: string;
44
+ deviceBrand?: string;
45
+ osVersion?: string;
46
+ appVersion?: string;
47
+ buildNumber?: string;
48
+ locale?: string;
49
+ timezone?: string;
50
+ previousAnonymousUserId?: string;
51
+ signUpMethod?: string;
52
+ }
53
+
54
+ let userDocumentConfig: UserDocumentConfig = {};
55
+
56
+ /**
57
+ * Configure user document service
58
+ */
59
+ export function configureUserDocumentService(config: UserDocumentConfig): void {
60
+ userDocumentConfig = { ...config };
61
+ }
62
+
63
+ /**
64
+ * Get sign-up method from auth user
65
+ */
66
+ function getSignUpMethod(user: UserDocumentUser): string | undefined {
67
+ if (user.isAnonymous) return "anonymous";
68
+ if (user.email) {
69
+ const providerData = (
70
+ user as unknown as { providerData?: { providerId: string }[] }
71
+ ).providerData;
72
+ if (providerData && providerData.length > 0) {
73
+ const providerId = providerData[0].providerId;
74
+ if (providerId === "google.com") return "google";
75
+ if (providerId === "apple.com") return "apple";
76
+ if (providerId === "password") return "email";
77
+ }
78
+ return "email";
79
+ }
80
+ return undefined;
81
+ }
82
+
83
+ /**
84
+ * Build base user data from auth user
85
+ */
86
+ function buildBaseData(
87
+ user: UserDocumentUser,
88
+ extras?: UserDocumentExtras,
89
+ ): Record<string, unknown> {
90
+ const data: Record<string, unknown> = {
91
+ displayName: user.displayName,
92
+ email: user.email,
93
+ photoURL: user.photoURL,
94
+ isAnonymous: user.isAnonymous,
95
+ };
96
+
97
+ if (extras?.deviceId) data.deviceId = extras.deviceId;
98
+ if (extras?.platform) data.platform = extras.platform;
99
+ if (extras?.deviceModel) data.deviceModel = extras.deviceModel;
100
+ if (extras?.deviceBrand) data.deviceBrand = extras.deviceBrand;
101
+ if (extras?.osVersion) data.osVersion = extras.osVersion;
102
+ if (extras?.appVersion) data.appVersion = extras.appVersion;
103
+ if (extras?.buildNumber) data.buildNumber = extras.buildNumber;
104
+ if (extras?.locale) data.locale = extras.locale;
105
+ if (extras?.timezone) data.timezone = extras.timezone;
106
+
107
+ return data;
108
+ }
109
+
110
+ /**
111
+ * Build create data for new user document
112
+ */
113
+ function buildCreateData(
114
+ baseData: Record<string, unknown>,
115
+ extras?: UserDocumentExtras,
116
+ ): Record<string, unknown> {
117
+ const createData: Record<string, unknown> = {
118
+ ...baseData,
119
+ ...userDocumentConfig.extraFields,
120
+ createdAt: serverTimestamp(),
121
+ updatedAt: serverTimestamp(),
122
+ lastLoginAt: serverTimestamp(),
123
+ };
124
+
125
+ if (extras?.previousAnonymousUserId) {
126
+ createData.previousAnonymousUserId = extras.previousAnonymousUserId;
127
+ createData.convertedFromAnonymous = true;
128
+ createData.convertedAt = serverTimestamp();
129
+ }
130
+
131
+ if (extras?.signUpMethod) {
132
+ createData.signUpMethod = extras.signUpMethod;
133
+ }
134
+
135
+ return createData;
136
+ }
137
+
138
+ /**
139
+ * Build update data for existing user document
140
+ */
141
+ function buildUpdateData(
142
+ baseData: Record<string, unknown>,
143
+ extras?: UserDocumentExtras,
144
+ ): Record<string, unknown> {
145
+ const updateData: Record<string, unknown> = {
146
+ ...baseData,
147
+ lastLoginAt: serverTimestamp(),
148
+ updatedAt: serverTimestamp(),
149
+ };
150
+
151
+ if (extras?.previousAnonymousUserId) {
152
+ updateData.previousAnonymousUserId = extras.previousAnonymousUserId;
153
+ updateData.convertedFromAnonymous = true;
154
+ updateData.convertedAt = serverTimestamp();
155
+ if (extras?.signUpMethod) {
156
+ updateData.signUpMethod = extras.signUpMethod;
157
+ }
158
+ }
159
+
160
+ return updateData;
161
+ }
162
+
163
+ /**
164
+ * Ensure user document exists in Firestore
165
+ * Creates new document or updates existing one
166
+ */
167
+ export async function ensureUserDocument(
168
+ user: UserDocumentUser | User,
169
+ extras?: UserDocumentExtras,
170
+ ): Promise<boolean> {
171
+ const db = getFirestore();
172
+ if (!db || !user.uid) return false;
173
+
174
+ try {
175
+ // Collect additional extras if configured
176
+ let allExtras = extras || {};
177
+ if (userDocumentConfig.collectExtras) {
178
+ const collectedExtras = await userDocumentConfig.collectExtras();
179
+ allExtras = { ...collectedExtras, ...allExtras };
180
+ }
181
+
182
+ // Add sign-up method if not provided
183
+ if (!allExtras.signUpMethod) {
184
+ allExtras.signUpMethod = getSignUpMethod(user);
185
+ }
186
+
187
+ const collectionName = userDocumentConfig.collectionName || "users";
188
+ const userRef = doc(db, collectionName, user.uid);
189
+ const userDoc = await getDoc(userRef);
190
+ const baseData = buildBaseData(user, allExtras as UserDocumentExtras);
191
+
192
+ if (!userDoc.exists()) {
193
+ const createData = buildCreateData(baseData, allExtras as UserDocumentExtras);
194
+ await setDoc(userRef, createData);
195
+ } else {
196
+ const updateData = buildUpdateData(baseData, allExtras as UserDocumentExtras);
197
+ await setDoc(userRef, updateData, { merge: true });
198
+ }
199
+
200
+ return true;
201
+ } catch (error) {
202
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
203
+ // eslint-disable-next-line no-console
204
+ console.error("[UserDocumentService] Failed:", error);
205
+ }
206
+ return false;
207
+ }
208
+ }
209
+
210
+ /**
211
+ * Mark user as deleted (soft delete)
212
+ */
213
+ export async function markUserDeleted(userId: string): Promise<boolean> {
214
+ const db = getFirestore();
215
+ if (!db || !userId) return false;
216
+
217
+ try {
218
+ const collectionName = userDocumentConfig.collectionName || "users";
219
+ const userRef = doc(db, collectionName, userId);
220
+ await setDoc(
221
+ userRef,
222
+ {
223
+ isDeleted: true,
224
+ deletedAt: serverTimestamp(),
225
+ updatedAt: serverTimestamp(),
226
+ },
227
+ { merge: true },
228
+ );
229
+ return true;
230
+ } catch {
231
+ return false;
232
+ }
233
+ }