canvasframework 0.4.4 → 0.4.6
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/components/QRCodeReader.js +2 -2
- package/components/TimePicker.js +2 -2
- package/core/CanvasFramework.js +61 -11
- package/index.js +10 -1
- package/package.json +1 -1
- package/utils/FirebaseAuth.js +653 -0
- package/utils/FirebaseCore.js +246 -0
- package/utils/FirebaseFirestore.js +581 -0
- package/utils/FirebaseFunctions.js +97 -0
- package/utils/FirebaseRealtimeDB.js +498 -0
- package/utils/FirebaseStorage.js +612 -0
- package/utils/PayPalPayment.js +678 -0
- package/utils/StripePayment.js +552 -0
|
@@ -0,0 +1,581 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FirebaseFirestore - Utilitaire pour Firebase Firestore
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* const firestore = new FirebaseFirestore(firebaseCore);
|
|
6
|
+
* await firestore.add('users', { name: 'John', age: 30 });
|
|
7
|
+
* const users = await firestore.getCollection('users');
|
|
8
|
+
* firestore.listenDocument('users/123', (doc) => console.log(doc.data()));
|
|
9
|
+
*/
|
|
10
|
+
class FirebaseFirestore {
|
|
11
|
+
constructor(firebaseCore) {
|
|
12
|
+
this.core = firebaseCore;
|
|
13
|
+
this.db = null;
|
|
14
|
+
this.listeners = new Map();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Initialiser Firestore
|
|
19
|
+
*/
|
|
20
|
+
initialize() {
|
|
21
|
+
if (!this.db) {
|
|
22
|
+
this.db = this.core.getFirestore();
|
|
23
|
+
}
|
|
24
|
+
return this.db;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Obtenir une référence de collection
|
|
29
|
+
*/
|
|
30
|
+
collection(path) {
|
|
31
|
+
if (!this.db) this.initialize();
|
|
32
|
+
return this.db.collection(path);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Obtenir une référence de document
|
|
37
|
+
*/
|
|
38
|
+
doc(path) {
|
|
39
|
+
if (!this.db) this.initialize();
|
|
40
|
+
return this.db.doc(path);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// ==================== OPERATIONS CRUD ====================
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Ajouter un document avec ID auto-généré
|
|
47
|
+
*/
|
|
48
|
+
async add(collectionPath, data) {
|
|
49
|
+
try {
|
|
50
|
+
const docRef = await this.collection(collectionPath).add({
|
|
51
|
+
...data,
|
|
52
|
+
createdAt: firebase.firestore.FieldValue.serverTimestamp(),
|
|
53
|
+
updatedAt: firebase.firestore.FieldValue.serverTimestamp()
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
success: true,
|
|
58
|
+
id: docRef.id,
|
|
59
|
+
ref: docRef
|
|
60
|
+
};
|
|
61
|
+
} catch (error) {
|
|
62
|
+
console.error(`❌ Erreur add ${collectionPath}:`, error);
|
|
63
|
+
throw error;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Créer/Écraser un document avec ID spécifique
|
|
69
|
+
*/
|
|
70
|
+
async set(documentPath, data, options = {}) {
|
|
71
|
+
try {
|
|
72
|
+
await this.doc(documentPath).set({
|
|
73
|
+
...data,
|
|
74
|
+
createdAt: firebase.firestore.FieldValue.serverTimestamp(),
|
|
75
|
+
updatedAt: firebase.firestore.FieldValue.serverTimestamp()
|
|
76
|
+
}, options);
|
|
77
|
+
|
|
78
|
+
return { success: true };
|
|
79
|
+
} catch (error) {
|
|
80
|
+
console.error(`❌ Erreur set ${documentPath}:`, error);
|
|
81
|
+
throw error;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Mettre à jour un document (merge)
|
|
87
|
+
*/
|
|
88
|
+
async update(documentPath, data) {
|
|
89
|
+
try {
|
|
90
|
+
await this.doc(documentPath).update({
|
|
91
|
+
...data,
|
|
92
|
+
updatedAt: firebase.firestore.FieldValue.serverTimestamp()
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
return { success: true };
|
|
96
|
+
} catch (error) {
|
|
97
|
+
console.error(`❌ Erreur update ${documentPath}:`, error);
|
|
98
|
+
throw error;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Récupérer un document
|
|
104
|
+
*/
|
|
105
|
+
async get(documentPath) {
|
|
106
|
+
try {
|
|
107
|
+
const doc = await this.doc(documentPath).get();
|
|
108
|
+
|
|
109
|
+
if (doc.exists) {
|
|
110
|
+
return {
|
|
111
|
+
id: doc.id,
|
|
112
|
+
...doc.data(),
|
|
113
|
+
ref: doc.ref
|
|
114
|
+
};
|
|
115
|
+
} else {
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
} catch (error) {
|
|
119
|
+
console.error(`❌ Erreur get ${documentPath}:`, error);
|
|
120
|
+
throw error;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Récupérer une collection
|
|
126
|
+
*/
|
|
127
|
+
async getCollection(collectionPath, options = {}) {
|
|
128
|
+
try {
|
|
129
|
+
let query = this.collection(collectionPath);
|
|
130
|
+
|
|
131
|
+
// Appliquer les filtres
|
|
132
|
+
if (options.where) {
|
|
133
|
+
options.where.forEach(([field, operator, value]) => {
|
|
134
|
+
query = query.where(field, operator, value);
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Tri
|
|
139
|
+
if (options.orderBy) {
|
|
140
|
+
const [field, direction = 'asc'] = Array.isArray(options.orderBy)
|
|
141
|
+
? options.orderBy
|
|
142
|
+
: [options.orderBy];
|
|
143
|
+
query = query.orderBy(field, direction);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Limite
|
|
147
|
+
if (options.limit) {
|
|
148
|
+
query = query.limit(options.limit);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Pagination
|
|
152
|
+
if (options.startAfter) {
|
|
153
|
+
query = query.startAfter(options.startAfter);
|
|
154
|
+
}
|
|
155
|
+
if (options.startAt) {
|
|
156
|
+
query = query.startAt(options.startAt);
|
|
157
|
+
}
|
|
158
|
+
if (options.endBefore) {
|
|
159
|
+
query = query.endBefore(options.endBefore);
|
|
160
|
+
}
|
|
161
|
+
if (options.endAt) {
|
|
162
|
+
query = query.endAt(options.endAt);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const snapshot = await query.get();
|
|
166
|
+
|
|
167
|
+
const docs = snapshot.docs.map(doc => ({
|
|
168
|
+
id: doc.id,
|
|
169
|
+
...doc.data(),
|
|
170
|
+
ref: doc.ref
|
|
171
|
+
}));
|
|
172
|
+
|
|
173
|
+
return {
|
|
174
|
+
docs,
|
|
175
|
+
size: snapshot.size,
|
|
176
|
+
empty: snapshot.empty,
|
|
177
|
+
lastDoc: snapshot.docs[snapshot.docs.length - 1]
|
|
178
|
+
};
|
|
179
|
+
} catch (error) {
|
|
180
|
+
console.error(`❌ Erreur getCollection ${collectionPath}:`, error);
|
|
181
|
+
throw error;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Supprimer un document
|
|
187
|
+
*/
|
|
188
|
+
async delete(documentPath) {
|
|
189
|
+
try {
|
|
190
|
+
await this.doc(documentPath).delete();
|
|
191
|
+
return { success: true };
|
|
192
|
+
} catch (error) {
|
|
193
|
+
console.error(`❌ Erreur delete ${documentPath}:`, error);
|
|
194
|
+
throw error;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// ==================== LISTENERS EN TEMPS RÉEL ====================
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Écouter un document en temps réel
|
|
202
|
+
*/
|
|
203
|
+
listenDocument(documentPath, callback, onError = null) {
|
|
204
|
+
const listenerId = `doc_${documentPath}_${Date.now()}`;
|
|
205
|
+
|
|
206
|
+
const unsubscribe = this.doc(documentPath).onSnapshot(
|
|
207
|
+
(doc) => {
|
|
208
|
+
if (doc.exists) {
|
|
209
|
+
callback({
|
|
210
|
+
id: doc.id,
|
|
211
|
+
...doc.data(),
|
|
212
|
+
ref: doc.ref
|
|
213
|
+
}, null);
|
|
214
|
+
} else {
|
|
215
|
+
callback(null, null);
|
|
216
|
+
}
|
|
217
|
+
},
|
|
218
|
+
(error) => {
|
|
219
|
+
console.error(`❌ Erreur listener ${documentPath}:`, error);
|
|
220
|
+
if (onError) onError(error);
|
|
221
|
+
}
|
|
222
|
+
);
|
|
223
|
+
|
|
224
|
+
this.listeners.set(listenerId, unsubscribe);
|
|
225
|
+
|
|
226
|
+
return () => this.unlisten(listenerId);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Écouter une collection en temps réel
|
|
231
|
+
*/
|
|
232
|
+
listenCollection(collectionPath, callback, options = {}, onError = null) {
|
|
233
|
+
const listenerId = `col_${collectionPath}_${Date.now()}`;
|
|
234
|
+
|
|
235
|
+
let query = this.collection(collectionPath);
|
|
236
|
+
|
|
237
|
+
// Appliquer les filtres
|
|
238
|
+
if (options.where) {
|
|
239
|
+
options.where.forEach(([field, operator, value]) => {
|
|
240
|
+
query = query.where(field, operator, value);
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (options.orderBy) {
|
|
245
|
+
const [field, direction = 'asc'] = Array.isArray(options.orderBy)
|
|
246
|
+
? options.orderBy
|
|
247
|
+
: [options.orderBy];
|
|
248
|
+
query = query.orderBy(field, direction);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (options.limit) {
|
|
252
|
+
query = query.limit(options.limit);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const unsubscribe = query.onSnapshot(
|
|
256
|
+
(snapshot) => {
|
|
257
|
+
const docs = snapshot.docs.map(doc => ({
|
|
258
|
+
id: doc.id,
|
|
259
|
+
...doc.data(),
|
|
260
|
+
ref: doc.ref
|
|
261
|
+
}));
|
|
262
|
+
|
|
263
|
+
// Détecter les changements
|
|
264
|
+
const changes = snapshot.docChanges().map(change => ({
|
|
265
|
+
type: change.type, // 'added', 'modified', 'removed'
|
|
266
|
+
doc: {
|
|
267
|
+
id: change.doc.id,
|
|
268
|
+
...change.doc.data()
|
|
269
|
+
},
|
|
270
|
+
oldIndex: change.oldIndex,
|
|
271
|
+
newIndex: change.newIndex
|
|
272
|
+
}));
|
|
273
|
+
|
|
274
|
+
callback({
|
|
275
|
+
docs,
|
|
276
|
+
changes,
|
|
277
|
+
size: snapshot.size,
|
|
278
|
+
empty: snapshot.empty
|
|
279
|
+
}, null);
|
|
280
|
+
},
|
|
281
|
+
(error) => {
|
|
282
|
+
console.error(`❌ Erreur listener ${collectionPath}:`, error);
|
|
283
|
+
if (onError) onError(error);
|
|
284
|
+
}
|
|
285
|
+
);
|
|
286
|
+
|
|
287
|
+
this.listeners.set(listenerId, unsubscribe);
|
|
288
|
+
|
|
289
|
+
return () => this.unlisten(listenerId);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Arrêter un listener
|
|
294
|
+
*/
|
|
295
|
+
unlisten(listenerId) {
|
|
296
|
+
const unsubscribe = this.listeners.get(listenerId);
|
|
297
|
+
if (unsubscribe) {
|
|
298
|
+
unsubscribe();
|
|
299
|
+
this.listeners.delete(listenerId);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Arrêter tous les listeners
|
|
305
|
+
*/
|
|
306
|
+
unlistenAll() {
|
|
307
|
+
this.listeners.forEach((unsubscribe) => {
|
|
308
|
+
unsubscribe();
|
|
309
|
+
});
|
|
310
|
+
this.listeners.clear();
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// ==================== QUERIES AVANCÉES ====================
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Requête simple
|
|
317
|
+
*/
|
|
318
|
+
async query(collectionPath, field, operator, value, options = {}) {
|
|
319
|
+
return this.getCollection(collectionPath, {
|
|
320
|
+
where: [[field, operator, value]],
|
|
321
|
+
...options
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Requête avec multiples conditions
|
|
327
|
+
*/
|
|
328
|
+
async queryMultiple(collectionPath, conditions, options = {}) {
|
|
329
|
+
return this.getCollection(collectionPath, {
|
|
330
|
+
where: conditions,
|
|
331
|
+
...options
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Recherche plein texte (simple)
|
|
337
|
+
*/
|
|
338
|
+
async searchByPrefix(collectionPath, field, prefix, options = {}) {
|
|
339
|
+
const end = prefix.replace(/.$/, c => String.fromCharCode(c.charCodeAt(0) + 1));
|
|
340
|
+
|
|
341
|
+
return this.getCollection(collectionPath, {
|
|
342
|
+
where: [
|
|
343
|
+
[field, '>=', prefix],
|
|
344
|
+
[field, '<', end]
|
|
345
|
+
],
|
|
346
|
+
...options
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Pagination (récupérer la page suivante)
|
|
352
|
+
*/
|
|
353
|
+
async getNextPage(collectionPath, lastDoc, pageSize = 10, options = {}) {
|
|
354
|
+
return this.getCollection(collectionPath, {
|
|
355
|
+
...options,
|
|
356
|
+
limit: pageSize,
|
|
357
|
+
startAfter: lastDoc
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// ==================== BATCH OPERATIONS ====================
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Opération batch (max 500 opérations)
|
|
365
|
+
*/
|
|
366
|
+
batch() {
|
|
367
|
+
if (!this.db) this.initialize();
|
|
368
|
+
return this.db.batch();
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Écriture batch
|
|
373
|
+
*/
|
|
374
|
+
async batchWrite(operations) {
|
|
375
|
+
try {
|
|
376
|
+
const batch = this.batch();
|
|
377
|
+
|
|
378
|
+
operations.forEach(({ type, path, data }) => {
|
|
379
|
+
const docRef = this.doc(path);
|
|
380
|
+
|
|
381
|
+
switch (type) {
|
|
382
|
+
case 'set':
|
|
383
|
+
batch.set(docRef, {
|
|
384
|
+
...data,
|
|
385
|
+
updatedAt: firebase.firestore.FieldValue.serverTimestamp()
|
|
386
|
+
});
|
|
387
|
+
break;
|
|
388
|
+
case 'update':
|
|
389
|
+
batch.update(docRef, {
|
|
390
|
+
...data,
|
|
391
|
+
updatedAt: firebase.firestore.FieldValue.serverTimestamp()
|
|
392
|
+
});
|
|
393
|
+
break;
|
|
394
|
+
case 'delete':
|
|
395
|
+
batch.delete(docRef);
|
|
396
|
+
break;
|
|
397
|
+
}
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
await batch.commit();
|
|
401
|
+
return { success: true };
|
|
402
|
+
} catch (error) {
|
|
403
|
+
console.error('❌ Erreur batch write:', error);
|
|
404
|
+
throw error;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// ==================== TRANSACTIONS ====================
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Transaction
|
|
412
|
+
*/
|
|
413
|
+
async transaction(updateFunction) {
|
|
414
|
+
try {
|
|
415
|
+
if (!this.db) this.initialize();
|
|
416
|
+
|
|
417
|
+
const result = await this.db.runTransaction(updateFunction);
|
|
418
|
+
return result;
|
|
419
|
+
} catch (error) {
|
|
420
|
+
console.error('❌ Erreur transaction:', error);
|
|
421
|
+
throw error;
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* Incrémenter un champ
|
|
427
|
+
*/
|
|
428
|
+
async increment(documentPath, field, amount = 1) {
|
|
429
|
+
try {
|
|
430
|
+
await this.update(documentPath, {
|
|
431
|
+
[field]: firebase.firestore.FieldValue.increment(amount)
|
|
432
|
+
});
|
|
433
|
+
return { success: true };
|
|
434
|
+
} catch (error) {
|
|
435
|
+
console.error(`❌ Erreur increment ${documentPath}:`, error);
|
|
436
|
+
throw error;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* Ajouter à un array
|
|
442
|
+
*/
|
|
443
|
+
async arrayUnion(documentPath, field, values) {
|
|
444
|
+
try {
|
|
445
|
+
await this.update(documentPath, {
|
|
446
|
+
[field]: firebase.firestore.FieldValue.arrayUnion(...values)
|
|
447
|
+
});
|
|
448
|
+
return { success: true };
|
|
449
|
+
} catch (error) {
|
|
450
|
+
console.error(`❌ Erreur arrayUnion ${documentPath}:`, error);
|
|
451
|
+
throw error;
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
/**
|
|
456
|
+
* Retirer d'un array
|
|
457
|
+
*/
|
|
458
|
+
async arrayRemove(documentPath, field, values) {
|
|
459
|
+
try {
|
|
460
|
+
await this.update(documentPath, {
|
|
461
|
+
[field]: firebase.firestore.FieldValue.arrayRemove(...values)
|
|
462
|
+
});
|
|
463
|
+
return { success: true };
|
|
464
|
+
} catch (error) {
|
|
465
|
+
console.error(`❌ Erreur arrayRemove ${documentPath}:`, error);
|
|
466
|
+
throw error;
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// ==================== HELPERS ====================
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* Générer un ID unique
|
|
474
|
+
*/
|
|
475
|
+
generateId() {
|
|
476
|
+
if (!this.db) this.initialize();
|
|
477
|
+
return this.db.collection('_').doc().id;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* Obtenir le timestamp serveur
|
|
482
|
+
*/
|
|
483
|
+
getServerTimestamp() {
|
|
484
|
+
return firebase.firestore.FieldValue.serverTimestamp();
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
/**
|
|
488
|
+
* Supprimer un champ
|
|
489
|
+
*/
|
|
490
|
+
getDeleteField() {
|
|
491
|
+
return firebase.firestore.FieldValue.delete();
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* Vérifier si un document existe
|
|
496
|
+
*/
|
|
497
|
+
async exists(documentPath) {
|
|
498
|
+
const doc = await this.doc(documentPath).get();
|
|
499
|
+
return doc.exists;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/**
|
|
503
|
+
* Compter les documents d'une collection
|
|
504
|
+
*/
|
|
505
|
+
async count(collectionPath, options = {}) {
|
|
506
|
+
const result = await this.getCollection(collectionPath, options);
|
|
507
|
+
return result.size;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
/**
|
|
511
|
+
* Créer un index composé (helper pour les logs)
|
|
512
|
+
*/
|
|
513
|
+
logCompositeIndexUrl(collectionPath, fields) {
|
|
514
|
+
console.log(`📋 Index composé recommandé pour ${collectionPath}:`);
|
|
515
|
+
console.log(`Champs: ${fields.join(', ')}`);
|
|
516
|
+
console.log('Créez cet index dans la console Firebase Firestore');
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
// ==================== OFFLINE ====================
|
|
520
|
+
|
|
521
|
+
/**
|
|
522
|
+
* Activer la persistance locale
|
|
523
|
+
*/
|
|
524
|
+
async enablePersistence(options = {}) {
|
|
525
|
+
try {
|
|
526
|
+
if (!this.db) this.initialize();
|
|
527
|
+
|
|
528
|
+
await this.db.enablePersistence({
|
|
529
|
+
synchronizeTabs: options.synchronizeTabs !== false
|
|
530
|
+
});
|
|
531
|
+
return { success: true };
|
|
532
|
+
} catch (error) {
|
|
533
|
+
if (error.code === 'failed-precondition') {
|
|
534
|
+
console.warn('⚠️ Persistance: plusieurs onglets ouverts');
|
|
535
|
+
} else if (error.code === 'unimplemented') {
|
|
536
|
+
console.warn('⚠️ Persistance non supportée');
|
|
537
|
+
}
|
|
538
|
+
throw error;
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
/**
|
|
543
|
+
* Désactiver le réseau (mode offline)
|
|
544
|
+
*/
|
|
545
|
+
async disableNetwork() {
|
|
546
|
+
try {
|
|
547
|
+
if (!this.db) this.initialize();
|
|
548
|
+
await this.db.disableNetwork();
|
|
549
|
+
return { success: true };
|
|
550
|
+
} catch (error) {
|
|
551
|
+
console.error('❌ Erreur disableNetwork:', error);
|
|
552
|
+
throw error;
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
/**
|
|
557
|
+
* Réactiver le réseau
|
|
558
|
+
*/
|
|
559
|
+
async enableNetwork() {
|
|
560
|
+
try {
|
|
561
|
+
if (!this.db) this.initialize();
|
|
562
|
+
await this.db.enableNetwork();
|
|
563
|
+
return { success: true };
|
|
564
|
+
} catch (error) {
|
|
565
|
+
console.error('❌ Erreur enableNetwork:', error);
|
|
566
|
+
throw error;
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
// ==================== CLEANUP ====================
|
|
571
|
+
|
|
572
|
+
/**
|
|
573
|
+
* Nettoyer les ressources
|
|
574
|
+
*/
|
|
575
|
+
destroy() {
|
|
576
|
+
this.unlistenAll();
|
|
577
|
+
this.db = null;
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
export default FirebaseFirestore;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FirebaseFunctions - Utilitaire pour Firebase Cloud Functions
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* const functions = new FirebaseFunctions(firebaseCore);
|
|
6
|
+
* const result = await functions.call('addMessage', { text: 'Hello World' });
|
|
7
|
+
*/
|
|
8
|
+
class FirebaseFunctions {
|
|
9
|
+
constructor(firebaseCore, region = 'us-central1') {
|
|
10
|
+
this.core = firebaseCore;
|
|
11
|
+
this.region = region;
|
|
12
|
+
this.functions = null;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Initialiser Functions
|
|
17
|
+
*/
|
|
18
|
+
initialize() {
|
|
19
|
+
if (!this.functions) {
|
|
20
|
+
this.functions = this.core.getFunctions(this.region);
|
|
21
|
+
}
|
|
22
|
+
return this.functions;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Appeler une Cloud Function
|
|
27
|
+
*/
|
|
28
|
+
async call(functionName, data = {}) {
|
|
29
|
+
try {
|
|
30
|
+
if (!this.functions) this.initialize();
|
|
31
|
+
|
|
32
|
+
const callable = this.functions.httpsCallable(functionName);
|
|
33
|
+
const result = await callable(data);
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
success: true,
|
|
37
|
+
data: result.data
|
|
38
|
+
};
|
|
39
|
+
} catch (error) {
|
|
40
|
+
console.error(`❌ Erreur call ${functionName}:`, error);
|
|
41
|
+
throw this.formatError(error);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Appeler une fonction avec timeout personnalisé
|
|
47
|
+
*/
|
|
48
|
+
async callWithTimeout(functionName, data = {}, timeoutMs = 60000) {
|
|
49
|
+
try {
|
|
50
|
+
if (!this.functions) this.initialize();
|
|
51
|
+
|
|
52
|
+
const callable = this.functions.httpsCallable(functionName, {
|
|
53
|
+
timeout: timeoutMs
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const result = await callable(data);
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
success: true,
|
|
60
|
+
data: result.data
|
|
61
|
+
};
|
|
62
|
+
} catch (error) {
|
|
63
|
+
console.error(`❌ Erreur callWithTimeout ${functionName}:`, error);
|
|
64
|
+
throw this.formatError(error);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Formater les erreurs
|
|
70
|
+
*/
|
|
71
|
+
formatError(error) {
|
|
72
|
+
return {
|
|
73
|
+
code: error.code,
|
|
74
|
+
message: error.message,
|
|
75
|
+
details: error.details,
|
|
76
|
+
originalError: error
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Utiliser l'émulateur local
|
|
82
|
+
*/
|
|
83
|
+
useEmulator(host = 'localhost', port = 5001) {
|
|
84
|
+
if (!this.functions) this.initialize();
|
|
85
|
+
|
|
86
|
+
this.functions.useEmulator(host, port);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Nettoyer les ressources
|
|
91
|
+
*/
|
|
92
|
+
destroy() {
|
|
93
|
+
this.functions = null;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export default FirebaseFunctions;
|