Expo/Firebase: Image chosen from camera roll uploading as octet-stream instead of .jpg - firebase

I've been having trouble viewing the image files I've uploaded to firebase and just noticed the issue is with the file type in firebase.
Two files in my firebase storage console. One uploaded from my IOS simulator (octet-stream) and the other uploaded directly into the console from the browser which uploads properly and is viewable.
Here are my select and upload functions:
_selectPhoto = async () => {
const status = await getPermission(Permissions.CAMERA_ROLL);
if (status) {
let imageName = "pic"
const result = await ImagePicker.launchImageLibraryAsync(options);
if (!result.cancelled) {
Animated.timing(this.animatedWidth, {
toValue: 600,
duration: 15000
}).start()
this.uploadImage(result.uri, imageName)
.then(() => {
this.props.navigation.navigate('Profile')
})
.catch((error) => {
Alert.alert('Must Sign In');
this.props.navigation.navigate('Login')
console.log(error);
})
}
}
};
uploadImage = async (uri, imageName) => {
const user = firebase.auth().currentUser;
const response = await fetch(uri);
const blob = await response.blob();
let storageRef = firebase.storage().ref().child(''images/'+user.displayName+'/'+imageName+'.jpg'');
const snapshot = await storageRef.put(blob);
blob.close();
snapshot.ref.getDownloadURL().then(function(downloadURL) {
console.log("File available at", downloadURL);
user.updateProfile({
photoURL: downloadURL.toString(),
}).then(function() {
console.log('saved photo')
}).catch(function(error) {
console.log('failed photo')
});
});
}
When I get the link in my console, it also has the media&token:
... .appspot.com/o/profile-pic.jpg?alt=media&token=56eb9c36-b5cd-4dbb-bec1-3ea5c3a74bdd
If I CMD+Click in VS Code I receive an error:
{
error: {
code: 400,
message: "Invalid HTTP method/URL pair."
}
}
So naturally, when I put that link in the browser it downloads a file with that name but says:
The file “pic.jpg” could not be opened.
It may be damaged or use a
file format that Preview doesn’t recognize.
Maybe it could be something with mediaTypes, but I'm not exactly sure how to use it.
mediaTypes : String -- Choose what type of media to pick. Usage:
ImagePicker.MediaTypeOptions., where is one of: Images,
Videos, All.
Thanks!

I've been fighting with this same issue for the past few days. I was finally able get images to upload and render as expected by following the Firebase Upload example in the Expo repo. I don't fully understand why it works, but it seems like Firebase doesn't like the blob that's generated by
const blob = await response.blob();
Try replacing the above with:
const blob = await new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.onload = function() {
resolve(xhr.response);
};
xhr.onerror = function(e) {
console.log(e);
reject(new TypeError('Network request failed'));
};
xhr.responseType = 'blob';
xhr.open('GET', uri, true);
xhr.send(null);
});

Related

expo react native upload image to firebase storage

I am trying to upload an image from the phone library with expo-image-picker to Firebase storage and download the URL to save it in Firestore but for some reason, my app keeps crashing on (iPhone) without any error. I have tried every possible way to fix this issue(running my code line by line etc) but nothing has yet fixed this issue.
Has anyone encountered a similar issue and could help me with this particular problem? I have been stuck for a few days now. It would be a big help. Thank you in advance.
Here is my code:
Turning image to blob. at First, I used the fetch method but this seems to work better.
const urlToBlob = async (url) => {
return await new Promise((resolve, reject) => {
var xhr = new XMLHttpRequest();
xhr.onerror = reject;
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
resolve(xhr.response);
}
};
xhr.open("GET", url);
xhr.responseType = "blob"; // convert type
xhr.send();
});
};
Uploading an image to storage. Sometimes it uploads but if you upload again it crashes.
const uploadImageAsync = async (imageUri) => {
let blob;
const imageRef = imageUri.substring(imageUri.lastIndexOf("/"));
try {
blob = await urlToBlob(imageUri);
const ref = await firebase.storage().ref().child(imageRef);
await ref.put(blob);
return await ref.getDownloadURL();
} catch (error) {
console.log(
"🚀 ~ file: eventServices.jsx ~ line 33 ~ createEvent ~ error",
error
);
} finally {
blob.close();
console.log("blob closed");
}
};
Here I get the image and pass it to my function which should return the URL to the image. URL then should get saved in Firestore.
export const createEvent = async (eventObj) => {
const imageUri = eventObj.image;
try {
const downloadUrl = uploadImageAsync(imageUri);
console.log(downloadUrl );
await firebase
.firestore()
.collection("events")
.add({ ...eventObj, image: downloadUrl });
console.log("Event added!");
} catch (error) {
console.log(
"🚀 ~ file: eventServices.jsx ~ line 62 ~ createEvent ~ error",
error
);
}
};

