react-firebase-ql 0.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.
package/dist/index.mjs ADDED
@@ -0,0 +1,1359 @@
1
+ // src/Users.ts
2
+ import {
3
+ setPersistence,
4
+ browserSessionPersistence,
5
+ signInWithEmailAndPassword,
6
+ signInWithPhoneNumber,
7
+ updatePassword,
8
+ sendPasswordResetEmail,
9
+ signOut,
10
+ getMultiFactorResolver,
11
+ multiFactor,
12
+ PhoneAuthProvider,
13
+ PhoneMultiFactorGenerator,
14
+ createUserWithEmailAndPassword,
15
+ sendEmailVerification,
16
+ applyActionCode,
17
+ verifyPasswordResetCode,
18
+ deleteUser,
19
+ browserLocalPersistence,
20
+ confirmPasswordReset
21
+ } from "firebase/auth";
22
+
23
+ // src/BaseModel.ts
24
+ import {
25
+ doc,
26
+ collection,
27
+ onSnapshot,
28
+ where,
29
+ orderBy,
30
+ getDoc,
31
+ startAfter,
32
+ limit,
33
+ query,
34
+ updateDoc,
35
+ getDocs,
36
+ addDoc,
37
+ setDoc,
38
+ deleteDoc,
39
+ increment,
40
+ getCountFromServer,
41
+ writeBatch,
42
+ or,
43
+ and,
44
+ arrayUnion,
45
+ arrayRemove
46
+ } from "firebase/firestore";
47
+
48
+ // src/helpers.ts
49
+ var generateRandomString = (length) => {
50
+ if (length > 60) {
51
+ throw new Error(`Maximum generatable character is 60, ${length} was required`);
52
+ }
53
+ var result = "";
54
+ var characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
55
+ var charactersLength = characters.length;
56
+ for (var i = 0; i < length; i++) {
57
+ result += characters.charAt(Math.floor(Math.random() * charactersLength));
58
+ }
59
+ return result;
60
+ };
61
+ var camelToTitleCase = (key) => {
62
+ const topic = key[0].toUpperCase() + key.slice(1);
63
+ let result = "";
64
+ let i = 0;
65
+ while (i < topic.length) {
66
+ if (i === 0) {
67
+ result += topic.charAt(i);
68
+ } else {
69
+ if (topic.charAt(i) === topic.charAt(i).toUpperCase()) {
70
+ result += ` ${topic.charAt(i)}`;
71
+ } else {
72
+ result += topic.charAt(i);
73
+ }
74
+ }
75
+ i++;
76
+ }
77
+ return result;
78
+ };
79
+ function convertUnicode(input) {
80
+ return input.replace(/\\+u([0-9a-fA-F]{4})/g, (a, b) => String.fromCharCode(parseInt(b, 16)));
81
+ }
82
+ var moneyFormatter = (x, shorten = true, decimailPlaces) => {
83
+ if (x === void 0) {
84
+ return "";
85
+ }
86
+ const base = 1e5;
87
+ const num = parseInt(x);
88
+ if (num < base || !shorten) {
89
+ return num.toFixed(decimailPlaces).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
90
+ } else {
91
+ let val = "";
92
+ if (num / base < 10) {
93
+ val = (num / 1e3).toFixed(decimailPlaces) + "K";
94
+ } else if (num < base * 1e4) {
95
+ val = (num / (base * 10)).toFixed(decimailPlaces) + "M";
96
+ } else {
97
+ val = (num / (base * 1e4)).toFixed(decimailPlaces) + "B";
98
+ }
99
+ return val;
100
+ }
101
+ };
102
+ function getDaysAgo(startDate, daysApart) {
103
+ const localCopy = new Date(startDate.toDateString());
104
+ return new Date(localCopy.setDate(localCopy.getDate() + daysApart));
105
+ }
106
+ function timeAgo(dateInput) {
107
+ const date = new Date(dateInput);
108
+ if (isNaN(date.getTime())) {
109
+ throw new Error("Invalid date input");
110
+ }
111
+ const now = /* @__PURE__ */ new Date();
112
+ const secondsAgo = Math.floor((now.getTime() - date.getTime()) / 1e3);
113
+ const minutesAgo = Math.floor(secondsAgo / 60);
114
+ if (minutesAgo < 1)
115
+ return "Just now";
116
+ const hoursAgo = Math.floor(minutesAgo / 60);
117
+ if (hoursAgo < 1)
118
+ return `${minutesAgo} minute${minutesAgo !== 1 ? "s" : ""} ago`;
119
+ const daysAgo = Math.floor(hoursAgo / 24);
120
+ if (daysAgo < 1)
121
+ return `${hoursAgo} hour${hoursAgo !== 1 ? "s" : ""} ago`;
122
+ const monthsAgo = Math.floor(daysAgo / 30);
123
+ if (daysAgo < 30)
124
+ return `${daysAgo} day${daysAgo !== 1 ? "s" : ""} ago`;
125
+ const yearsAgo = Math.floor(monthsAgo / 12);
126
+ if (monthsAgo < 12)
127
+ return `${monthsAgo} month${monthsAgo !== 1 ? "s" : ""} ago`;
128
+ return `${yearsAgo} year${yearsAgo !== 1 ? "s" : ""} ago`;
129
+ }
130
+ var range = (start, end) => Array.from({ length: end - start + 1 }, (_, i) => start + i);
131
+ var nairaFormatter = (amount) => {
132
+ return `\u20A6${moneyFormatter(amount, true, 2)}`;
133
+ };
134
+ var camelCaseToNormal = (camelCaseStr) => {
135
+ return camelCaseStr.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/^./, (str) => str.toUpperCase());
136
+ };
137
+ var errorLogger = (...error) => {
138
+ if (window.location.hostname === "localhost") {
139
+ console.log(error);
140
+ }
141
+ };
142
+ function isValidEmail(email) {
143
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
144
+ const pass = emailRegex.test(email);
145
+ if (!pass) {
146
+ return "Invalid email address";
147
+ }
148
+ }
149
+ function isValidPassword(password) {
150
+ if (password.length < 8) {
151
+ return "Password cannot be less than 8 characters in length";
152
+ }
153
+ const hasUppercase = /[A-Z]/.test(password);
154
+ if (!hasUppercase) {
155
+ return "Password must contain at least 1 uppercase letter";
156
+ }
157
+ const isAllUppercase = /^[A-Z]+$/.test(password);
158
+ if (isAllUppercase) {
159
+ return "Password cannot be all uppercase letter";
160
+ }
161
+ const hasSpecialCharacter = /[!@#$%^&*(),.?":{}|<>]/.test(password);
162
+ if (!hasSpecialCharacter) {
163
+ return "Password must contain at least 1 special character";
164
+ }
165
+ }
166
+ function numberToAlphabet(num) {
167
+ let result = "";
168
+ while (num > 0) {
169
+ num--;
170
+ const charCode = num % 26 + 65;
171
+ result = String.fromCharCode(charCode) + result;
172
+ num = Math.floor(num / 26);
173
+ }
174
+ return result;
175
+ }
176
+ var getNthNumberOfArray = (param) => {
177
+ const { num, step, start } = param;
178
+ if (num <= 0 || step <= 0) {
179
+ throw new Error("Both number and step must be greater than 0");
180
+ }
181
+ return Array.from({ length: Math.floor(num / step) }, (_, i) => {
182
+ if (start) {
183
+ i = start + i - 1;
184
+ return (i + 1) * step;
185
+ }
186
+ return (i + 1) * step;
187
+ });
188
+ };
189
+ var noEmptyField = (args) => {
190
+ return args.requiredFields.every(
191
+ (field) => Object.keys(FormData).includes(field) && args.formData[field] !== void 0 && args.formData[field] !== null && args.formData[field] !== ""
192
+ );
193
+ };
194
+ var getInitials = (fullName) => {
195
+ if (fullName) {
196
+ const names = fullName.trim().split(/\s+/);
197
+ if (names.length >= 2) {
198
+ const firstInitial = names[0].charAt(0).toUpperCase();
199
+ const lastInitial = names[names.length - 1].charAt(0).toUpperCase();
200
+ return `${firstInitial}${lastInitial}`;
201
+ }
202
+ if (names.length === 1) {
203
+ const name = names[0];
204
+ return `${name.charAt(0).toUpperCase()}${name.charAt(name.length - 1).toUpperCase()}`;
205
+ }
206
+ }
207
+ return "";
208
+ };
209
+ var fetchCurrentTimestamp = async () => {
210
+ const res = await fetch("https://worldtimeapi.org/api/timezone/etc/utc");
211
+ if (!res.ok)
212
+ throw new Error("Failed to fetch time");
213
+ const data = await res.json();
214
+ return {
215
+ iso: data.datetime,
216
+ unix: data.unixtime * 1e3
217
+ // Convert to milliseconds if needed
218
+ };
219
+ };
220
+ var formHasError = (formError) => {
221
+ return Object.values(formError).every((item) => item !== void 0);
222
+ };
223
+
224
+ // src/BaseModel.ts
225
+ var BaseModel = class {
226
+ // offset data
227
+ // offset?: QueryDocumentSnapshot<DocumentData>;
228
+ constructor(table, db) {
229
+ // Get a new write batch
230
+ // protected batch?: WriteBatch
231
+ // Database table name
232
+ this.table = "";
233
+ this.table = table;
234
+ this.firestorDB = db;
235
+ }
236
+ /**
237
+ * save multiple documents
238
+ * @param param0
239
+ */
240
+ async saveBatch({ data }) {
241
+ try {
242
+ const batch = writeBatch(this.firestorDB);
243
+ const obj = data;
244
+ obj.forEach((document) => {
245
+ const docRef = document.reference ? doc(this.firestorDB, this.table, document.reference) : doc(collection(this.firestorDB, this.table));
246
+ if (document.reference) {
247
+ delete document.reference;
248
+ }
249
+ batch.set(docRef, document);
250
+ });
251
+ await batch.commit();
252
+ return true;
253
+ } catch (error) {
254
+ throw new Error(`saveBatch: , ${error}`);
255
+ }
256
+ }
257
+ /**
258
+ * update multiple documents
259
+ * @param param0
260
+ */
261
+ async updateBatch({ data }) {
262
+ try {
263
+ const batch = writeBatch(this.firestorDB);
264
+ const obj = data;
265
+ obj.forEach((document) => {
266
+ const docRef = doc(this.firestorDB, this.table, document.reference);
267
+ delete document.reference;
268
+ batch.update(docRef, document);
269
+ });
270
+ await batch.commit();
271
+ return true;
272
+ } catch (error) {
273
+ throw new Error(`updateBatch: , ${error}`);
274
+ }
275
+ }
276
+ /**
277
+ * delete multiple documents
278
+ * @param param0
279
+ */
280
+ async deleteBatch({ ids }) {
281
+ try {
282
+ const batch = writeBatch(this.firestorDB);
283
+ ids.forEach((id) => {
284
+ const docRef = doc(this.firestorDB, this.table, id);
285
+ batch.delete(docRef);
286
+ });
287
+ await batch.commit();
288
+ return true;
289
+ } catch (error) {
290
+ throw new Error(`deleteBatch: , ${error}`);
291
+ }
292
+ }
293
+ /**
294
+ * Get realtime update from the database
295
+ * @param id
296
+ */
297
+ stream(callBack, id) {
298
+ const ref2 = id ? doc(this.firestorDB, this.table, id) : collection(this.firestorDB, this.table);
299
+ try {
300
+ if (id) {
301
+ return onSnapshot(ref2, (doc2) => {
302
+ callBack(doc2.exists() ? { ...doc2.data(), reference: ref2.id } : void 0);
303
+ });
304
+ } else {
305
+ return onSnapshot(ref2, (snapShot) => {
306
+ callBack(
307
+ snapShot.docs.map((value) => {
308
+ const data = { ...value.data(), reference: value.id };
309
+ return data;
310
+ })
311
+ );
312
+ });
313
+ }
314
+ } catch (error) {
315
+ callBack(void 0);
316
+ }
317
+ }
318
+ /**
319
+ * Get realtime value from database with where clause
320
+ * @param wh
321
+ * @param lim
322
+ * @param order
323
+ */
324
+ streamWhere(wh, callBack, lim, order, offset) {
325
+ try {
326
+ const whereParameter = wh.map((clause) => where(
327
+ clause.key,
328
+ clause.operator,
329
+ clause.value
330
+ ));
331
+ let constraint = [];
332
+ if (wh) {
333
+ constraint.push(...whereParameter);
334
+ }
335
+ if (order) {
336
+ constraint.push(orderBy(order.parameter, order.direction));
337
+ }
338
+ if (offset) {
339
+ getDoc(doc(this.firestorDB, this.table, offset)).then((off) => {
340
+ constraint.push(startAfter(off));
341
+ }).catch((error) => errorLogger(error));
342
+ }
343
+ if (lim) {
344
+ constraint.push(limit(lim));
345
+ }
346
+ const ref2 = collection(this.firestorDB, this.table);
347
+ return onSnapshot(query(
348
+ ref2,
349
+ ...constraint
350
+ ), (snapShot) => {
351
+ callBack(snapShot.docs.map((value) => {
352
+ const data = { ...value.data(), reference: value.id };
353
+ return data;
354
+ }));
355
+ });
356
+ } catch (error) {
357
+ throw new Error(`streamWhere: , ${error}`);
358
+ }
359
+ }
360
+ /**
361
+ * Fetch a single item from database
362
+ * @param id
363
+ */
364
+ async find(id) {
365
+ try {
366
+ const ref2 = doc(this.firestorDB, this.table, id);
367
+ const docSnap = await getDoc(ref2);
368
+ if (docSnap.exists()) {
369
+ this.data = { ...docSnap.data(), reference: id };
370
+ return true;
371
+ }
372
+ return false;
373
+ } catch (error) {
374
+ throw new Error(`find: , ${error}`);
375
+ }
376
+ }
377
+ /**
378
+ * check if data exists
379
+ * @param id
380
+ * @returns
381
+ */
382
+ async dataExists(id) {
383
+ try {
384
+ const ref2 = doc(this.firestorDB, this.table, id);
385
+ const docSanp = await getDoc(ref2);
386
+ return docSanp.exists();
387
+ } catch (error) {
388
+ throw new Error(`dataExists: , ${error}`);
389
+ }
390
+ }
391
+ /**
392
+ * Update part of a data
393
+ * @param data
394
+ * @param id
395
+ * @returns
396
+ */
397
+ async update(data, id) {
398
+ try {
399
+ delete data.reference;
400
+ const docRef = doc(this.firestorDB, this.table, id);
401
+ await updateDoc(docRef, data);
402
+ return true;
403
+ } catch (error) {
404
+ throw new Error(`update: , ${error}`);
405
+ }
406
+ }
407
+ /**
408
+ * update an array in a document
409
+ * @param {Array<any>} data array of data to be saved
410
+ * @param {string} reference document reference
411
+ * @param {string} key document object key containing the array
412
+ * @returns {boolean}
413
+ */
414
+ async updateAtomicArray(data, reference, key) {
415
+ try {
416
+ const docRef = doc(this.firestorDB, this.table, reference);
417
+ await updateDoc(docRef, { [key]: arrayUnion(...data) });
418
+ return true;
419
+ } catch (error) {
420
+ throw new Error(`updateAtomicArray error: ${error}`);
421
+ }
422
+ }
423
+ /**
424
+ * removes items from document array
425
+ * @param {Array<any>} data array of data to be removed
426
+ * @param {string} id document reference
427
+ * @param {string} key key to reference
428
+ * @returns {boolean}
429
+ */
430
+ async removeFromArray(data, id, key) {
431
+ try {
432
+ const docRef = doc(this.firestorDB, this.table, id);
433
+ await updateDoc(docRef, { [key]: arrayRemove(...data) });
434
+ return true;
435
+ } catch (error) {
436
+ throw new Error(`updateAtomicArray error: ${error}`);
437
+ }
438
+ }
439
+ /**
440
+ * Get all items from database
441
+ * @returns void
442
+ */
443
+ async findAll(ids) {
444
+ try {
445
+ const colRef = collection(this.firestorDB, this.table);
446
+ if (ids) {
447
+ const results = [];
448
+ for (let id of ids) {
449
+ const found = await this.find(id);
450
+ if (found) {
451
+ results.push(this.data);
452
+ }
453
+ }
454
+ this.data = results;
455
+ return true;
456
+ } else {
457
+ const snaptshots = await getDocs(colRef);
458
+ if (!snaptshots.empty) {
459
+ this.data = snaptshots.docs.map((document) => {
460
+ return { ...document.data(), reference: document.id };
461
+ });
462
+ return true;
463
+ } else {
464
+ return false;
465
+ }
466
+ }
467
+ } catch (error) {
468
+ throw new Error(`findAll: , ${error}`);
469
+ }
470
+ }
471
+ /**
472
+ * perform complex query
473
+ * @param param0
474
+ */
475
+ async findWhereOrAnd({ wh, lim, order, offset }) {
476
+ try {
477
+ const colRef = collection(this.firestorDB, this.table);
478
+ const andWhere = [];
479
+ const orWhere = [];
480
+ let filterConstraint = and();
481
+ if (wh) {
482
+ wh.parameter.forEach((clause) => {
483
+ const whe = where(
484
+ clause.key,
485
+ clause.operator,
486
+ clause.value
487
+ );
488
+ clause.type === "and" ? andWhere.push(whe) : orWhere.push(whe);
489
+ });
490
+ if (wh.type === "andOr") {
491
+ filterConstraint = and(...andWhere, or(...orWhere));
492
+ } else if (wh.type === "or") {
493
+ filterConstraint = or(...orWhere);
494
+ } else {
495
+ filterConstraint = and(...andWhere);
496
+ }
497
+ }
498
+ let constraint = [];
499
+ if (order) {
500
+ constraint.push(orderBy(order.parameter, order.direction));
501
+ }
502
+ if (offset) {
503
+ const off = await getDoc(doc(this.firestorDB, this.table, offset));
504
+ constraint.push(startAfter(off));
505
+ }
506
+ if (lim) {
507
+ constraint.push(limit(lim));
508
+ }
509
+ const snapshot = await getDocs(
510
+ query(
511
+ colRef,
512
+ filterConstraint,
513
+ ...constraint
514
+ )
515
+ );
516
+ if (!snapshot.empty) {
517
+ this.data = snapshot.docs.map((document) => {
518
+ return { ...document.data(), reference: document.id };
519
+ });
520
+ return true;
521
+ } else {
522
+ return false;
523
+ }
524
+ } catch (error) {
525
+ throw new Error(`findWhereOrAnd: ${error}`);
526
+ }
527
+ }
528
+ /**
529
+ * complex query with and only
530
+ * @param param0
531
+ * @returns
532
+ */
533
+ async findWhere({ wh, lim, order, offset }) {
534
+ try {
535
+ const whereParameter = wh ? wh.map((clause) => where(
536
+ clause.key,
537
+ clause.operator,
538
+ clause.value
539
+ )) : [];
540
+ let constraint = [];
541
+ if (wh) {
542
+ constraint.push(...whereParameter);
543
+ }
544
+ if (order) {
545
+ constraint.push(orderBy(order.parameter, order.direction));
546
+ }
547
+ if (offset) {
548
+ const off = await getDoc(doc(this.firestorDB, this.table, offset));
549
+ constraint.push(startAfter(off));
550
+ }
551
+ if (lim) {
552
+ constraint.push(limit(lim));
553
+ }
554
+ const ref2 = collection(this.firestorDB, this.table);
555
+ const snapshot = await getDocs(
556
+ query(
557
+ ref2,
558
+ ...constraint
559
+ )
560
+ );
561
+ if (!snapshot.empty) {
562
+ return snapshot.docs.map((document) => {
563
+ return { ...document.data(), reference: document.id };
564
+ });
565
+ } else {
566
+ return [];
567
+ }
568
+ } catch (error) {
569
+ throw new Error(`findWhere: , ${error}`);
570
+ }
571
+ }
572
+ /**
573
+ * create or update data
574
+ * @param data
575
+ */
576
+ async save(data, id) {
577
+ delete data.reference;
578
+ try {
579
+ if (id === void 0) {
580
+ const documentRef = await addDoc(collection(this.firestorDB, this.table), data);
581
+ return documentRef.id;
582
+ } else {
583
+ await setDoc(doc(this.firestorDB, this.table, id), data);
584
+ return id;
585
+ }
586
+ } catch (error) {
587
+ throw new Error(`save error: , ${error}`);
588
+ }
589
+ }
590
+ /**
591
+ * Delete document from database
592
+ * @param id
593
+ */
594
+ async delete(id) {
595
+ try {
596
+ await deleteDoc(doc(this.firestorDB, this.table, id));
597
+ return true;
598
+ } catch (error) {
599
+ throw new Error(`delete: , ${error}`);
600
+ }
601
+ }
602
+ /**
603
+ * Increment or decrement counters
604
+ * @param param0
605
+ */
606
+ async incrementDecrement({ dbReference, key, isIncrement = true, incrementalValue }) {
607
+ try {
608
+ const docRef = doc(this.firestorDB, this.table, dbReference);
609
+ const value = isIncrement ? incrementalValue != null ? incrementalValue : 1 : (incrementalValue != null ? incrementalValue : 1) * -1;
610
+ await updateDoc(docRef, { [key]: increment(value) });
611
+ return true;
612
+ } catch (error) {
613
+ throw new Error(`incrementDecrement: , ${error}`);
614
+ }
615
+ }
616
+ /**
617
+ * Count data in database
618
+ * @param {whereClause[]} wh - query parameter (e.g. [
619
+ * {
620
+ */
621
+ async countData(wh) {
622
+ try {
623
+ const qryParameter = wh.map((clause) => where(
624
+ clause.key,
625
+ clause.operator,
626
+ clause.value
627
+ ));
628
+ const qry = query(
629
+ collection(this.firestorDB, this.table),
630
+ ...qryParameter
631
+ );
632
+ const aggregate = await getCountFromServer(qry);
633
+ return aggregate.data().count;
634
+ } catch (error) {
635
+ throw new Error(`countData error: ,${error}`);
636
+ }
637
+ }
638
+ /**
639
+ * Get realtime value from database with where clause
640
+ * @param wh
641
+ * @param lim
642
+ * @param order
643
+ */
644
+ async streamCount(wh, callBack, order, offset) {
645
+ try {
646
+ const whereParameter = wh.map(
647
+ (clause) => where(clause.key, clause.operator, clause.value)
648
+ );
649
+ const constraint = [...whereParameter];
650
+ if (order)
651
+ constraint.push(orderBy(order.parameter, order.direction));
652
+ if (offset) {
653
+ const off = await getDoc(doc(this.firestorDB, this.table, offset));
654
+ constraint.push(startAfter(off));
655
+ }
656
+ const streamerConstraint = [...constraint, limit(1)];
657
+ const ref2 = collection(this.firestorDB, this.table);
658
+ const unsubscribe = onSnapshot(
659
+ query(ref2, ...streamerConstraint),
660
+ async (snapShot) => {
661
+ if (snapShot.empty)
662
+ return;
663
+ const aggregate = await getCountFromServer(query(
664
+ collection(this.firestorDB, this.table),
665
+ ...constraint
666
+ ));
667
+ callBack(aggregate.data().count);
668
+ }
669
+ );
670
+ return unsubscribe;
671
+ } catch (error) {
672
+ throw new Error(`streamCount: ${error}`);
673
+ }
674
+ }
675
+ };
676
+
677
+ // src/Users.ts
678
+ var Users = class extends BaseModel {
679
+ constructor() {
680
+ super(...arguments);
681
+ /**
682
+ * fetch phone number from database
683
+ * @param uid
684
+ * @returns
685
+ */
686
+ this.getPhoneNumber = async (uid) => {
687
+ let phoneNumber = null;
688
+ const dbUser = await this.find(uid);
689
+ if (dbUser) {
690
+ const dUser = this.data;
691
+ phoneNumber = dUser.phoneNumber;
692
+ }
693
+ return phoneNumber;
694
+ };
695
+ /**
696
+ * send OTP to enable 2 factor authentication
697
+ * @param user
698
+ * @param recaptchaverifier
699
+ */
700
+ this.setMultiFactorEnrollment = async (user, recaptchaverifier, auth, phoneNumber) => {
701
+ try {
702
+ const multifactorSession = await multiFactor(user).getSession();
703
+ const phoneInfoOptions = {
704
+ phoneNumber,
705
+ session: multifactorSession
706
+ };
707
+ const phoneAuthProvider = new PhoneAuthProvider(auth);
708
+ return await phoneAuthProvider.verifyPhoneNumber(phoneInfoOptions, recaptchaverifier);
709
+ } catch (e) {
710
+ throw new Error(`setMultiFactorEnrollment error: , ${e}`);
711
+ }
712
+ };
713
+ /**
714
+ * complete user signIn process with OTP
715
+ * @param param0
716
+ * @param userCode
717
+ * @param user
718
+ * @param onSuccess
719
+ * @returns {User | null}
720
+ */
721
+ this.confirmOTP = async (verifier, userCode) => {
722
+ try {
723
+ const { verificationId, resolver } = verifier;
724
+ const cred = PhoneAuthProvider.credential(verificationId, userCode);
725
+ const multiFactorAssertion = PhoneMultiFactorGenerator.assertion(cred);
726
+ let user = null;
727
+ if (resolver) {
728
+ const credential = await resolver.resolveSignIn(multiFactorAssertion);
729
+ user = credential.user;
730
+ } else {
731
+ if (verifier.user) {
732
+ await multiFactor(verifier.user).enroll(multiFactorAssertion, "primary phone");
733
+ user = verifier.user;
734
+ }
735
+ }
736
+ return user;
737
+ } catch (error) {
738
+ throw new Error(`confirmOTP error: , ${error}`);
739
+ }
740
+ };
741
+ /**
742
+ * send OTP to member
743
+ * @param resolver
744
+ * @param recaptchaVerifier
745
+ * @param setter
746
+ * @returns {MFAVerifier | null}
747
+ */
748
+ this.sendOTP = async (resolver, recaptchaVerifier, auth) => {
749
+ try {
750
+ if (resolver.hints[0].factorId === PhoneMultiFactorGenerator.FACTOR_ID) {
751
+ const phoneInfoOptions = {
752
+ multiFactorHint: resolver.hints[0],
753
+ session: resolver.session
754
+ };
755
+ const phoneAuthProvider = new PhoneAuthProvider(auth);
756
+ const verificationId = await phoneAuthProvider.verifyPhoneNumber(phoneInfoOptions, recaptchaVerifier);
757
+ return { verificationId, resolver };
758
+ } else {
759
+ return null;
760
+ }
761
+ } catch (e) {
762
+ throw new Error(`sendOTP error: , ${e}`);
763
+ }
764
+ };
765
+ /**
766
+ * Handle email verification for user
767
+ * @param auth
768
+ * @param actionCode
769
+ * @returns {error: string | null}
770
+ */
771
+ this.verifyEmail = async (auth, actionCode) => {
772
+ try {
773
+ await applyActionCode(auth, actionCode);
774
+ return true;
775
+ } catch (error) {
776
+ throw new Error(`verifyEmail error: , ${error}`);
777
+ }
778
+ };
779
+ /**
780
+ * confirm that password reset link is correct
781
+ * @param auth
782
+ * @param actionCode
783
+ * @returns {email: string | null}
784
+ */
785
+ this.verifyPasswordResetLink = async (auth, actionCode) => {
786
+ try {
787
+ return await verifyPasswordResetCode(auth, actionCode);
788
+ } catch (error) {
789
+ throw new Error(`passwordResetLink error: , ${error}`);
790
+ }
791
+ };
792
+ }
793
+ /**
794
+ * register user and send email verification
795
+ * delete user if registration is not successful
796
+ * @param param0
797
+ */
798
+ async registerWithEmailAndPassword({
799
+ auth,
800
+ email,
801
+ password,
802
+ userData,
803
+ persist = "session"
804
+ }) {
805
+ try {
806
+ if (persist) {
807
+ await setPersistence(auth, persist === "local" ? browserLocalPersistence : browserSessionPersistence);
808
+ }
809
+ const credential = await createUserWithEmailAndPassword(auth, email, password);
810
+ this.user = credential.user;
811
+ if (userData) {
812
+ await this.save(userData, credential.user.uid);
813
+ }
814
+ return credential.user.uid;
815
+ } catch (error) {
816
+ if (this.user) {
817
+ this.deleteAccount(this.user);
818
+ }
819
+ throw new Error(`registerWithEmailAndPassword error: , ${error}`);
820
+ }
821
+ }
822
+ /**
823
+ * confirm user is valid and get their information
824
+ * from firestore users table if exists
825
+ * @param { string , string, boolean} credentials - login credentials
826
+ * @returns {Promise<QueryReturn>}
827
+ */
828
+ async login({ email, password, auth, persist = "session", verifyEmail = false }) {
829
+ try {
830
+ if (persist) {
831
+ await setPersistence(auth, persist === "local" ? browserLocalPersistence : browserSessionPersistence);
832
+ }
833
+ const userAuth = await signInWithEmailAndPassword(auth, email, password);
834
+ if (userAuth) {
835
+ if (verifyEmail && !userAuth.user.emailVerified) {
836
+ return { message: "Email is not verified", status: "error" };
837
+ } else {
838
+ return { message: "User successfully logged in", status: "success", data: userAuth.user };
839
+ }
840
+ } else {
841
+ return { message: "Unknown account", status: "error" };
842
+ }
843
+ } catch (error) {
844
+ throw new Error(`login error: , ${error}`);
845
+ }
846
+ }
847
+ /**
848
+ * sign user with user's number
849
+ * @param param0
850
+ * @returns
851
+ */
852
+ async signInWithPhoneNumber({ auth, phoneNumber, appVerifier }) {
853
+ try {
854
+ return await signInWithPhoneNumber(auth, phoneNumber, appVerifier);
855
+ } catch (error) {
856
+ throw new Error(`signInWithPhoneNumber error: , ${error}`);
857
+ }
858
+ }
859
+ /**
860
+ * Check if user is currently logged in
861
+ * @param auth
862
+ * @returns
863
+ */
864
+ isLoggedIn(auth) {
865
+ try {
866
+ if (auth.currentUser) {
867
+ return true;
868
+ } else {
869
+ return false;
870
+ }
871
+ } catch (error) {
872
+ throw new Error(`isLoggedIn error: , ${error}`);
873
+ }
874
+ }
875
+ /**
876
+ * Reset logged in user's password
877
+ * @param {Auth, string} param0
878
+ * @returns {boolean}
879
+ */
880
+ async resetPassword({ auth, newPassword }) {
881
+ try {
882
+ await updatePassword(auth.currentUser, newPassword);
883
+ return true;
884
+ } catch (e) {
885
+ throw new Error(`resetPassword error: , ${e}`);
886
+ }
887
+ }
888
+ /**
889
+ * send password reset message to user's email address
890
+ * @param param0
891
+ * @returns
892
+ */
893
+ async sendPasswordResetMessage({ auth, email }) {
894
+ try {
895
+ await sendPasswordResetEmail(auth, email);
896
+ return true;
897
+ } catch (e) {
898
+ throw new Error(`sendPasswordResetMessage error: , ${e}`);
899
+ }
900
+ }
901
+ /**
902
+ * logout users and end current session
903
+ * @param param0
904
+ * @returns
905
+ */
906
+ async logout({ auth }) {
907
+ try {
908
+ await signOut(auth);
909
+ return true;
910
+ } catch (e) {
911
+ throw new Error(`logout error: , ${e}`);
912
+ }
913
+ }
914
+ /**
915
+ * create and validate users
916
+ * @param param0
917
+ */
918
+ async loginWithMultiAuthFactor({ email, password, auth, recaptcha, getNumber, persist = true, verifyEmail = false }) {
919
+ try {
920
+ if (persist) {
921
+ await setPersistence(auth, browserSessionPersistence);
922
+ }
923
+ const credential = await signInWithEmailAndPassword(auth, email, password);
924
+ if (credential) {
925
+ if (verifyEmail && !credential.user.emailVerified) {
926
+ await this.sendEmailVerification(credential.user);
927
+ throw new Error("Email is not verified");
928
+ }
929
+ let phoneNumber;
930
+ if (getNumber) {
931
+ phoneNumber = await this.getPhoneNumber(credential.user.uid);
932
+ }
933
+ const user = credential.user;
934
+ const verificationId = await this.setMultiFactorEnrollment(user, recaptcha, auth, phoneNumber != null ? phoneNumber : credential.user.phoneNumber);
935
+ if (verificationId) {
936
+ return { verificationId, user };
937
+ } else {
938
+ return null;
939
+ }
940
+ } else {
941
+ return null;
942
+ }
943
+ } catch (e) {
944
+ const error = e;
945
+ if (error.code === "auth/multi-factor-auth-required") {
946
+ const resolver = getMultiFactorResolver(auth, error);
947
+ return await this.sendOTP(resolver, recaptcha, auth);
948
+ } else {
949
+ throw new Error(`loginWithMultiAuthFactor error: ${error}`);
950
+ }
951
+ }
952
+ }
953
+ /**
954
+ * delete user account
955
+ * @param user
956
+ */
957
+ async deleteAccount(user) {
958
+ try {
959
+ await Promise.all([
960
+ deleteUser(user),
961
+ this.delete(user.uid)
962
+ ]);
963
+ } catch (error) {
964
+ throw new Error(`deleteAccount error: ,${error}`);
965
+ }
966
+ }
967
+ /**
968
+ * resend email verification for expired link
969
+ * @param auth
970
+ * @returns
971
+ */
972
+ async sendEmailVerification(user) {
973
+ try {
974
+ if (user) {
975
+ sendEmailVerification(user);
976
+ return null;
977
+ } else {
978
+ return "Unknown user account, please sign up!";
979
+ }
980
+ } catch (error) {
981
+ throw new Error(`sendEmailVerification error: , ${error}`);
982
+ }
983
+ }
984
+ async doPasswordReset(auth, actionCode, newPassword) {
985
+ try {
986
+ await confirmPasswordReset(auth, actionCode, newPassword);
987
+ return true;
988
+ } catch (e) {
989
+ throw new Error(`doPasswordReset error: , ${e}`);
990
+ }
991
+ }
992
+ };
993
+
994
+ // src/Storage.ts
995
+ import {
996
+ ref,
997
+ uploadString,
998
+ getDownloadURL,
999
+ uploadBytesResumable,
1000
+ deleteObject
1001
+ } from "firebase/storage";
1002
+
1003
+ // src/constants.ts
1004
+ var fbsession = /^firebase:authUser:/;
1005
+ var UPLOADTYPES = /* @__PURE__ */ ((UPLOADTYPES2) => {
1006
+ UPLOADTYPES2["IMAGES"] = "images";
1007
+ UPLOADTYPES2["DOCUMENTS"] = "documents";
1008
+ UPLOADTYPES2["VIDEOS"] = "videos";
1009
+ UPLOADTYPES2["AUDIOS"] = "audios";
1010
+ return UPLOADTYPES2;
1011
+ })(UPLOADTYPES || {});
1012
+ var AUTH_PROVIDERS = /* @__PURE__ */ ((AUTH_PROVIDERS2) => {
1013
+ AUTH_PROVIDERS2[AUTH_PROVIDERS2["GOOGLE"] = 0] = "GOOGLE";
1014
+ AUTH_PROVIDERS2[AUTH_PROVIDERS2["APPLE"] = 1] = "APPLE";
1015
+ AUTH_PROVIDERS2[AUTH_PROVIDERS2["FACEBOOK"] = 2] = "FACEBOOK";
1016
+ AUTH_PROVIDERS2[AUTH_PROVIDERS2["TWITTER"] = 3] = "TWITTER";
1017
+ return AUTH_PROVIDERS2;
1018
+ })(AUTH_PROVIDERS || {});
1019
+
1020
+ // src/Storage.ts
1021
+ var StorageUpload = class {
1022
+ constructor(props) {
1023
+ // validate file (size, type)
1024
+ this.validateFile = (file, storageRef) => {
1025
+ var _a;
1026
+ let goodSize = false;
1027
+ let goodType = false;
1028
+ let extension = "";
1029
+ let goodTypes = [];
1030
+ if (typeof file !== "string") {
1031
+ if (storageRef === "images" /* IMAGES */) {
1032
+ goodTypes = ["image/png", "image/jpg", "image/jpeg"];
1033
+ extension = this.getExtensionName(file.type);
1034
+ } else if (storageRef === "documents" /* DOCUMENTS */) {
1035
+ goodTypes = [
1036
+ "application/pdf",
1037
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
1038
+ "application/vnd.ms-excel"
1039
+ ];
1040
+ extension = this.getDocExtensionName(file.type);
1041
+ } else if (storageRef === "videos" /* VIDEOS */) {
1042
+ goodTypes = ["video/mp4", "video/m4v"];
1043
+ } else if (storageRef === "audios" /* AUDIOS */) {
1044
+ goodTypes = ["audio/mp3", "audio/mpeg"];
1045
+ }
1046
+ goodType = goodTypes.includes(file.type);
1047
+ goodSize = file.size > 0 && file.size <= this.maxSize;
1048
+ } else {
1049
+ goodType = true;
1050
+ goodSize = true;
1051
+ }
1052
+ if (!goodSize || !goodType) {
1053
+ this.setUploadError(storageRef, (_a = this.maxSize) != null ? _a : 1e6, goodSize);
1054
+ } else {
1055
+ this.setFilePath(storageRef, extension);
1056
+ }
1057
+ };
1058
+ /**
1059
+ * Get human readable form for file size
1060
+ * @param size
1061
+ * @returns
1062
+ */
1063
+ this.sizeMetric = (size) => {
1064
+ let result = `${size} bytes`;
1065
+ if (size <= 999999) {
1066
+ result = `${Math.round(size / 1e3)} Kb`;
1067
+ } else if (size <= 999999999) {
1068
+ result = `${Math.round(size / 1e6)} Mb`;
1069
+ } else if (size <= 999999999999) {
1070
+ result = `${Math.round(size / 1e9)} Gb`;
1071
+ } else if (size <= 999999999999999) {
1072
+ result = `${Math.round(size / 1e12)} T`;
1073
+ }
1074
+ return result;
1075
+ };
1076
+ /**
1077
+ * Get file extensions
1078
+ * @param fileType
1079
+ * @returns
1080
+ */
1081
+ this.getExtensionName = (fileType) => {
1082
+ let ext = "";
1083
+ if (fileType === "application/pdf") {
1084
+ ext = "pdf";
1085
+ }
1086
+ if (fileType === "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") {
1087
+ ext = ".xlsx";
1088
+ }
1089
+ if (fileType === "application/vnd.ms-excel") {
1090
+ ext = ".xls";
1091
+ }
1092
+ return ext;
1093
+ };
1094
+ this.getDocExtensionName = (fileType) => {
1095
+ let ext = "";
1096
+ if (fileType === "image/png") {
1097
+ ext = "png";
1098
+ }
1099
+ if (fileType === "image/jpg") {
1100
+ ext = "jpg";
1101
+ }
1102
+ if (fileType === "image/jpeg") {
1103
+ ext = "jpeg";
1104
+ }
1105
+ return ext;
1106
+ };
1107
+ /**
1108
+ * setting error messages for failed file validation
1109
+ * @param ref
1110
+ * @param maxSize
1111
+ * @param isGoodSize
1112
+ * @param isGoodType
1113
+ */
1114
+ this.setUploadError = (ref2, maxSize, isGoodSize) => {
1115
+ this.uploadError = !isGoodSize ? `File size is must not be larger than ${this.sizeMetric(maxSize)}` : `File is not a valid ${ref2 === "images" /* IMAGES */ ? "image" : ref2 === "documents" /* DOCUMENTS */ ? "document" : ref2 === "videos" /* VIDEOS */ ? "video" : "audio"}`;
1116
+ };
1117
+ // generate new file name and extension
1118
+ this.setFilePath = (ref2, ext) => {
1119
+ const d = /* @__PURE__ */ new Date();
1120
+ const fileExtension = ref2 === "images" /* IMAGES */ || ref2 === "documents" /* DOCUMENTS */ ? "." + ext : (
1121
+ // (ref===UPLOADTYPES.DOCUMENTS?'.pdf':(
1122
+ ref2 === "videos" /* VIDEOS */ ? ".mp4" : ".mp3"
1123
+ );
1124
+ const fileName = generateRandomString(30);
1125
+ this.fullPath = ref2.concat(
1126
+ `/`,
1127
+ `${this.additionalPath}/`,
1128
+ `${fileName}_${d.getTime()}${fileExtension}`
1129
+ );
1130
+ };
1131
+ /**
1132
+ * upload base64 data_url
1133
+ * @param reference
1134
+ */
1135
+ this.uploadAsString = async (reference) => {
1136
+ try {
1137
+ const result = await uploadString(reference, this.file, "data_url");
1138
+ if (result) {
1139
+ return await getDownloadURL(ref(this.storage, result.ref.fullPath));
1140
+ } else {
1141
+ return false;
1142
+ }
1143
+ } catch (error) {
1144
+ throw new Error(`uploadAsString: , ${error}`);
1145
+ }
1146
+ };
1147
+ /**
1148
+ * upload blobs and files
1149
+ * @param reference
1150
+ */
1151
+ this.uploadAsFile = async (reference) => {
1152
+ try {
1153
+ const snapShot = await uploadBytesResumable(reference, this.file);
1154
+ if (snapShot) {
1155
+ return await getDownloadURL(ref(this.storage, this.fullPath));
1156
+ } else {
1157
+ return false;
1158
+ }
1159
+ } catch (error) {
1160
+ throw new Error(`uploadAsFile: , ${error}`);
1161
+ }
1162
+ };
1163
+ const { file, basePath, otherPath, maxSize, storage } = props;
1164
+ this.storage = storage;
1165
+ if (file) {
1166
+ this.additionalPath = otherPath;
1167
+ this.maxSize = maxSize != null ? maxSize : 1e6;
1168
+ this.file = file;
1169
+ this.validateFile(file, basePath);
1170
+ }
1171
+ }
1172
+ /**
1173
+ * Upload images, documents and videos
1174
+ * @param progressMonitor
1175
+ */
1176
+ async doUpload() {
1177
+ if (this.uploadError) {
1178
+ throw new Error(`doUPload Error: ,${this.uploadError}`);
1179
+ } else {
1180
+ try {
1181
+ const reference = ref(this.storage, this.fullPath);
1182
+ if (typeof this.file === "string") {
1183
+ console.log("uploading as string");
1184
+ return await this.uploadAsString(reference);
1185
+ } else {
1186
+ console.log("uploading as file");
1187
+ return await this.uploadAsFile(reference);
1188
+ }
1189
+ } catch (error) {
1190
+ throw new Error(`doUPload Error: ,${error}`);
1191
+ }
1192
+ }
1193
+ }
1194
+ /**
1195
+ * delete files from storage
1196
+ * @param filePath
1197
+ * @param storage
1198
+ * @returns {boolean}
1199
+ */
1200
+ static async deleteFile(filePath, storage) {
1201
+ try {
1202
+ const delRef = ref(storage, filePath);
1203
+ await deleteObject(delRef);
1204
+ return true;
1205
+ } catch (error) {
1206
+ return false;
1207
+ }
1208
+ }
1209
+ };
1210
+
1211
+ // src/useAuth.ts
1212
+ import { useEffect } from "react";
1213
+ import { onAuthStateChanged } from "firebase/auth";
1214
+ function useAuth(auth, callback) {
1215
+ useEffect(() => {
1216
+ const unsubscribe = onAuthStateChanged(auth, async (authUser) => {
1217
+ callback(authUser);
1218
+ });
1219
+ return () => unsubscribe();
1220
+ }, []);
1221
+ }
1222
+
1223
+ // src/useCounter.ts
1224
+ import { useCallback, useEffect as useEffect2, useState } from "react";
1225
+ var useCount = (param) => {
1226
+ const [data, setData] = useState(0);
1227
+ const [loading, setLoading] = useState(true);
1228
+ const fetcher = useCallback(async () => {
1229
+ if (!param.where)
1230
+ return;
1231
+ try {
1232
+ const model = param.model;
1233
+ const found = await model.countData(param.where);
1234
+ setData(found);
1235
+ } catch (error) {
1236
+ errorLogger("useCount: ", error);
1237
+ } finally {
1238
+ setLoading(false);
1239
+ }
1240
+ }, [
1241
+ JSON.stringify(param.where),
1242
+ loading
1243
+ ]);
1244
+ useEffect2(() => {
1245
+ fetcher();
1246
+ }, [fetcher]);
1247
+ return [data, loading, setLoading];
1248
+ };
1249
+
1250
+ // src/useFetch.ts
1251
+ import { useCallback as useCallback2, useEffect as useEffect3, useState as useState2 } from "react";
1252
+ var useFetch = (param) => {
1253
+ const [data, setData] = useState2();
1254
+ const [loading, setLoading] = useState2(true);
1255
+ const fetcher = useCallback2(async () => {
1256
+ var _a, _b, _c, _d;
1257
+ if (!param.reference && !param.where)
1258
+ return;
1259
+ try {
1260
+ const model = param.model;
1261
+ if (param.reference) {
1262
+ const found = await model.find(param.reference);
1263
+ if (found)
1264
+ setData(model.data);
1265
+ } else if (param.where) {
1266
+ const found = await model.findWhere({
1267
+ wh: param.where,
1268
+ lim: (_b = (_a = param.filter) == null ? void 0 : _a.limit) != null ? _b : 100,
1269
+ order: (_c = param.filter) == null ? void 0 : _c.orderBy,
1270
+ offset: (_d = param.filter) == null ? void 0 : _d.offset
1271
+ });
1272
+ setData(found);
1273
+ }
1274
+ } catch (error) {
1275
+ errorLogger("useFetch: ", error);
1276
+ } finally {
1277
+ setLoading(false);
1278
+ }
1279
+ }, [
1280
+ param.reference,
1281
+ JSON.stringify(param.where),
1282
+ JSON.stringify(param.filter),
1283
+ loading
1284
+ ]);
1285
+ useEffect3(() => {
1286
+ fetcher();
1287
+ }, [fetcher]);
1288
+ return [data, loading, setLoading];
1289
+ };
1290
+
1291
+ // src/useStream.ts
1292
+ import { useEffect as useEffect4 } from "react";
1293
+ var useStream = (param, callback) => {
1294
+ useEffect4(() => {
1295
+ let unsubscribe;
1296
+ try {
1297
+ const { model, reference, where: where2, filter } = param;
1298
+ if (reference) {
1299
+ unsubscribe = model.stream((data) => {
1300
+ if (data)
1301
+ callback(data);
1302
+ }, reference);
1303
+ } else if (where2) {
1304
+ const allInvalid = where2.every((item) => item.value === void 0);
1305
+ if (allInvalid) {
1306
+ callback([]);
1307
+ return;
1308
+ }
1309
+ unsubscribe = model.streamWhere(
1310
+ where2,
1311
+ (data) => {
1312
+ if (data)
1313
+ callback(data);
1314
+ },
1315
+ 100,
1316
+ filter == null ? void 0 : filter.orderBy,
1317
+ filter == null ? void 0 : filter.offset
1318
+ );
1319
+ }
1320
+ } catch (error) {
1321
+ errorLogger("useStream error:", error);
1322
+ }
1323
+ return () => {
1324
+ if (typeof unsubscribe === "function") {
1325
+ unsubscribe();
1326
+ }
1327
+ };
1328
+ }, [JSON.stringify(param.where), JSON.stringify(param.filter), param.reference]);
1329
+ };
1330
+ export {
1331
+ AUTH_PROVIDERS,
1332
+ BaseModel,
1333
+ StorageUpload,
1334
+ UPLOADTYPES,
1335
+ Users,
1336
+ camelCaseToNormal,
1337
+ camelToTitleCase,
1338
+ convertUnicode,
1339
+ errorLogger,
1340
+ fbsession,
1341
+ fetchCurrentTimestamp,
1342
+ formHasError,
1343
+ generateRandomString,
1344
+ getDaysAgo,
1345
+ getInitials,
1346
+ getNthNumberOfArray,
1347
+ isValidEmail,
1348
+ isValidPassword,
1349
+ moneyFormatter,
1350
+ nairaFormatter,
1351
+ noEmptyField,
1352
+ numberToAlphabet,
1353
+ range,
1354
+ timeAgo,
1355
+ useAuth,
1356
+ useCount,
1357
+ useFetch,
1358
+ useStream
1359
+ };