I am working on a project that uses Firebase Realtime Database and Firebase Storage.
First Part:
I have a function to upload the Pictures of users in javascript:
const firebaseConfig = {
apiKey: FB_WEB_API_KEY,
authDomain: FB_WEB_AUTH_DOMAIN,
databaseURL: FB_WEB_DATABASE_URL,
projectId: FB_WEB_PROJECT_ID,
storageBucket: FB_WEB_STORAGE_BUCKET,
appId: FB_WEB_APP_ID,
}
firebase = require('firebase/app')
require('firebase/functions')
require('firebase/database')
require('firebase/storage')
require('firebase/auth')
require('firebase/functions')
firebase.initializeApp(firebaseConfig)
export async function setUserPicture(pId, userId, picture) {
if (picture !== '' && picture != null) {
let promises = []
const filesRef = firebase.storage().ref(`users/${pId}/${userId}`)
filesRef.listAll().then(async folder => {
for (let file of folder.items) {
promises.push(filesRef.child(file.name).delete())
}
// Wait until all previous images are deleted
await Promise.all(promises)
let attachmentRef = firebase
.storage()
.ref(`users/${pId}/${userId}/${userId}@${Date.now()}`)
attachmentRef.put(picture).then(_ => {
attachmentRef.getDownloadURL().then(url => {
firebase
.database()
.ref(`users/${pId}/${userId}`)
.update({ photoURL: url })
})
})
})
}
}
In this function all images related to user's avatars are uploaded to a folder called users in Firebase Storage following this pattern:
'users/${pId}/${userId}/' // Inside this path is placed the images
Before upload the new image, I delete all previous images from the specific folder in the storage, to be sure that there are no previous images.
Also when I upload the image, I take the downloadURL
and save it in a field of the User data.
This are the packages installed for this:
"firebase": "^7.12.0",
"firebase-admin": "^8.11.0",
"firebase-functions": "^3.6.1",
Second Part:
For this project, I have some Cloud Functions to handle some data. In this case, I have a cloud function to save 2 resized images in the Storage, in the same folders where I have the original image of the avatar. This is to use proper sizes to improve the load time.
const functions = require('firebase-functions')
const admin = require('firebase-admin')
const { tmpdir } = require('os')
const { dirname, join } = require('path')
const sharp = require('sharp')
const fs = require('fs-extra')
const { uuid } = require('uuidv4')
exports.onUploadUserPicture = functions
.runWith({ memory: '2GB', timeoutSeconds: 120 })
.storage.object()
.onFinalize(async object => {
const bucket = admin.storage().bucket()
const originalPath = object.name
const originalFileName = originalPath.split('/').pop()
const userId = originalFileName.split('@').shift()
const bucketDir = dirname(originalPath)
const workingDir = join(tmpdir(), 'resizing')
const tmpPath = join(workingDir, 'origin.png')
const metadata = {
contentDisposition: object.contentDisposition,
contentEncoding: object.contentEncoding,
contentLanguage: object.contentLanguage,
contentType: object.contentType,
metadata: object.metadata || {},
}
metadata.metadata.resizedImage = true
console.log(`Apply resizing to ${originalFileName}`)
if (!originalPath.includes('users') || !object.contentType.includes('image')) {
console.log(`This file is not a user picture. Exit!`)
return false
} else if (originalFileName.includes('_50x50')) {
console.log(`Already processed: [ ${originalFileName} ] for size: [ 50 ]. Exit!`)
return false
} else if (originalFileName.includes('_300x300')) {
console.log(`Already processed: [ ${originalFileName} ] for size: [ 300 ]. Exit!`)
return false
}
await fs.ensureDir(workingDir)
await bucket.file(originalPath).download({ destination: tmpPath })
const sizes = [50, 300]
const promises = sizes.map(async size => {
console.log(`Resizing ${originalFileName} at size ${size}`)
const newImgName = `${userId}@${Date.now()}_${size}x${size}`
const imgPath = join(workingDir, newImgName)
await sharp(tmpPath)
.clone()
.resize(size, size, { fit: 'inside', withoutEnlargement: true })
.toFile(imgPath)
console.log(`Just resized ${newImgName} at size ${size}`)
// If the original image has a download token, add a
// new token to the image being resized
if (object.metadata.firebaseStorageDownloadTokens) {
metadata.metadata.firebaseStorageDownloadTokens = uuid()
}
return bucket.upload(imgPath, {
destination: join(bucketDir, newImgName),
metadata: metadata,
})
})
await Promise.all(promises)
return fs.remove(workingDir)
})
In this Cloud Function, firstly, I verify if it is user picture (based on the location of the original image) and if it is not a resized image (not have a _50x50 or _300x300)
Then, I download the original image, create both resized thumbnails, and after that upload to Storage.
I apply some metadata from the original image, a convention name to be unique and also generate a new storage download token for each image,
The PROBLEM
The real issue is that when I verify in Firebase Storage, sometimes, in a random way, the picture uploaded or that appear in the Storage is not correct. Some times is a picture that I uploaded before and even that supposed to be deleted previously by one of these functions.
Is there something missing in these functions??