FormData using BusBoy for firebase works in serve but not in deploy

Situation
I have a firebase function that updates the user image.
Problem
When I run locally my function using firebase serve, I successfully upload the image to firestore using Postman. However, when I run firebase deploy and I try to upload the image using Postman, I get a 500 Internal Server Error. The other functions (not dealing with FormData, just json) work perfectly when I deploy them.
I don't understand why it works locally, but not on deploy when I am doing the exact same thing. Not sure if this is something in the config I am missing, or if I am doing something wrong. Any help would be appreciated!
Code
users.js
const { admin, db, firebase } = require('../util/admin');
const config = require('../util/config');
exports.postUserImage = (req, res) => {
const BusBoy = require('busboy');
const path = require('path');
const os = require('os');
const fs = require('fs');
let imgFileName;
let imgToBeUploaded = {};
const busboy = new BusBoy({ headers: req.headers });
busboy.on('file', (fieldname, file, filename, encoding, mimetype) => {
// Invalid file type
if (mimetype !== 'image/jpeg' && mimetype !== 'image/png') {
return res.status(400).json({ error: 'Invalid file type' });
}
// Extract img extension
const imgDotLength = filename.split('.').length;
const imgExtension = filename.split('.')[imgDotLength - 1];
// Create img file name
imgFileName = `${Math.round(Math.random() * 1000000)}.${imgExtension}`;
// Create img path
const filepath = path.join(os.tmpdir(), imgFileName);
// Create img object to be uploaded
imgToBeUploaded = { filepath, mimetype };
// Use file system to create the file
file.pipe(fs.createWriteStream(filepath));
});
busboy.on('finish', () => {
admin
.storage()
.bucket()
.upload(imgToBeUploaded.filepath, {
resumable: false,
metadata: {
metadata: {
contentType: imgToBeUploaded.mimetype
}
}
})
.then(() => {
// Create img url to add to our user
const imgUrl = `https://firebasestorage.googleapis.com/v0/b/${config.storageBucket}/o/${imgFileName}?alt=media`;
// Add img url to user document
return db.doc(`/users/${req.user.handle}`).update({ imgUrl });
})
.then(() => {
return res.json({ message: 'Image uploaded succesfully' });
})
.catch((err) => {
console.error(err);
return res.status(500).json({ error });
});
});
busboy.end(req.rawBody);
};
index.js
const { app, functions } = require('./util/admin');
const FirebaseAuth = require('./util/firebaseAuth');
const {
postUserImage,
} = require('./handlers/users');
app.post('/user/image', FirebaseAuth, postUserImage);

React Native base64 Image Upload to Firebase Storage

