UNPKG

@taraai/read-write

Version:

Synchronous NoSQL/Firestore for React

229 lines (216 loc) 7.92 kB
import { omitBy, isUndefined } from 'lodash' import { actionTypes } from '../constants' const { FILE_UPLOAD_ERROR, FILE_UPLOAD_PROGRESS } = actionTypes /** * Delete file from Firebase Storage with support for deleteing meta * data from database (either Real Time Database or Firestore depending on * config) * @param {object} firebase - Internal firebase object * @param {object} settings - Settings object * @param {string} settings.path - Path to File which should be deleted * @param {string} settings.dbPath - Path of meta data with Database (Real Time Or * Firestore depnding on config) * @returns {Promise} Resolves with path and dbPath */ export function deleteFile(firebase, { path, dbPath }) { return firebase .storage() .ref(path) .delete() .then(() => { // return path if dbPath or a database does not exist if (!dbPath || (!firebase.database && !firebase.firestore)) { return { path } } // Choose delete function based on config (Handling Firestore and RTDB) const metaDeletePromise = () => firebase._.config.useFirestoreForStorageMeta ? firebase.firestore().doc(dbPath).delete() // file meta in Firestore : firebase.database().ref(dbPath).remove() // file meta in RTDB return metaDeletePromise().then(() => ({ path, dbPath })) }) } /** * Create a function to handle response from upload. * @param {object} settings - Settings object * @param {object} settings.fileData - File data which was uploaded * @param {object} settings.uploadTaskSnapshot - Snapshot from storage upload task * @param {object} settings.firebase - Firebase instance * @param {string} settings.downloadURL - Download url * @returns {Function} Function for handling upload result */ function createUploadMetaResponseHandler({ fileData, firebase, uploadTaskSnapshot, downloadURL }) { /** * Converts upload meta data snapshot into an object (handling both * RTDB and Firestore) * @param {object} metaDataSnapshot - Snapshot from metadata upload (from * RTDB or Firestore) * @returns {object} Upload result including snapshot, key, File */ return function uploadResultFromSnap(metaDataSnapshot) { const { useFirestoreForStorageMeta } = firebase._.config const result = { snapshot: metaDataSnapshot, key: metaDataSnapshot.key || metaDataSnapshot.id, File: fileData, metaDataSnapshot, uploadTaskSnapshot, // Support legacy method uploadTaskSnaphot: uploadTaskSnapshot, createdAt: useFirestoreForStorageMeta ? firebase.firestore.FieldValue.serverTimestamp() : firebase.database.ServerValue.TIMESTAMP } // Attach id if it exists (Firestore) if (metaDataSnapshot.id) { result.id = metaDataSnapshot.id } // Attach downloadURL if it exists if (downloadURL) { result.downloadURL = downloadURL } return result } } /** * Get download URL from upload task snapshot * @param {firebase.storage.UploadTaskSnapshot} uploadTaskSnapshot - Upload task snapshot * @returns {Promise} Resolves with download URL */ function getDownloadURLFromUploadTaskSnapshot(uploadTaskSnapshot) { // Handle different downloadURL patterns (Firebase JS SDK v5.*.* vs v4.*.*) if ( uploadTaskSnapshot.ref && typeof uploadTaskSnapshot.ref.getDownloadURL === 'function' ) { // Get downloadURL and attach to response return uploadTaskSnapshot.ref.getDownloadURL() } // Only attach downloadURL if downloadURLs is defined (not defined in v5.*.*) return Promise.resolve( uploadTaskSnapshot.downloadURLs && uploadTaskSnapshot.downloadURLs[0] ) } /** * Write file metadata to Database (either Real Time Datbase or Firestore * depending on config). * @param {object} settings - Settings object * @param {object} settings.firebase - Internal firebase object * @param {object} settings.uploadTaskSnapshot - Snapshot from upload task * @param {string} settings.dbPath - Path of meta data with Database (Real Time Or * Firestore depnding on config) * @param {object} settings.options - Options object * @returns {Promise} Resolves with payload (includes snapshot, File, and * metaDataSnapshot) */ export function writeMetadataToDb({ firebase, uploadTaskSnapshot, dbPath, options }) { // Support metadata factories from both global config and options const { fileMetadataFactory, useFirestoreForStorageMeta } = firebase._.config const { metadataFactory, documentId, useSetForMetadata } = options const metaFactoryFunction = metadataFactory || fileMetadataFactory // Get download URL for use in metadata write return getDownloadURLFromUploadTaskSnapshot(uploadTaskSnapshot).then( (downloadURL) => { // Apply fileMetadataFactory if it exists in config const fileData = typeof metaFactoryFunction === 'function' ? metaFactoryFunction( uploadTaskSnapshot, firebase, uploadTaskSnapshot.metadata, downloadURL ) : omitBy(uploadTaskSnapshot.metadata, isUndefined) // Create the snapshot handler function const resultFromSnap = createUploadMetaResponseHandler({ fileData, firebase, uploadTaskSnapshot, downloadURL }) // Function for creating promise for writing file metadata (handles writing to RTDB or Firestore) const documentIdFromOptions = typeof documentId === 'function' ? documentId( uploadTaskSnapshot, firebase, uploadTaskSnapshot.metadata, downloadURL ) : documentId const metaSetPromise = (fileData) => { if (useFirestoreForStorageMeta) { if (documentIdFromOptions) { const docRef = firebase // Write metadata to Firestore .firestore() .collection(dbPath) .doc(documentIdFromOptions) return useSetForMetadata === false ? docRef.update(fileData).then(() => docRef) : docRef.set(fileData, { merge: true }).then(() => docRef) } return firebase.firestore().collection(dbPath).add(fileData) } // Create new reference for metadata const newMetaRef = firebase.database().ref(dbPath).push() // Write metadata to Real Time Database and return new meta ref return newMetaRef.set(fileData).then((res) => newMetaRef) } return metaSetPromise(fileData).then(resultFromSnap) } ) } /** * Upload a file with actions fired for progress, success, and errors * @param {Function} dispatch - Action dispatch function * @param {object} firebase - Internal firebase object * @param {object} opts - File data object * @param {object} opts.path - Location within Firebase Stroage at which to upload file. * @param {Blob} opts.file - File to upload * @param {object} opts.fileMetadata - Metadata to pass along to storageRef.put call * @param {object} opts.meta - Meta object * @returns {Promise} Promise which resolves after file upload * @private */ export function uploadFileWithProgress( dispatch, firebase, { path, file, filename, meta, fileMetadata } ) { const uploadEvent = firebase .storage() .ref(`${path}/${filename}`) .put(file, fileMetadata) const unListen = uploadEvent.on(firebase.storage.TaskEvent.STATE_CHANGED, { next: (snapshot) => { dispatch({ type: FILE_UPLOAD_PROGRESS, meta, payload: { snapshot, percent: Math.floor( (snapshot.bytesTransferred / snapshot.totalBytes) * 100 ) } }) }, error: (err) => { dispatch({ type: FILE_UPLOAD_ERROR, meta, payload: err }) unListen() }, complete: () => { unListen() } }) return uploadEvent }