@taraai/read-write
Version:
Synchronous NoSQL/Firestore for React
229 lines (216 loc) • 7.92 kB
JavaScript
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
}