I am currently working on an App. The workflow I currently have is fairly simple.
A user creates an account, and then is taken to a page to populate their profile information. Name, description, and a few images.
I use expo's ImagePicker to get the image:
let result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ImagePicker.MediaTypeOptions.Images,
quality: 0.1,
allowsEditing: true,
aspect: [2, 3],
base64: true
});
Originally, I was using this to upload the images:
// Why are we using XMLHttpRequest? See:
// https://github.com/expo/expo/issues/2402#issuecomment-443726662
const blob = await new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.onload = function() {
resolve(xhr.response);
};
xhr.onerror = function(e) {
reject(new TypeError("Network request failed"));
};
xhr.responseType = "blob";
xhr.open("GET", uri, true);
xhr.send(null);
});
const ref = firebase
.storage()
.ref()
.child(uuid.v4());
const snapshot = await ref.put(blob);
// We're done with the blob, close and release it
blob.close();
let url = await snapshot.ref.getDownloadURL();
return url;
The problem here is I looped through that function about 6 times, and I kept getting some obscure error.
Currently, I am attempting to upload the images using this:
const ref = firebase
.storage()
.ref()
.child(uuid.v4());
const snapshot = await ref.putString(b64Url, "data_url");
This works well on web, but in the native app I get the error:
FirebaseStorageError {
"code_": "storage/invalid-format",
"message_": "Firebase Storage: String does not match format 'base64': Invalid character found",
"name_": "FirebaseError",
"serverResponse_": null,
}
The last comment on this issue outlines the problem. To break it down: atob doesn't exist. This is the sole problem behind the error. To fix, I polyfilled it like this:
import { decode, encode } from "base-64";
if (!global.btoa) {
global.btoa = encode;
}
if (!global.atob) {
global.atob = decode;
}
However, the second problem is that:
Firebase also tries to use the native Blob class (implemented by react-native), but the react-native version of Blob incorrectly converts the Uint8Array data to a string, corrupting the upload.
I tried his solution of deleteing global.Blob and restoring it after the upload. Firebase must have become dependent upon blob though, because now it errors out since Blob doesn't exist. Edit: Blob is actually being called somewhere in AppEntry.bundle, the uploading works correctly.
I would like to keep my app in a managed workflow, so I would very much prefer not to eject.
My questions are as follows:
Where specifically in react-native is the broken Blob code that:
incorrectly converts the Uint8Array data to a string
Is there a way that I can, while avoiding errors or ejecting, upload 6 images at once to firebase storage? If so, how?
The solution I ended up following was this:
async function uploadImageAsync(uri) {
const ref = firebase
.storage()
.ref()
.child(uuid.v4());
const blob = await new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.onload = function() {
resolve(xhr.response);
};
xhr.onerror = function() {
reject(new TypeError("Network request failed"));
};
xhr.responseType = "blob";
xhr.open("GET", uri, true);
xhr.send(null);
});
var mimeString = uri
.split(",")[0]
.split(":")[1]
.split(";")[0];
const snapshot = await ref.put(blob, { contentType: mimeString });
let url = await snapshot.ref.getDownloadURL();
return url;
}
I found that I could not seem to get firebase's putString function to work, but I could create a blob out of the string using XMLHttpRequest. Then I just upload the blob to firebase

Unable to upload image to firebase storage with firebase functions

