@edgedev/firebase 1.1.6 → 1.2.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/edgeFirebase.ts +485 -0
- package/index.ts +6 -446
- package/package.json +1 -1
package/edgeFirebase.ts
ADDED
|
@@ -0,0 +1,485 @@
|
|
|
1
|
+
import { initializeApp } from "firebase/app";
|
|
2
|
+
import { reactive } from "vue";
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
getFirestore,
|
|
6
|
+
collection,
|
|
7
|
+
addDoc,
|
|
8
|
+
doc,
|
|
9
|
+
query,
|
|
10
|
+
onSnapshot,
|
|
11
|
+
WhereFilterOp,
|
|
12
|
+
QueryConstraint,
|
|
13
|
+
Unsubscribe,
|
|
14
|
+
where,
|
|
15
|
+
deleteDoc,
|
|
16
|
+
getDocs,
|
|
17
|
+
getDoc,
|
|
18
|
+
orderBy,
|
|
19
|
+
limit,
|
|
20
|
+
Query,
|
|
21
|
+
startAfter,
|
|
22
|
+
DocumentData,
|
|
23
|
+
setDoc
|
|
24
|
+
} from "firebase/firestore";
|
|
25
|
+
|
|
26
|
+
import {
|
|
27
|
+
getAuth,
|
|
28
|
+
setPersistence,
|
|
29
|
+
browserSessionPersistence,
|
|
30
|
+
browserLocalPersistence,
|
|
31
|
+
Persistence,
|
|
32
|
+
signInWithEmailAndPassword,
|
|
33
|
+
onAuthStateChanged,
|
|
34
|
+
signOut
|
|
35
|
+
} from "firebase/auth";
|
|
36
|
+
|
|
37
|
+
interface FirestoreQuery {
|
|
38
|
+
field: string;
|
|
39
|
+
operator: WhereFilterOp; // '==' | '<' | '<=' | '>' | '>=' | 'array-contains' | 'in' | 'array-contains-any';
|
|
40
|
+
value: unknown;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
interface FirestoreOrderBy {
|
|
44
|
+
field: string;
|
|
45
|
+
direction: "asc" | "desc";
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
interface FirestoreLimit {
|
|
49
|
+
limit: number;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
interface CollectionUnsubscribeObject {
|
|
53
|
+
[key: string]: Unsubscribe;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
interface CollectionDataObject {
|
|
57
|
+
[key: string]: object;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
interface UserDataObject {
|
|
61
|
+
uid: string | null;
|
|
62
|
+
email: string;
|
|
63
|
+
loggedIn: boolean;
|
|
64
|
+
logInError: boolean;
|
|
65
|
+
logInErrorMessage: string;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
interface Credentials {
|
|
69
|
+
email: string;
|
|
70
|
+
password: string;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
interface StaticDataResult {
|
|
74
|
+
data: object;
|
|
75
|
+
next: DocumentData | null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
interface firebaseConfig {
|
|
79
|
+
apiKey: string;
|
|
80
|
+
authDomain: string;
|
|
81
|
+
projectId: string;
|
|
82
|
+
storageBucket: string;
|
|
83
|
+
messagingSenderId: string;
|
|
84
|
+
appId: string;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export const EdgeFirebase = class {
|
|
88
|
+
constructor(
|
|
89
|
+
firebaseConfig: firebaseConfig = {
|
|
90
|
+
apiKey: "",
|
|
91
|
+
authDomain: "",
|
|
92
|
+
projectId: "",
|
|
93
|
+
storageBucket: "",
|
|
94
|
+
messagingSenderId: "",
|
|
95
|
+
appId: ""
|
|
96
|
+
}
|
|
97
|
+
) {
|
|
98
|
+
this.firebaseConfig = firebaseConfig;
|
|
99
|
+
this.app = initializeApp(this.firebaseConfig);
|
|
100
|
+
this.auth = getAuth(this.app);
|
|
101
|
+
this.db = getFirestore(this.app);
|
|
102
|
+
this.setOnAuthStateChanged();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
private firebaseConfig = null;
|
|
106
|
+
|
|
107
|
+
// Initialize Firebase
|
|
108
|
+
public app = null;
|
|
109
|
+
public auth = null;
|
|
110
|
+
public db = null;
|
|
111
|
+
|
|
112
|
+
// Composable to logout
|
|
113
|
+
public logOut = (): void => {
|
|
114
|
+
signOut(this.auth)
|
|
115
|
+
.then(() => {
|
|
116
|
+
Object.keys(this.unsubscibe).forEach((key) => {
|
|
117
|
+
if (this.unsubscibe[key] instanceof Function) {
|
|
118
|
+
this.unsubscibe[key]();
|
|
119
|
+
this.unsubscibe[key] = null;
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
})
|
|
123
|
+
.catch(() => {
|
|
124
|
+
// Do nothing
|
|
125
|
+
});
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
private setOnAuthStateChanged = (): void => {
|
|
129
|
+
onAuthStateChanged(this.auth, (userAuth) => {
|
|
130
|
+
if (userAuth) {
|
|
131
|
+
this.user.email = userAuth.email;
|
|
132
|
+
this.user.uid = userAuth.uid;
|
|
133
|
+
this.user.loggedIn = true;
|
|
134
|
+
this.user.logInError = false;
|
|
135
|
+
this.user.logInErrorMessage = "";
|
|
136
|
+
} else {
|
|
137
|
+
this.user.email = "";
|
|
138
|
+
this.user.uid = null;
|
|
139
|
+
this.user.loggedIn = false;
|
|
140
|
+
this.user.logInError = false;
|
|
141
|
+
this.user.logInErrorMessage = "";
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
// Composable to login and set persistence
|
|
147
|
+
public logIn = (credentials: Credentials, isPersistant = false): void => {
|
|
148
|
+
this.logOut();
|
|
149
|
+
let persistence: Persistence = browserSessionPersistence;
|
|
150
|
+
if (isPersistant) {
|
|
151
|
+
persistence = browserLocalPersistence;
|
|
152
|
+
}
|
|
153
|
+
setPersistence(this.auth, persistence)
|
|
154
|
+
.then(() => {
|
|
155
|
+
signInWithEmailAndPassword(
|
|
156
|
+
this.auth,
|
|
157
|
+
credentials.email,
|
|
158
|
+
credentials.password
|
|
159
|
+
)
|
|
160
|
+
.then(() => {
|
|
161
|
+
// do nothing
|
|
162
|
+
})
|
|
163
|
+
.catch((error) => {
|
|
164
|
+
this.user.email = "";
|
|
165
|
+
this.user.uid = null;
|
|
166
|
+
|
|
167
|
+
this.user.loggedIn = false;
|
|
168
|
+
this.user.logInError = true;
|
|
169
|
+
this.user.logInErrorMessage = error.code + ": " + error.message;
|
|
170
|
+
});
|
|
171
|
+
})
|
|
172
|
+
.catch((error) => {
|
|
173
|
+
this.user.email = "";
|
|
174
|
+
this.user.uid = null;
|
|
175
|
+
|
|
176
|
+
this.user.loggedIn = false;
|
|
177
|
+
this.user.logInError = true;
|
|
178
|
+
this.user.logInErrorMessage = error.code + ": " + error.message;
|
|
179
|
+
});
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
// Keeping this for reference on how to Type a Ref.
|
|
183
|
+
// const user = ref<UserDataObject>({
|
|
184
|
+
// uid: null,
|
|
185
|
+
// email: "",
|
|
186
|
+
// loggedIn: false,
|
|
187
|
+
// logInError: false,
|
|
188
|
+
// logInErrorMessage: ""
|
|
189
|
+
// });
|
|
190
|
+
|
|
191
|
+
// Simple Store Items (add matching key per firebase collection)
|
|
192
|
+
public data: CollectionDataObject = reactive({});
|
|
193
|
+
public unsubscibe: CollectionUnsubscribeObject = reactive({});
|
|
194
|
+
public user: UserDataObject = reactive({
|
|
195
|
+
uid: null,
|
|
196
|
+
email: "",
|
|
197
|
+
loggedIn: false,
|
|
198
|
+
logInError: false,
|
|
199
|
+
logInErrorMessage: ""
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
public getDocData = async (
|
|
203
|
+
collectionPath: string,
|
|
204
|
+
docId: string
|
|
205
|
+
): Promise<{ [key: string]: unknown }> => {
|
|
206
|
+
const docRef = doc(this.db, collectionPath, docId);
|
|
207
|
+
const docSnap = await getDoc(docRef);
|
|
208
|
+
const docData = docSnap.data();
|
|
209
|
+
docData.docId = docSnap.id;
|
|
210
|
+
return docData;
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
private getStaticData = async (
|
|
214
|
+
collectionPath: string,
|
|
215
|
+
queryList: FirestoreQuery[] = [],
|
|
216
|
+
orderList: FirestoreOrderBy[] = [],
|
|
217
|
+
max = 0,
|
|
218
|
+
last: DocumentData | null = null
|
|
219
|
+
): Promise<StaticDataResult> => {
|
|
220
|
+
const data: object = {};
|
|
221
|
+
|
|
222
|
+
const q = this.getQuery(collectionPath, queryList, orderList, max, last);
|
|
223
|
+
|
|
224
|
+
const docs = await getDocs(q);
|
|
225
|
+
const nextLast: DocumentData = docs.docs[docs.docs.length - 1];
|
|
226
|
+
|
|
227
|
+
docs.forEach((doc) => {
|
|
228
|
+
const item = doc.data();
|
|
229
|
+
item.docId = doc.id;
|
|
230
|
+
data[doc.id] = item;
|
|
231
|
+
});
|
|
232
|
+
return { data, next: nextLast };
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
// Class for wrapping a getSaticData to handle pagination
|
|
236
|
+
get SearchStaticData() {
|
|
237
|
+
const getStaticData = this.getStaticData;
|
|
238
|
+
return class {
|
|
239
|
+
private collectionPath = "";
|
|
240
|
+
private queryList: FirestoreQuery[] = [];
|
|
241
|
+
private orderList: FirestoreOrderBy[] = [];
|
|
242
|
+
private max = 0;
|
|
243
|
+
|
|
244
|
+
public results = reactive({
|
|
245
|
+
data: {},
|
|
246
|
+
pagination: [],
|
|
247
|
+
staticIsLastPage: true,
|
|
248
|
+
staticIsFirstPage: true,
|
|
249
|
+
staticCurrentPage: ""
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
public prev = async (): Promise<void> => {
|
|
253
|
+
const findIndex = this.results.pagination.findIndex(
|
|
254
|
+
(x) => x.key === this.results.staticCurrentPage
|
|
255
|
+
);
|
|
256
|
+
let last = null;
|
|
257
|
+
if (findIndex === 1) {
|
|
258
|
+
this.results.staticCurrentPage = "";
|
|
259
|
+
this.results.staticIsLastPage = false;
|
|
260
|
+
this.results.staticIsFirstPage = true;
|
|
261
|
+
} else {
|
|
262
|
+
last = this.results.pagination[findIndex - 2].next;
|
|
263
|
+
this.results.staticCurrentPage =
|
|
264
|
+
this.results.pagination[findIndex - 2].key;
|
|
265
|
+
}
|
|
266
|
+
await this.afterNextPrev(last);
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
public next = async (): Promise<void> => {
|
|
270
|
+
const findIndex = this.results.pagination.findIndex(
|
|
271
|
+
(x) => x.key === this.results.staticCurrentPage
|
|
272
|
+
);
|
|
273
|
+
const last = this.results.pagination[findIndex].next;
|
|
274
|
+
if (this.results.pagination.length === 1) {
|
|
275
|
+
this.results.staticIsFirstPage = true;
|
|
276
|
+
} else {
|
|
277
|
+
this.results.staticIsFirstPage = false;
|
|
278
|
+
}
|
|
279
|
+
await this.afterNextPrev(last);
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
private afterNextPrev = async (last): Promise<void> => {
|
|
283
|
+
let results = await getStaticData(
|
|
284
|
+
"users",
|
|
285
|
+
this.queryList,
|
|
286
|
+
this.orderList,
|
|
287
|
+
this.max,
|
|
288
|
+
last
|
|
289
|
+
);
|
|
290
|
+
|
|
291
|
+
if (last && Object.keys(results.data).length === 0) {
|
|
292
|
+
this.results.staticIsLastPage = true;
|
|
293
|
+
if (this.results.pagination.length === 1) {
|
|
294
|
+
last = null;
|
|
295
|
+
this.results.staticCurrentPage = "";
|
|
296
|
+
this.results.staticIsFirstPage = true;
|
|
297
|
+
} else {
|
|
298
|
+
last =
|
|
299
|
+
this.results.pagination[this.results.pagination.length - 2].next;
|
|
300
|
+
this.results.staticCurrentPage =
|
|
301
|
+
this.results.pagination[this.results.pagination.length - 2].key;
|
|
302
|
+
}
|
|
303
|
+
results = await getStaticData(
|
|
304
|
+
"users",
|
|
305
|
+
this.queryList,
|
|
306
|
+
this.orderList,
|
|
307
|
+
this.max,
|
|
308
|
+
last
|
|
309
|
+
);
|
|
310
|
+
} else {
|
|
311
|
+
this.results.staticIsLastPage = false;
|
|
312
|
+
if (this.results.pagination.length === 1) {
|
|
313
|
+
this.results.staticIsFirstPage = false;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
this.results.data = results.data;
|
|
317
|
+
this.results.staticCurrentPage = results.next.id;
|
|
318
|
+
if (!this.results.staticIsLastPage) {
|
|
319
|
+
if (results.next) {
|
|
320
|
+
const findItem = this.results.pagination.find(
|
|
321
|
+
(x) => x.key === results.next.id
|
|
322
|
+
);
|
|
323
|
+
if (!findItem) {
|
|
324
|
+
this.results.pagination.push({
|
|
325
|
+
key: results.next.id,
|
|
326
|
+
next: results.next
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
public getData = async (
|
|
334
|
+
collectionPath: string,
|
|
335
|
+
queryList: FirestoreQuery[] = [],
|
|
336
|
+
orderList: FirestoreOrderBy[] = [],
|
|
337
|
+
max = 0
|
|
338
|
+
): Promise<void> => {
|
|
339
|
+
this.collectionPath = collectionPath;
|
|
340
|
+
this.queryList = queryList;
|
|
341
|
+
this.orderList = orderList;
|
|
342
|
+
this.max = max;
|
|
343
|
+
this.results.staticIsLastPage = true;
|
|
344
|
+
this.results.staticIsFirstPage = true;
|
|
345
|
+
this.results.staticCurrentPage = "";
|
|
346
|
+
this.results.pagination = [];
|
|
347
|
+
this.results.pagination = [];
|
|
348
|
+
this.results.data = {};
|
|
349
|
+
const results = await getStaticData(
|
|
350
|
+
collectionPath,
|
|
351
|
+
queryList,
|
|
352
|
+
orderList,
|
|
353
|
+
max
|
|
354
|
+
);
|
|
355
|
+
if (Object.keys(results.data).length > 0) {
|
|
356
|
+
this.results.staticIsLastPage = false;
|
|
357
|
+
this.results.data = results.data;
|
|
358
|
+
this.results.staticCurrentPage = results.next.id;
|
|
359
|
+
this.results.pagination.push({
|
|
360
|
+
key: results.next.id,
|
|
361
|
+
next: results.next
|
|
362
|
+
});
|
|
363
|
+
} else {
|
|
364
|
+
this.results.staticIsLastPage = true;
|
|
365
|
+
this.results.staticIsFirstPage = true;
|
|
366
|
+
}
|
|
367
|
+
};
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// Class for wrapping a getSaticData to handle pagination
|
|
372
|
+
public SearchStaticDatas = new (class {})();
|
|
373
|
+
|
|
374
|
+
// Composable to start snapshot listener and set unsubscribe function
|
|
375
|
+
public startSnapshot = (
|
|
376
|
+
collectionPath: string,
|
|
377
|
+
queryList: FirestoreQuery[] = [],
|
|
378
|
+
orderList: FirestoreOrderBy[] = [],
|
|
379
|
+
max = 0
|
|
380
|
+
): void => {
|
|
381
|
+
this.data[collectionPath] = {};
|
|
382
|
+
const q = this.getQuery(collectionPath, queryList, orderList, max);
|
|
383
|
+
const unsubscribe = onSnapshot(q, (querySnapshot) => {
|
|
384
|
+
const items = {};
|
|
385
|
+
querySnapshot.forEach((doc) => {
|
|
386
|
+
const item = doc.data();
|
|
387
|
+
item.docId = doc.id;
|
|
388
|
+
items[doc.id] = item;
|
|
389
|
+
});
|
|
390
|
+
this.data[collectionPath] = items;
|
|
391
|
+
});
|
|
392
|
+
this.unsubscibe[collectionPath] = unsubscribe;
|
|
393
|
+
};
|
|
394
|
+
|
|
395
|
+
private getQuery = (
|
|
396
|
+
collectionPath: string,
|
|
397
|
+
queryList: FirestoreQuery[] = [],
|
|
398
|
+
orderList: FirestoreOrderBy[] = [],
|
|
399
|
+
max = 0,
|
|
400
|
+
after: DocumentData | null = null
|
|
401
|
+
): Query => {
|
|
402
|
+
const queryConditions: QueryConstraint[] = queryList.map((condition) =>
|
|
403
|
+
where(condition.field, condition.operator, condition.value)
|
|
404
|
+
);
|
|
405
|
+
|
|
406
|
+
const orderConditions: QueryConstraint[] = orderList.map((condition) =>
|
|
407
|
+
orderBy(condition.field, condition.direction)
|
|
408
|
+
);
|
|
409
|
+
|
|
410
|
+
let limitList: FirestoreLimit[] = [];
|
|
411
|
+
if (max > 0) {
|
|
412
|
+
limitList = [{ limit: max }];
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
const limitConditions: QueryConstraint[] = limitList.map((condition) =>
|
|
416
|
+
limit(condition.limit)
|
|
417
|
+
);
|
|
418
|
+
if (after) {
|
|
419
|
+
return query(
|
|
420
|
+
collection(this.db, collectionPath),
|
|
421
|
+
...queryConditions,
|
|
422
|
+
...orderConditions,
|
|
423
|
+
...limitConditions,
|
|
424
|
+
startAfter(after)
|
|
425
|
+
);
|
|
426
|
+
}
|
|
427
|
+
return query(
|
|
428
|
+
collection(this.db, collectionPath),
|
|
429
|
+
...queryConditions,
|
|
430
|
+
...orderConditions,
|
|
431
|
+
...limitConditions
|
|
432
|
+
);
|
|
433
|
+
};
|
|
434
|
+
|
|
435
|
+
// Composable to update/add a document
|
|
436
|
+
public storeDoc = async (
|
|
437
|
+
collectionPath: string,
|
|
438
|
+
item: object
|
|
439
|
+
): Promise<void> => {
|
|
440
|
+
const cloneItem = JSON.parse(JSON.stringify(item));
|
|
441
|
+
const currentTime = new Date().getTime();
|
|
442
|
+
cloneItem.last_updated = currentTime;
|
|
443
|
+
cloneItem.uid = this.user.uid;
|
|
444
|
+
if (!Object.prototype.hasOwnProperty.call(cloneItem, "doc_created_at")) {
|
|
445
|
+
cloneItem.doc_created_at = currentTime;
|
|
446
|
+
}
|
|
447
|
+
if (Object.prototype.hasOwnProperty.call(cloneItem, "docId")) {
|
|
448
|
+
const docId = cloneItem.docId;
|
|
449
|
+
if (Object.prototype.hasOwnProperty.call(this.data, collectionPath)) {
|
|
450
|
+
this.data[collectionPath][docId] = cloneItem;
|
|
451
|
+
}
|
|
452
|
+
setDoc(doc(this.db, collectionPath, docId), cloneItem);
|
|
453
|
+
} else {
|
|
454
|
+
const docRef = await addDoc(
|
|
455
|
+
collection(this.db, collectionPath),
|
|
456
|
+
cloneItem
|
|
457
|
+
);
|
|
458
|
+
if (Object.prototype.hasOwnProperty.call(this.data, collectionPath)) {
|
|
459
|
+
this.data[collectionPath][docRef.id] = cloneItem;
|
|
460
|
+
}
|
|
461
|
+
this.storeDoc(collectionPath, { ...cloneItem, docId: docRef.id });
|
|
462
|
+
}
|
|
463
|
+
};
|
|
464
|
+
|
|
465
|
+
// Composable to delete a document
|
|
466
|
+
public removeDoc = (collectionPath: string, docId: string): void => {
|
|
467
|
+
// Just in case getting collection back from firebase is slow:
|
|
468
|
+
if (Object.prototype.hasOwnProperty.call(this.data, collectionPath)) {
|
|
469
|
+
if (
|
|
470
|
+
Object.prototype.hasOwnProperty.call(this.data[collectionPath], docId)
|
|
471
|
+
) {
|
|
472
|
+
delete this.data[collectionPath][docId];
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
deleteDoc(doc(this.db, collectionPath, docId));
|
|
476
|
+
};
|
|
477
|
+
|
|
478
|
+
// Composable to stop snapshot listener
|
|
479
|
+
public stopSnapshot = (collectionPath: string): void => {
|
|
480
|
+
if (this.unsubscibe[collectionPath] instanceof Function) {
|
|
481
|
+
this.unsubscibe[collectionPath]();
|
|
482
|
+
this.unsubscibe[collectionPath] = null;
|
|
483
|
+
}
|
|
484
|
+
};
|
|
485
|
+
};
|
package/index.ts
CHANGED
|
@@ -1,448 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
collection,
|
|
7
|
-
addDoc,
|
|
8
|
-
updateDoc,
|
|
9
|
-
doc,
|
|
10
|
-
query,
|
|
11
|
-
onSnapshot,
|
|
12
|
-
WhereFilterOp,
|
|
13
|
-
QueryConstraint,
|
|
14
|
-
Unsubscribe,
|
|
15
|
-
where,
|
|
16
|
-
deleteDoc,
|
|
17
|
-
getDocs,
|
|
18
|
-
getDoc,
|
|
19
|
-
orderBy,
|
|
20
|
-
limit,
|
|
21
|
-
Query,
|
|
22
|
-
startAfter,
|
|
23
|
-
DocumentData,
|
|
24
|
-
setDoc
|
|
25
|
-
} from "firebase/firestore";
|
|
26
|
-
|
|
27
|
-
import {
|
|
28
|
-
getAuth,
|
|
29
|
-
setPersistence,
|
|
30
|
-
browserSessionPersistence,
|
|
31
|
-
browserLocalPersistence,
|
|
32
|
-
Persistence,
|
|
33
|
-
signInWithEmailAndPassword,
|
|
34
|
-
onAuthStateChanged,
|
|
35
|
-
signOut
|
|
36
|
-
} from "firebase/auth";
|
|
37
|
-
|
|
38
|
-
interface FirestoreQuery {
|
|
39
|
-
field: string;
|
|
40
|
-
operator: WhereFilterOp; // '==' | '<' | '<=' | '>' | '>=' | 'array-contains' | 'in' | 'array-contains-any';
|
|
41
|
-
value: unknown;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
interface FirestoreOrderBy {
|
|
45
|
-
field: string;
|
|
46
|
-
direction: "asc" | "desc";
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
interface FirestoreLimit {
|
|
50
|
-
limit: number;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
interface CollectionUnsubscribeObject {
|
|
54
|
-
[key: string]: Unsubscribe;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
interface CollectionDataObject {
|
|
58
|
-
[key: string]: object;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
interface UserDataObject {
|
|
62
|
-
uid: string | null;
|
|
63
|
-
email: string;
|
|
64
|
-
loggedIn: boolean;
|
|
65
|
-
logInError: boolean;
|
|
66
|
-
logInErrorMessage: string;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
interface Credentials {
|
|
70
|
-
email: string;
|
|
71
|
-
password: string;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
interface StaticDataResult {
|
|
75
|
-
data: object;
|
|
76
|
-
next: DocumentData | null;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
const firebaseConfig = {
|
|
80
|
-
apiKey: import.meta.env.VITE_FIREBASE_API_KEY as string,
|
|
81
|
-
authDomain: import.meta.env.VITE_FIREBASE_AUTH_DOMAIN as string,
|
|
82
|
-
projectId: import.meta.env.VITE_FIREBASE_PROJECT_ID as string,
|
|
83
|
-
storageBucket: import.meta.env.VITE_FIREBASE_STORAGE_BUCKET as string,
|
|
84
|
-
messagingSenderId: import.meta.env
|
|
85
|
-
.VITE_FIREBASE_MESSAGING_SENDER_ID as string,
|
|
86
|
-
appId: import.meta.env.VITE_FIREBASE_APP_ID as string
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
// Initialize Firebase
|
|
90
|
-
export const app = initializeApp(firebaseConfig);
|
|
91
|
-
export const auth = getAuth(app);
|
|
92
|
-
export const db = getFirestore(app);
|
|
93
|
-
|
|
94
|
-
onAuthStateChanged(auth, (userAuth) => {
|
|
95
|
-
if (userAuth) {
|
|
96
|
-
user.email = userAuth.email;
|
|
97
|
-
user.uid = userAuth.uid;
|
|
98
|
-
user.loggedIn = true;
|
|
99
|
-
user.logInError = false;
|
|
100
|
-
user.logInErrorMessage = "";
|
|
101
|
-
} else {
|
|
102
|
-
user.email = "";
|
|
103
|
-
user.uid = null;
|
|
104
|
-
user.loggedIn = false;
|
|
105
|
-
user.logInError = false;
|
|
106
|
-
user.logInErrorMessage = "";
|
|
107
|
-
}
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
// Composable to logout
|
|
111
|
-
export const logOut = (): void => {
|
|
112
|
-
signOut(auth)
|
|
113
|
-
.then(() => {
|
|
114
|
-
Object.keys(unsubscibe).forEach((key) => {
|
|
115
|
-
if (unsubscibe[key] instanceof Function) {
|
|
116
|
-
unsubscibe[key]();
|
|
117
|
-
unsubscibe[key] = null;
|
|
118
|
-
}
|
|
119
|
-
});
|
|
120
|
-
})
|
|
121
|
-
.catch(() => {
|
|
122
|
-
// Do nothing
|
|
123
|
-
});
|
|
124
|
-
};
|
|
125
|
-
|
|
126
|
-
// Composable to login and set persistence
|
|
127
|
-
export const logIn = (credentials: Credentials, isPersistant = false): void => {
|
|
128
|
-
logOut();
|
|
129
|
-
let persistence: Persistence = browserSessionPersistence;
|
|
130
|
-
if (isPersistant) {
|
|
131
|
-
persistence = browserLocalPersistence;
|
|
132
|
-
}
|
|
133
|
-
setPersistence(auth, persistence)
|
|
134
|
-
.then(() => {
|
|
135
|
-
signInWithEmailAndPassword(auth, credentials.email, credentials.password)
|
|
136
|
-
.then(() => {
|
|
137
|
-
// do nothing
|
|
138
|
-
})
|
|
139
|
-
.catch((error) => {
|
|
140
|
-
user.email = "";
|
|
141
|
-
user.uid = null;
|
|
142
|
-
|
|
143
|
-
user.loggedIn = false;
|
|
144
|
-
user.logInError = true;
|
|
145
|
-
user.logInErrorMessage = error.code + ": " + error.message;
|
|
146
|
-
});
|
|
147
|
-
})
|
|
148
|
-
.catch((error) => {
|
|
149
|
-
user.email = "";
|
|
150
|
-
user.uid = null;
|
|
151
|
-
|
|
152
|
-
user.loggedIn = false;
|
|
153
|
-
user.logInError = true;
|
|
154
|
-
user.logInErrorMessage = error.code + ": " + error.message;
|
|
155
|
-
});
|
|
156
|
-
};
|
|
157
|
-
|
|
158
|
-
// Keeping this for reference on how to Type a Ref.
|
|
159
|
-
// export const user = ref<UserDataObject>({
|
|
160
|
-
// uid: null,
|
|
161
|
-
// email: "",
|
|
162
|
-
// loggedIn: false,
|
|
163
|
-
// logInError: false,
|
|
164
|
-
// logInErrorMessage: ""
|
|
165
|
-
// });
|
|
166
|
-
|
|
167
|
-
// Simple Store Items (add matching key per firebase collection)
|
|
168
|
-
export const data: CollectionDataObject = reactive({});
|
|
169
|
-
export const unsubscibe: CollectionUnsubscribeObject = reactive({});
|
|
170
|
-
export const user: UserDataObject = reactive({
|
|
171
|
-
uid: null,
|
|
172
|
-
email: "",
|
|
173
|
-
loggedIn: false,
|
|
174
|
-
logInError: false,
|
|
175
|
-
logInErrorMessage: ""
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
export const getDocData = async (
|
|
179
|
-
collectionPath: string,
|
|
180
|
-
docId: string
|
|
181
|
-
): Promise<{ [key: string]: unknown }> => {
|
|
182
|
-
const docRef = doc(db, collectionPath, docId);
|
|
183
|
-
const docSnap = await getDoc(docRef);
|
|
184
|
-
const docData = docSnap.data();
|
|
185
|
-
docData.docId = docSnap.id;
|
|
186
|
-
return docData;
|
|
187
|
-
};
|
|
188
|
-
|
|
189
|
-
export const getStaticData = async (
|
|
190
|
-
collectionPath: string,
|
|
191
|
-
queryList: FirestoreQuery[] = [],
|
|
192
|
-
orderList: FirestoreOrderBy[] = [],
|
|
193
|
-
max = 0,
|
|
194
|
-
last: DocumentData | null = null
|
|
195
|
-
): Promise<StaticDataResult> => {
|
|
196
|
-
const data: object = {};
|
|
197
|
-
|
|
198
|
-
const q = getQuery(collectionPath, queryList, orderList, max, last);
|
|
199
|
-
|
|
200
|
-
const docs = await getDocs(q);
|
|
201
|
-
const nextLast: DocumentData = docs.docs[docs.docs.length - 1];
|
|
202
|
-
|
|
203
|
-
docs.forEach((doc) => {
|
|
204
|
-
const item = doc.data();
|
|
205
|
-
item.docId = doc.id;
|
|
206
|
-
data[doc.id] = item;
|
|
207
|
-
});
|
|
208
|
-
return { data, next: nextLast };
|
|
209
|
-
};
|
|
210
|
-
|
|
211
|
-
// Class for wrapping a getSaticData to handle pagination
|
|
212
|
-
export class SearchStaticData {
|
|
213
|
-
collectionPath = "";
|
|
214
|
-
queryList: FirestoreQuery[] = [];
|
|
215
|
-
orderList: FirestoreOrderBy[] = [];
|
|
216
|
-
max = 0;
|
|
217
|
-
|
|
218
|
-
results = reactive({
|
|
219
|
-
data: {},
|
|
220
|
-
pagination: [],
|
|
221
|
-
staticIsLastPage: true,
|
|
222
|
-
staticIsFirstPage: true,
|
|
223
|
-
staticCurrentPage: ""
|
|
224
|
-
});
|
|
225
|
-
|
|
226
|
-
prev = async (): Promise<void> => {
|
|
227
|
-
const findIndex = this.results.pagination.findIndex(
|
|
228
|
-
(x) => x.key === this.results.staticCurrentPage
|
|
229
|
-
);
|
|
230
|
-
let last = null;
|
|
231
|
-
if (findIndex === 1) {
|
|
232
|
-
this.results.staticCurrentPage = "";
|
|
233
|
-
this.results.staticIsLastPage = false;
|
|
234
|
-
this.results.staticIsFirstPage = true;
|
|
235
|
-
} else {
|
|
236
|
-
last = this.results.pagination[findIndex - 2].next;
|
|
237
|
-
this.results.staticCurrentPage =
|
|
238
|
-
this.results.pagination[findIndex - 2].key;
|
|
239
|
-
}
|
|
240
|
-
await this.afterNextPrev(last);
|
|
241
|
-
};
|
|
242
|
-
|
|
243
|
-
next = async (): Promise<void> => {
|
|
244
|
-
const findIndex = this.results.pagination.findIndex(
|
|
245
|
-
(x) => x.key === this.results.staticCurrentPage
|
|
246
|
-
);
|
|
247
|
-
const last = this.results.pagination[findIndex].next;
|
|
248
|
-
if (this.results.pagination.length === 1) {
|
|
249
|
-
this.results.staticIsFirstPage = true;
|
|
250
|
-
} else {
|
|
251
|
-
this.results.staticIsFirstPage = false;
|
|
252
|
-
}
|
|
253
|
-
await this.afterNextPrev(last);
|
|
254
|
-
};
|
|
255
|
-
|
|
256
|
-
afterNextPrev = async (last): Promise<void> => {
|
|
257
|
-
let results = await getStaticData(
|
|
258
|
-
"users",
|
|
259
|
-
this.queryList,
|
|
260
|
-
this.orderList,
|
|
261
|
-
this.max,
|
|
262
|
-
last
|
|
263
|
-
);
|
|
264
|
-
|
|
265
|
-
if (last && Object.keys(results.data).length === 0) {
|
|
266
|
-
this.results.staticIsLastPage = true;
|
|
267
|
-
if (this.results.pagination.length === 1) {
|
|
268
|
-
last = null;
|
|
269
|
-
this.results.staticCurrentPage = "";
|
|
270
|
-
this.results.staticIsFirstPage = true;
|
|
271
|
-
} else {
|
|
272
|
-
last = this.results.pagination[this.results.pagination.length - 2].next;
|
|
273
|
-
this.results.staticCurrentPage =
|
|
274
|
-
this.results.pagination[this.results.pagination.length - 2].key;
|
|
275
|
-
}
|
|
276
|
-
results = await getStaticData(
|
|
277
|
-
"users",
|
|
278
|
-
this.queryList,
|
|
279
|
-
this.orderList,
|
|
280
|
-
this.max,
|
|
281
|
-
last
|
|
282
|
-
);
|
|
283
|
-
} else {
|
|
284
|
-
this.results.staticIsLastPage = false;
|
|
285
|
-
if (this.results.pagination.length === 1) {
|
|
286
|
-
this.results.staticIsFirstPage = false;
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
this.results.data = results.data;
|
|
290
|
-
this.results.staticCurrentPage = results.next.id;
|
|
291
|
-
if (!this.results.staticIsLastPage) {
|
|
292
|
-
if (results.next) {
|
|
293
|
-
const findItem = this.results.pagination.find(
|
|
294
|
-
(x) => x.key === results.next.id
|
|
295
|
-
);
|
|
296
|
-
if (!findItem) {
|
|
297
|
-
this.results.pagination.push({
|
|
298
|
-
key: results.next.id,
|
|
299
|
-
next: results.next
|
|
300
|
-
});
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
};
|
|
305
|
-
|
|
306
|
-
getData = async (
|
|
307
|
-
collectionPath: string,
|
|
308
|
-
queryList: FirestoreQuery[] = [],
|
|
309
|
-
orderList: FirestoreOrderBy[] = [],
|
|
310
|
-
max = 0
|
|
311
|
-
): Promise<void> => {
|
|
312
|
-
this.collectionPath = collectionPath;
|
|
313
|
-
this.queryList = queryList;
|
|
314
|
-
this.orderList = orderList;
|
|
315
|
-
this.max = max;
|
|
316
|
-
this.results.staticIsLastPage = true;
|
|
317
|
-
this.results.staticIsFirstPage = true;
|
|
318
|
-
this.results.staticCurrentPage = "";
|
|
319
|
-
this.results.pagination = [];
|
|
320
|
-
this.results.pagination = [];
|
|
321
|
-
this.results.data = {};
|
|
322
|
-
const results = await getStaticData(
|
|
323
|
-
collectionPath,
|
|
324
|
-
queryList,
|
|
325
|
-
orderList,
|
|
326
|
-
max
|
|
327
|
-
);
|
|
328
|
-
if (Object.keys(results.data).length > 0) {
|
|
329
|
-
this.results.staticIsLastPage = false;
|
|
330
|
-
this.results.data = results.data;
|
|
331
|
-
this.results.staticCurrentPage = results.next.id;
|
|
332
|
-
this.results.pagination.push({
|
|
333
|
-
key: results.next.id,
|
|
334
|
-
next: results.next
|
|
335
|
-
});
|
|
336
|
-
} else {
|
|
337
|
-
this.results.staticIsLastPage = true;
|
|
338
|
-
this.results.staticIsFirstPage = true;
|
|
339
|
-
}
|
|
340
|
-
};
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
// Composable to start snapshot listener and set unsubscribe function
|
|
344
|
-
export const startSnapshot = (
|
|
345
|
-
collectionPath: string,
|
|
346
|
-
queryList: FirestoreQuery[] = [],
|
|
347
|
-
orderList: FirestoreOrderBy[] = [],
|
|
348
|
-
max = 0
|
|
349
|
-
): void => {
|
|
350
|
-
data[collectionPath] = {};
|
|
351
|
-
const q = getQuery(collectionPath, queryList, orderList, max);
|
|
352
|
-
const unsubscribe = onSnapshot(q, (querySnapshot) => {
|
|
353
|
-
const items = {};
|
|
354
|
-
querySnapshot.forEach((doc) => {
|
|
355
|
-
const item = doc.data();
|
|
356
|
-
item.docId = doc.id;
|
|
357
|
-
items[doc.id] = item;
|
|
358
|
-
});
|
|
359
|
-
data[collectionPath] = items;
|
|
360
|
-
});
|
|
361
|
-
unsubscibe[collectionPath] = unsubscribe;
|
|
362
|
-
};
|
|
363
|
-
|
|
364
|
-
const getQuery = (
|
|
365
|
-
collectionPath: string,
|
|
366
|
-
queryList: FirestoreQuery[] = [],
|
|
367
|
-
orderList: FirestoreOrderBy[] = [],
|
|
368
|
-
max = 0,
|
|
369
|
-
after: DocumentData | null = null
|
|
370
|
-
): Query => {
|
|
371
|
-
const queryConditions: QueryConstraint[] = queryList.map((condition) =>
|
|
372
|
-
where(condition.field, condition.operator, condition.value)
|
|
373
|
-
);
|
|
374
|
-
|
|
375
|
-
const orderConditions: QueryConstraint[] = orderList.map((condition) =>
|
|
376
|
-
orderBy(condition.field, condition.direction)
|
|
377
|
-
);
|
|
378
|
-
|
|
379
|
-
let limitList: FirestoreLimit[] = [];
|
|
380
|
-
if (max > 0) {
|
|
381
|
-
limitList = [{ limit: max }];
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
const limitConditions: QueryConstraint[] = limitList.map((condition) =>
|
|
385
|
-
limit(condition.limit)
|
|
386
|
-
);
|
|
387
|
-
if (after) {
|
|
388
|
-
return query(
|
|
389
|
-
collection(db, collectionPath),
|
|
390
|
-
...queryConditions,
|
|
391
|
-
...orderConditions,
|
|
392
|
-
...limitConditions,
|
|
393
|
-
startAfter(after)
|
|
394
|
-
);
|
|
395
|
-
}
|
|
396
|
-
return query(
|
|
397
|
-
collection(db, collectionPath),
|
|
398
|
-
...queryConditions,
|
|
399
|
-
...orderConditions,
|
|
400
|
-
...limitConditions
|
|
401
|
-
);
|
|
402
|
-
};
|
|
403
|
-
|
|
404
|
-
// Composable to update/add a document
|
|
405
|
-
export const storeDoc = async (
|
|
406
|
-
collectionPath: string,
|
|
407
|
-
item: object
|
|
408
|
-
): Promise<void> => {
|
|
409
|
-
const cloneItem = JSON.parse(JSON.stringify(item));
|
|
410
|
-
const currentTime = new Date().getTime();
|
|
411
|
-
cloneItem.last_updated = currentTime;
|
|
412
|
-
cloneItem.uid = user.uid;
|
|
413
|
-
if (!Object.prototype.hasOwnProperty.call(cloneItem, "doc_created_at")) {
|
|
414
|
-
cloneItem.doc_created_at = currentTime;
|
|
415
|
-
}
|
|
416
|
-
if (Object.prototype.hasOwnProperty.call(cloneItem, "docId")) {
|
|
417
|
-
const docId = cloneItem.docId;
|
|
418
|
-
if (Object.prototype.hasOwnProperty.call(data, collectionPath)) {
|
|
419
|
-
data[collectionPath][docId] = cloneItem;
|
|
420
|
-
}
|
|
421
|
-
setDoc(doc(db, collectionPath, docId), cloneItem);
|
|
422
|
-
} else {
|
|
423
|
-
const docRef = await addDoc(collection(db, collectionPath), cloneItem);
|
|
424
|
-
if (Object.prototype.hasOwnProperty.call(data, collectionPath)) {
|
|
425
|
-
data[collectionPath][docRef.id] = cloneItem;
|
|
426
|
-
}
|
|
427
|
-
storeDoc(collectionPath, { ...cloneItem, docId: docRef.id });
|
|
428
|
-
}
|
|
429
|
-
};
|
|
430
|
-
|
|
431
|
-
// Composable to delete a document
|
|
432
|
-
export const removeDoc = (collectionPath: string, docId: string): void => {
|
|
433
|
-
// Just in case getting collection back from firebase is slow:
|
|
434
|
-
if (Object.prototype.hasOwnProperty.call(data, collectionPath)) {
|
|
435
|
-
if (Object.prototype.hasOwnProperty.call(data[collectionPath], docId)) {
|
|
436
|
-
delete data[collectionPath][docId];
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
deleteDoc(doc(db, collectionPath, docId));
|
|
440
|
-
};
|
|
441
|
-
|
|
442
|
-
// Composable to stop snapshot listener
|
|
443
|
-
export const stopSnapshot = (collectionPath: string): void => {
|
|
444
|
-
if (unsubscibe[collectionPath] instanceof Function) {
|
|
445
|
-
unsubscibe[collectionPath]();
|
|
446
|
-
unsubscibe[collectionPath] = null;
|
|
1
|
+
import { EdgeFirebase } from "./edgeFirebase";
|
|
2
|
+
export default {
|
|
3
|
+
install: (app, options) => {
|
|
4
|
+
const eFb = new EdgeFirebase(options);
|
|
5
|
+
app.provide("edgeFirebase", eFb);
|
|
447
6
|
}
|
|
448
7
|
};
|
|
8
|
+
export { EdgeFirebase };
|