canvasframework 0.4.3 → 0.4.5
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 +1 -0
- package/core/UIBuilder.js +2 -0
- 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,612 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FirebaseStorage - Utilitaire pour Firebase Cloud Storage
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* const storage = new FirebaseStorage(firebaseCore);
|
|
6
|
+
* await storage.uploadFile('images/photo.jpg', file);
|
|
7
|
+
* const url = await storage.getDownloadURL('images/photo.jpg');
|
|
8
|
+
* await storage.deleteFile('images/photo.jpg');
|
|
9
|
+
*/
|
|
10
|
+
class FirebaseStorage {
|
|
11
|
+
constructor(firebaseCore) {
|
|
12
|
+
this.core = firebaseCore;
|
|
13
|
+
this.storage = null;
|
|
14
|
+
this.uploadTasks = new Map();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Initialiser Storage
|
|
19
|
+
*/
|
|
20
|
+
initialize() {
|
|
21
|
+
if (!this.storage) {
|
|
22
|
+
this.storage = this.core.getStorage();
|
|
23
|
+
}
|
|
24
|
+
return this.storage;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Obtenir une référence
|
|
29
|
+
*/
|
|
30
|
+
ref(path = '') {
|
|
31
|
+
if (!this.storage) this.initialize();
|
|
32
|
+
return this.storage.ref(path);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// ==================== UPLOAD ====================
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Upload un fichier
|
|
39
|
+
*/
|
|
40
|
+
async uploadFile(path, file, metadata = {}, onProgress = null) {
|
|
41
|
+
try {
|
|
42
|
+
const storageRef = this.ref(path);
|
|
43
|
+
|
|
44
|
+
// Créer la tâche d'upload
|
|
45
|
+
const uploadTask = storageRef.put(file, {
|
|
46
|
+
contentType: file.type,
|
|
47
|
+
...metadata
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// Stocker la tâche
|
|
51
|
+
this.uploadTasks.set(path, uploadTask);
|
|
52
|
+
|
|
53
|
+
// Écouter la progression
|
|
54
|
+
if (onProgress) {
|
|
55
|
+
uploadTask.on('state_changed',
|
|
56
|
+
(snapshot) => {
|
|
57
|
+
const progress = (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
|
|
58
|
+
onProgress({
|
|
59
|
+
progress,
|
|
60
|
+
bytesTransferred: snapshot.bytesTransferred,
|
|
61
|
+
totalBytes: snapshot.totalBytes,
|
|
62
|
+
state: snapshot.state
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Attendre la fin de l'upload
|
|
69
|
+
const snapshot = await uploadTask;
|
|
70
|
+
|
|
71
|
+
// Récupérer l'URL de téléchargement
|
|
72
|
+
const downloadURL = await snapshot.ref.getDownloadURL();
|
|
73
|
+
|
|
74
|
+
// Nettoyer
|
|
75
|
+
this.uploadTasks.delete(path);
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
success: true,
|
|
79
|
+
url: downloadURL,
|
|
80
|
+
fullPath: snapshot.ref.fullPath,
|
|
81
|
+
name: snapshot.ref.name,
|
|
82
|
+
size: snapshot.metadata.size,
|
|
83
|
+
contentType: snapshot.metadata.contentType,
|
|
84
|
+
timeCreated: snapshot.metadata.timeCreated,
|
|
85
|
+
metadata: snapshot.metadata
|
|
86
|
+
};
|
|
87
|
+
} catch (error) {
|
|
88
|
+
console.error(`❌ Erreur uploadFile ${path}:`, error);
|
|
89
|
+
this.uploadTasks.delete(path);
|
|
90
|
+
throw error;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Upload depuis un Blob
|
|
96
|
+
*/
|
|
97
|
+
async uploadBlob(path, blob, metadata = {}, onProgress = null) {
|
|
98
|
+
return this.uploadFile(path, blob, metadata, onProgress);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Upload depuis une Data URL (base64)
|
|
103
|
+
*/
|
|
104
|
+
async uploadDataURL(path, dataURL, metadata = {}, onProgress = null) {
|
|
105
|
+
try {
|
|
106
|
+
const storageRef = this.ref(path);
|
|
107
|
+
|
|
108
|
+
const uploadTask = storageRef.putString(dataURL, 'data_url', metadata);
|
|
109
|
+
this.uploadTasks.set(path, uploadTask);
|
|
110
|
+
|
|
111
|
+
if (onProgress) {
|
|
112
|
+
uploadTask.on('state_changed',
|
|
113
|
+
(snapshot) => {
|
|
114
|
+
const progress = (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
|
|
115
|
+
onProgress({
|
|
116
|
+
progress,
|
|
117
|
+
bytesTransferred: snapshot.bytesTransferred,
|
|
118
|
+
totalBytes: snapshot.totalBytes,
|
|
119
|
+
state: snapshot.state
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const snapshot = await uploadTask;
|
|
126
|
+
const downloadURL = await snapshot.ref.getDownloadURL();
|
|
127
|
+
|
|
128
|
+
this.uploadTasks.delete(path);
|
|
129
|
+
|
|
130
|
+
return {
|
|
131
|
+
success: true,
|
|
132
|
+
url: downloadURL,
|
|
133
|
+
fullPath: snapshot.ref.fullPath,
|
|
134
|
+
name: snapshot.ref.name,
|
|
135
|
+
metadata: snapshot.metadata
|
|
136
|
+
};
|
|
137
|
+
} catch (error) {
|
|
138
|
+
console.error(`❌ Erreur uploadDataURL ${path}:`, error);
|
|
139
|
+
this.uploadTasks.delete(path);
|
|
140
|
+
throw error;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Upload avec compression d'image
|
|
146
|
+
*/
|
|
147
|
+
async uploadImageCompressed(path, file, options = {}, onProgress = null) {
|
|
148
|
+
try {
|
|
149
|
+
const {
|
|
150
|
+
maxWidth = 1920,
|
|
151
|
+
maxHeight = 1080,
|
|
152
|
+
quality = 0.8,
|
|
153
|
+
outputFormat = 'image/jpeg'
|
|
154
|
+
} = options;
|
|
155
|
+
|
|
156
|
+
// Créer un canvas pour la compression
|
|
157
|
+
const compressed = await this.compressImage(file, maxWidth, maxHeight, quality, outputFormat);
|
|
158
|
+
|
|
159
|
+
// Upload l'image compressée
|
|
160
|
+
return await this.uploadBlob(path, compressed, {
|
|
161
|
+
contentType: outputFormat
|
|
162
|
+
}, onProgress);
|
|
163
|
+
} catch (error) {
|
|
164
|
+
console.error(`❌ Erreur uploadImageCompressed ${path}:`, error);
|
|
165
|
+
throw error;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Upload multiple fichiers
|
|
171
|
+
*/
|
|
172
|
+
async uploadMultiple(files, basePath = '', onProgress = null) {
|
|
173
|
+
try {
|
|
174
|
+
const uploads = files.map((file, index) => {
|
|
175
|
+
const path = `${basePath}/${file.name}`;
|
|
176
|
+
|
|
177
|
+
return this.uploadFile(path, file, {}, (progress) => {
|
|
178
|
+
if (onProgress) {
|
|
179
|
+
onProgress({
|
|
180
|
+
fileIndex: index,
|
|
181
|
+
fileName: file.name,
|
|
182
|
+
...progress
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
const results = await Promise.all(uploads);
|
|
189
|
+
|
|
190
|
+
return {
|
|
191
|
+
success: true,
|
|
192
|
+
files: results
|
|
193
|
+
};
|
|
194
|
+
} catch (error) {
|
|
195
|
+
console.error('❌ Erreur uploadMultiple:', error);
|
|
196
|
+
throw error;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// ==================== DOWNLOAD ====================
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Obtenir l'URL de téléchargement
|
|
204
|
+
*/
|
|
205
|
+
async getDownloadURL(path) {
|
|
206
|
+
try {
|
|
207
|
+
const url = await this.ref(path).getDownloadURL();
|
|
208
|
+
return url;
|
|
209
|
+
} catch (error) {
|
|
210
|
+
console.error(`❌ Erreur getDownloadURL ${path}:`, error);
|
|
211
|
+
throw error;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Télécharger un fichier comme Blob
|
|
217
|
+
*/
|
|
218
|
+
async downloadAsBlob(path, maxSize = 10485760) {
|
|
219
|
+
try {
|
|
220
|
+
const blob = await this.ref(path).getBytes(maxSize);
|
|
221
|
+
return new Blob([blob]);
|
|
222
|
+
} catch (error) {
|
|
223
|
+
console.error(`❌ Erreur downloadAsBlob ${path}:`, error);
|
|
224
|
+
throw error;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Télécharger un fichier comme Data URL
|
|
230
|
+
*/
|
|
231
|
+
async downloadAsDataURL(path) {
|
|
232
|
+
try {
|
|
233
|
+
const url = await this.ref(path).getDownloadURL();
|
|
234
|
+
const response = await fetch(url);
|
|
235
|
+
const blob = await response.blob();
|
|
236
|
+
|
|
237
|
+
return new Promise((resolve, reject) => {
|
|
238
|
+
const reader = new FileReader();
|
|
239
|
+
reader.onloadend = () => resolve(reader.result);
|
|
240
|
+
reader.onerror = reject;
|
|
241
|
+
reader.readAsDataURL(blob);
|
|
242
|
+
});
|
|
243
|
+
} catch (error) {
|
|
244
|
+
console.error(`❌ Erreur downloadAsDataURL ${path}:`, error);
|
|
245
|
+
throw error;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// ==================== DELETE ====================
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Supprimer un fichier
|
|
253
|
+
*/
|
|
254
|
+
async deleteFile(path) {
|
|
255
|
+
try {
|
|
256
|
+
await this.ref(path).delete();
|
|
257
|
+
return { success: true };
|
|
258
|
+
} catch (error) {
|
|
259
|
+
console.error(`❌ Erreur deleteFile ${path}:`, error);
|
|
260
|
+
throw error;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Supprimer plusieurs fichiers
|
|
266
|
+
*/
|
|
267
|
+
async deleteMultiple(paths) {
|
|
268
|
+
try {
|
|
269
|
+
const deletions = paths.map(path => this.deleteFile(path));
|
|
270
|
+
await Promise.all(deletions);
|
|
271
|
+
|
|
272
|
+
return { success: true };
|
|
273
|
+
} catch (error) {
|
|
274
|
+
console.error('❌ Erreur deleteMultiple:', error);
|
|
275
|
+
throw error;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// ==================== METADATA ====================
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Obtenir les métadonnées
|
|
283
|
+
*/
|
|
284
|
+
async getMetadata(path) {
|
|
285
|
+
try {
|
|
286
|
+
const metadata = await this.ref(path).getMetadata();
|
|
287
|
+
return metadata;
|
|
288
|
+
} catch (error) {
|
|
289
|
+
console.error(`❌ Erreur getMetadata ${path}:`, error);
|
|
290
|
+
throw error;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Mettre à jour les métadonnées
|
|
296
|
+
*/
|
|
297
|
+
async updateMetadata(path, metadata) {
|
|
298
|
+
try {
|
|
299
|
+
const newMetadata = await this.ref(path).updateMetadata(metadata);
|
|
300
|
+
return newMetadata;
|
|
301
|
+
} catch (error) {
|
|
302
|
+
console.error(`❌ Erreur updateMetadata ${path}:`, error);
|
|
303
|
+
throw error;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// ==================== LIST ====================
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Lister les fichiers d'un dossier
|
|
311
|
+
*/
|
|
312
|
+
async listFiles(path, options = {}) {
|
|
313
|
+
try {
|
|
314
|
+
const { maxResults = 100, pageToken = null } = options;
|
|
315
|
+
|
|
316
|
+
const result = await this.ref(path).list({
|
|
317
|
+
maxResults,
|
|
318
|
+
pageToken
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
const items = result.items.map(item => ({
|
|
322
|
+
name: item.name,
|
|
323
|
+
fullPath: item.fullPath,
|
|
324
|
+
bucket: item.bucket
|
|
325
|
+
}));
|
|
326
|
+
|
|
327
|
+
const prefixes = result.prefixes.map(prefix => ({
|
|
328
|
+
name: prefix.name,
|
|
329
|
+
fullPath: prefix.fullPath
|
|
330
|
+
}));
|
|
331
|
+
|
|
332
|
+
return {
|
|
333
|
+
items,
|
|
334
|
+
prefixes,
|
|
335
|
+
nextPageToken: result.nextPageToken || null
|
|
336
|
+
};
|
|
337
|
+
} catch (error) {
|
|
338
|
+
console.error(`❌ Erreur listFiles ${path}:`, error);
|
|
339
|
+
throw error;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Lister tous les fichiers (récursif)
|
|
345
|
+
*/
|
|
346
|
+
async listAllFiles(path) {
|
|
347
|
+
try {
|
|
348
|
+
const result = await this.ref(path).listAll();
|
|
349
|
+
|
|
350
|
+
const items = result.items.map(item => ({
|
|
351
|
+
name: item.name,
|
|
352
|
+
fullPath: item.fullPath,
|
|
353
|
+
bucket: item.bucket
|
|
354
|
+
}));
|
|
355
|
+
|
|
356
|
+
const prefixes = result.prefixes.map(prefix => ({
|
|
357
|
+
name: prefix.name,
|
|
358
|
+
fullPath: prefix.fullPath
|
|
359
|
+
}));
|
|
360
|
+
|
|
361
|
+
return {
|
|
362
|
+
items,
|
|
363
|
+
prefixes
|
|
364
|
+
};
|
|
365
|
+
} catch (error) {
|
|
366
|
+
console.error(`❌ Erreur listAllFiles ${path}:`, error);
|
|
367
|
+
throw error;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// ==================== UPLOAD CONTROL ====================
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Mettre en pause un upload
|
|
375
|
+
*/
|
|
376
|
+
pauseUpload(path) {
|
|
377
|
+
const uploadTask = this.uploadTasks.get(path);
|
|
378
|
+
if (uploadTask) {
|
|
379
|
+
uploadTask.pause();
|
|
380
|
+
return true;
|
|
381
|
+
}
|
|
382
|
+
return false;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* Reprendre un upload
|
|
387
|
+
*/
|
|
388
|
+
resumeUpload(path) {
|
|
389
|
+
const uploadTask = this.uploadTasks.get(path);
|
|
390
|
+
if (uploadTask) {
|
|
391
|
+
uploadTask.resume();
|
|
392
|
+
return true;
|
|
393
|
+
}
|
|
394
|
+
return false;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Annuler un upload
|
|
399
|
+
*/
|
|
400
|
+
cancelUpload(path) {
|
|
401
|
+
const uploadTask = this.uploadTasks.get(path);
|
|
402
|
+
if (uploadTask) {
|
|
403
|
+
uploadTask.cancel();
|
|
404
|
+
this.uploadTasks.delete(path);
|
|
405
|
+
return true;
|
|
406
|
+
}
|
|
407
|
+
return false;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Obtenir l'état d'un upload
|
|
412
|
+
*/
|
|
413
|
+
getUploadState(path) {
|
|
414
|
+
const uploadTask = this.uploadTasks.get(path);
|
|
415
|
+
if (uploadTask) {
|
|
416
|
+
return uploadTask.snapshot.state;
|
|
417
|
+
}
|
|
418
|
+
return null;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// ==================== HELPERS ====================
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* Compresser une image
|
|
425
|
+
*/
|
|
426
|
+
compressImage(file, maxWidth, maxHeight, quality, outputFormat) {
|
|
427
|
+
return new Promise((resolve, reject) => {
|
|
428
|
+
const reader = new FileReader();
|
|
429
|
+
|
|
430
|
+
reader.onload = (e) => {
|
|
431
|
+
const img = new Image();
|
|
432
|
+
|
|
433
|
+
img.onload = () => {
|
|
434
|
+
const canvas = document.createElement('canvas');
|
|
435
|
+
const ctx = canvas.getContext('2d');
|
|
436
|
+
|
|
437
|
+
// Calculer les nouvelles dimensions
|
|
438
|
+
let width = img.width;
|
|
439
|
+
let height = img.height;
|
|
440
|
+
|
|
441
|
+
if (width > maxWidth) {
|
|
442
|
+
height = (height * maxWidth) / width;
|
|
443
|
+
width = maxWidth;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
if (height > maxHeight) {
|
|
447
|
+
width = (width * maxHeight) / height;
|
|
448
|
+
height = maxHeight;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
canvas.width = width;
|
|
452
|
+
canvas.height = height;
|
|
453
|
+
|
|
454
|
+
// Dessiner l'image redimensionnée
|
|
455
|
+
ctx.drawImage(img, 0, 0, width, height);
|
|
456
|
+
|
|
457
|
+
// Convertir en Blob
|
|
458
|
+
canvas.toBlob(
|
|
459
|
+
(blob) => {
|
|
460
|
+
if (blob) {
|
|
461
|
+
resolve(blob);
|
|
462
|
+
} else {
|
|
463
|
+
reject(new Error('Erreur compression image'));
|
|
464
|
+
}
|
|
465
|
+
},
|
|
466
|
+
outputFormat,
|
|
467
|
+
quality
|
|
468
|
+
);
|
|
469
|
+
};
|
|
470
|
+
|
|
471
|
+
img.onerror = () => reject(new Error('Erreur chargement image'));
|
|
472
|
+
img.src = e.target.result;
|
|
473
|
+
};
|
|
474
|
+
|
|
475
|
+
reader.onerror = () => reject(new Error('Erreur lecture fichier'));
|
|
476
|
+
reader.readAsDataURL(file);
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* Obtenir l'extension d'un fichier
|
|
482
|
+
*/
|
|
483
|
+
getFileExtension(filename) {
|
|
484
|
+
return filename.split('.').pop().toLowerCase();
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
/**
|
|
488
|
+
* Générer un nom de fichier unique
|
|
489
|
+
*/
|
|
490
|
+
generateUniqueFilename(originalFilename) {
|
|
491
|
+
const timestamp = Date.now();
|
|
492
|
+
const random = Math.random().toString(36).substring(2, 8);
|
|
493
|
+
const extension = this.getFileExtension(originalFilename);
|
|
494
|
+
const basename = originalFilename.replace(`.${extension}`, '');
|
|
495
|
+
|
|
496
|
+
return `${basename}_${timestamp}_${random}.${extension}`;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
/**
|
|
500
|
+
* Valider le type de fichier
|
|
501
|
+
*/
|
|
502
|
+
validateFileType(file, allowedTypes = []) {
|
|
503
|
+
if (allowedTypes.length === 0) return true;
|
|
504
|
+
|
|
505
|
+
const fileType = file.type;
|
|
506
|
+
const fileExtension = this.getFileExtension(file.name);
|
|
507
|
+
|
|
508
|
+
return allowedTypes.some(type => {
|
|
509
|
+
if (type.includes('*')) {
|
|
510
|
+
// Wildcard: "image/*"
|
|
511
|
+
const category = type.split('/')[0];
|
|
512
|
+
return fileType.startsWith(category);
|
|
513
|
+
} else if (type.startsWith('.')) {
|
|
514
|
+
// Extension: ".jpg"
|
|
515
|
+
return fileExtension === type.substring(1);
|
|
516
|
+
} else {
|
|
517
|
+
// MIME type: "image/jpeg"
|
|
518
|
+
return fileType === type;
|
|
519
|
+
}
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
/**
|
|
524
|
+
* Valider la taille du fichier
|
|
525
|
+
*/
|
|
526
|
+
validateFileSize(file, maxSizeInMB) {
|
|
527
|
+
const maxSizeInBytes = maxSizeInMB * 1024 * 1024;
|
|
528
|
+
return file.size <= maxSizeInBytes;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
/**
|
|
532
|
+
* Formater la taille d'un fichier
|
|
533
|
+
*/
|
|
534
|
+
formatFileSize(bytes) {
|
|
535
|
+
if (bytes === 0) return '0 Bytes';
|
|
536
|
+
|
|
537
|
+
const k = 1024;
|
|
538
|
+
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
|
539
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
540
|
+
|
|
541
|
+
return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i];
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
/**
|
|
545
|
+
* Vérifier si un fichier existe
|
|
546
|
+
*/
|
|
547
|
+
async fileExists(path) {
|
|
548
|
+
try {
|
|
549
|
+
await this.ref(path).getMetadata();
|
|
550
|
+
return true;
|
|
551
|
+
} catch (error) {
|
|
552
|
+
if (error.code === 'storage/object-not-found') {
|
|
553
|
+
return false;
|
|
554
|
+
}
|
|
555
|
+
throw error;
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
/**
|
|
560
|
+
* Copier un fichier
|
|
561
|
+
*/
|
|
562
|
+
async copyFile(sourcePath, destPath) {
|
|
563
|
+
try {
|
|
564
|
+
// Télécharger le fichier source
|
|
565
|
+
const blob = await this.downloadAsBlob(sourcePath);
|
|
566
|
+
|
|
567
|
+
// Re-uploader vers la destination
|
|
568
|
+
const result = await this.uploadBlob(destPath, blob);
|
|
569
|
+
|
|
570
|
+
return result;
|
|
571
|
+
} catch (error) {
|
|
572
|
+
console.error('❌ Erreur copyFile:', error);
|
|
573
|
+
throw error;
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
/**
|
|
578
|
+
* Déplacer un fichier
|
|
579
|
+
*/
|
|
580
|
+
async moveFile(sourcePath, destPath) {
|
|
581
|
+
try {
|
|
582
|
+
// Copier le fichier
|
|
583
|
+
await this.copyFile(sourcePath, destPath);
|
|
584
|
+
|
|
585
|
+
// Supprimer la source
|
|
586
|
+
await this.deleteFile(sourcePath);
|
|
587
|
+
|
|
588
|
+
return { success: true };
|
|
589
|
+
} catch (error) {
|
|
590
|
+
console.error('❌ Erreur moveFile:', error);
|
|
591
|
+
throw error;
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
// ==================== CLEANUP ====================
|
|
596
|
+
|
|
597
|
+
/**
|
|
598
|
+
* Nettoyer les ressources
|
|
599
|
+
*/
|
|
600
|
+
destroy() {
|
|
601
|
+
// Annuler tous les uploads en cours
|
|
602
|
+
this.uploadTasks.forEach((task, path) => {
|
|
603
|
+
task.cancel();
|
|
604
|
+
});
|
|
605
|
+
|
|
606
|
+
this.uploadTasks.clear();
|
|
607
|
+
this.storage = null;
|
|
608
|
+
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
export default FirebaseStorage;
|