here is my code:-
exports.uploadImage = (req, res) => {
const BusBoy = require('busboy');
const path = require('path');
const os = require('os');
const fs = require('fs');
const busboy = new BusBoy({ headers: req.headers });
let imageFileName;
let imageToBeUploaded = {};
busboy.on('file', (fieldname, file, filename, encoding, mimetype) => {
const imageExtension = filename.split('.')[filename.split('.').length - 1];
imageFileName = `${Math.round(Math.random() * 100000000000)}.${imageExtension}`;
const filepath = path.join(os.tmpdir(), imageFileName);
imageToBeUploaded = { filepath, mimetype };
file.pipe(fs.createWriteStream(filepath));
});
busboy.on('finish', () => {
console.log('Busboy on started');
//code breaks here
admin.storage().bucket().upload(imageToBeUploaded.filepath, {
resumable: false,
metadata: {
metadata: {
contentType: imageToBeUploaded.mimetype
}
}
})
.then(() => {
const imageUrl = `https://firebasestorage.googleapis.com/v0/b/${config.storageBucket}/o/${imageFileName}?alt=media`;
console.log('logging image url' + imageUrl);
return db.doc(`/users/${req.user.handle}`).update({ imageUrl })
})
.then(() => {
return res.json({ message: 'Image uploaded successfully' });
})
.catch(err => {
console.error(err);
return res.status(500).json({ error: err.code });
})
});
busboy.end(req.rawBody);
}
I have mentioned where my code is breaking in a comment and the error I am getting is Error: Cannot parse response as JSON: Not Found
message: 'Cannot parse response as JSON: Not Found'
The error message says cannot parse response as JSON. Does that mean the response from firebase is not JSON? I have a token in the header of the request and an image in the body as form-data. I really have not clue what wrong, please help
I unfortunately can't identify the JSON parsing error, so I've instead rewritten the code to be more streamlined as #robsiemb eluded to.
Your uploadImage function appears to be configured as some middleware, so I have done the same below. This code will stream the uploaded data straight to Cloud Storage under a unique file name as generated from Reference.push().key to prevent conflicts.
In the code below,
The uploaded file will be stored at a location similar to: userData/someUserId/images/-JhLeOlGIEjaIOFHR0xd.png
The image's raw URL is not stored in the database because unless the file object or containing bucket is made public it will require a signed URL which can only last up to 7 days (see below).
More than one file can be accepted and uploaded. If this is undesired, configure the limits for the BusBoy instance.
Basic error handling for non-POST requests and missing file entries was added.
// import Firebase libraries & initialize
const admin = require('firebase-admin');
admin.initializeApp(); // initializes from environment variables
// import required modules
const BusBoy = require('busboy');
exports.uploadImage = (req, res) => {
if (req.method !== 'POST') {
res.sendStatus(405); // 405 METHOD_NOT_ALLOWED
return;
}
let busboy = new BusBoy({headers: req.headers}); // add {limits: {files: 1}} to limit to only a single file upload
let bucket = admin.storage().bucket();
let db = admin.firestore();
let storageFilepath;
let storageFile;
// Note: Currently only the last file is saved to `/users/${req.user.handle}`
busboy.on('file', (fieldname, file, filename, encoding, mimetype) => {
let fileext = filename.match(/\.[0-9a-z]+$/i)[0];
storageFilepath = `userData/${req.user.handle}/images/` + getUniqueName() + fileext;
storageFile = bucket.file(storageFilepath);
file.pipe(storageFile.createWriteStream({ gzip: true }));
})
.on('finish', () => {
if (!storageFile) {
res.status(400).json({error: 'expected file'}); // 400 BAD_REQUEST
return;
}
db.doc(`/users/${req.user.handle}`).update({ imagePath: storageFilepath })
.then(() => {
res.status(201).json({ message: 'Image uploaded successfully' }); // 201 CREATED
})
.catch((err) => {
console.error(err);
res.status(500).json({ error: err.code }); // 500 INTERNAL_SERVER_ERROR
});
})
.on('error', (err) => {
console.error(err);
res.status(500).json({ error: err.code });
});
req.pipe(busboy);
});
function getUniqueName() {
// push() without arguments returns a ThennableReference, which we'll abuse for it's key generation
return admin.database().ref().push().key;
}
If you did want the uploaded image to be publicly accessible, you could use the following .on('finish', ...) handler that adds in the File.makePublic() function:
.on('finish', () => {
if (!storageFile) {
res.status(400).json({error: 'expected file'}); // 400 BAD_REQUEST
return;
}
storageFile.makePublic()
.then(() => {
return db.doc(`/users/${req.user.handle}`).update({
imagePath: storageFilepath,
imageUrl: `https://storage.googleapis.com/${config.storageBucket}/${storageFilepath}`
});
})
.then(() => {
res.status(201).json({ message: 'Image uploaded successfully' }); // 201 CREATED
})
.catch((err) => {
console.error(err);
res.status(500).json({ error: err.code }); // 500 INTERNAL_SERVER_ERROR
});
})
Found a solution to the issue!
Essentially - you need to set up your Google Application Credentials. Go into firebase and look into your settings. You need to set up the environment variable GOOGLE_APPLICATION_CREDENTIALS so that firebase has your credentials when you access these files.
https://firebase.google.com/docs/admin/setup?authuser=1 for more information.
After you've done that, check the security settings in firebase, in every area you're dealing with. This should solve the problem (it's definitely a security issue and not your code).
This was the tutorial in question as well for those looking on . https://www.youtube.com/watch?v=m_u6P5k0vP0&t=7661s .
In my case it was wrong bucket Id configured - after correcting that i was able to upload file

A base64 image is not stored properly in cloud storage for firebase (aka firebase storage)

I am uploading a base64 image to cloud storage for firebase (aka firebase storage), using the firebase web interface.
The code that I am using for uploading (in React Native) is the following:
const ref = await firebase.storage().ref(user).child(curTime.toString());
await ref.putString(base64String, 'base64', { contentType: 'image/jpeg' })
However, when I try to view the image it cannot be displayed.
The uploaded string is available in my dropbox, here (download to view it).
The file in firebase cloud storage is available here.
I have downloaded this file into my dropbox, you can access it here (download to view it).
Here is the relevant code:
const uploadImageAsync = async(userId, curTime, photoUri) => {
const user = normalizeUserId(userId); // prepare for firebase write
let firebaseImageUrl = null;
let base64String;
try {
base64String = await FileSystem.readAsStringAsync(photoUri,
{ encoding: FileSystem.EncodingTypes.Base64 });
} catch (error) {
console.log(error);
return null;
}
const ref = await firebase.storage().ref(user).child(curTime.toString());
await ref.putString(base64String, 'base64', { contentType: 'image/jpeg' })
.then(async(snapshot) => {
await snapshot.ref.getDownloadURL().then(uploadURL => {
firebaseImageUrl = uploadURL;
}, (error) => { // failed to get uploadedURL location
console.log(error);
return null;
});
}, (error) => { // failed to upload image to firebase storage);
console.log(error);
return null;
});
return firebaseImageUrl;
};

Resources