Solution for Firebase Storage (web) not working when files are uploaded from phone
is Given Below:
I’m creating a website using Firebase. The user should be able to upload & update their profile picture, so long as it is <10mb, and is a .jpg, .png, or .gif file.
What is supposed to happen:
- User uploads a valid image
- Create or update a reference
/users/{uid}/profileImage
within Firebase Storage - Use the
getDownloadURL()
method to get the image’s URL, and store it as text inside Firestore, under the user’s profile information - Use that URL as the
src
of the profile picture.
When I try to do this on my computer (Windows 10), it works perfectly. However, when I try to do it on my phone (iPhone 8; iOS 14.7.1), it does not work. The image uploaded from the phone reaches the /users/{uid}/profileImage
, but then it doesn’t properly get the downloadURL
due to permission problems, even though the user is authenticated on the phone browser, as required by the rules.
The following is the code to (1) get the file and (2) update the user’s profile picture:
// Grab dp img and store it in file var
let file = {}
const chooseFile = (e) => {
// Get the file from local machine
file = e.target.files[0]
console.log(file )
}
// Store dp in storage as file, and db as link
const updateDp = (currentUser) => {
// Check if new dp has been added/exists.
if ("name" in file) {
// Check if uploaded file is an image
if (file.type !== "image/jpeg" && file.type !== "image/png" && file.type !== "image/gif") {
alert("You can only upload .jpeg, .jpg, .png and .gif under 10mb")
return
}
// Check image file size
if (file.size/1024/1024>10) {
alert("The image size must be under 10mb")
return
}
// Create storage ref & put the file in it
storage
.ref("users/" + currentUser.uid + "/profileImage")
.put(file)
.then(() => {
// success => get download link, put it in DB, update dp img src
storage
.ref("users/" + currentUser.uid + "/profileImage")
.getDownloadURL()
.then(imgURL => {
db
.collection("users")
.doc(currentUser.uid)
.set({
dp_URL: imgURL,
dp_URL_last_modified: file.lastModifiedDate
}, {
merge: true
})
document.querySelector("#nav_dp").src = imgURL;
})
console.log("success")
}).catch(() => {
console.log(error.message)
})
} else {
console.log("Empty/no file")
}
}
The following is my Firebase Storage Rules:
rules_version = '2';
service firebase.storage {
match /b/{bucket}/o {
match /users/{uid}/{profileImage} {
allow read: if request.auth!=null;
allow write: if request.auth!=null && request.auth.uid == uid;
}
}
}
The security rules and code look fine to me except the console.log("success")
will log before you get the downloadURL as getDownloadURL()
returns a Promise. Try changing your function to an async function as shown:
// Store dp in storage as file, and db as link
const updateDp = async (currentUser) => {
// ^^^^^
// Check if new dp has been added/exists.
if ("name" in file) {
try {
// Check if uploaded file is an image
if (
file.type !== "image/jpeg" &&
file.type !== "image/png" &&
file.type !== "image/gif"
) {
alert("You can only upload .jpeg, .jpg, .png and .gif under 10mb");
return;
}
// Check image file size
if (file.size / 1024 / 1024 > 10) {
alert("The image size must be under 10mb");
return;
}
// Create storage ref & put the file in it
const userPicRef = storage.ref(
"users/" + currentUser.uid + "/profileImage"
);
await userPicRef.put(file);
console.log("Image uploaded")
// success => get download link, put it in DB, update dp img src
const imgURL = await UserPicRef.getDownloadURL();
console.log(`Image URL: ${imgURL}`)
await db.collection("users").doc(currentUser.uid).set(
{
dp_URL: imgURL,
dp_URL_last_modified: file.lastModifiedDate,
},
{
merge: true,
}
);
console.log("Document Added")
document.querySelector("#nav_dp").src = imgURL;
} catch (error) {
console.log(error);
}
} else {
console.log("Empty/no file");
}
};
Answering my own question.
Turns out, I was using file.lastModifiedDate
, which is a depreciated method. It works on the Google Chrome app ran on Android, but not in iOS.
To fix it, I used file.lastModified
instead.