Firebase Storage not displaying image properly (shows a small box) - reactjs

EDIT: I've updated the CORS config but its still showing the same error.
I have a Tinymce RTE on my page, and when u drop an image into the editor, I have some functions that upload it to firebase storage, then swaps out the src of the text editor with the url fetched from firebase. It works kinda ok, but its being displayed as a broken link image icon.
When I check the link, its because originally it downloads the image when the link is clicked. I added a metadata property when it uploads it, but now its just showing a tiny box.
Here is the code where the image dropped into the editor is uploaded into firebase storage
const imagesUploadHandler = async (blobInfo, success, failure) => {
try {
const file = blobInfo.blob();
const storageRef = ref(storage, file.name);
const metadata = {
contentType: 'image/jpeg',
};
await uploadBytes(storageRef, file, metadata);
const url = await getDownloadURL(storageRef);
console.log(url);
return url;
} catch (error) {
// Call the failure callback with the error message
console.log(error.message);
}
};
Originally, i didnt include the contentType metadata, and it was just uploading as application/octet-stream, which i assume is why it prompts you to save the image.
Image link: https://firebasestorage.googleapis.com/v0/b/cloudnoise-news.appspot.com/o/ref.jpg?alt=media&token=1edc90e7-1668-4a06-92a3-965ce275798b
Currently its displaying this
Somethings i checked through
firebase storage rules is in test mode, so should be able to read and write by anyone.
i tried sticking in different MIME types but it either shows the tiny box, or it shows "undefined"
the files upload successfully and the "swap" in Tinymce editor is also all good.
Any idea why this is happening?

you need to set the metadata tag
const metadata = {
contentType: file.type,
};
This should ensure that the correct content type is set when the image is uploaded to Firebase Storage.
If this does not resolve the issue, you may need to check that the URL returned from getDownloadURL is valid and points to the correct image. You can try opening the URL in a new browser tab to verify that the image is accessible.

I fixed it by adding a blob, I created a blob object with the file data, then i just made it upload the blob object instead of the single file.
const imagesUploadHandler = async (blobInfo, success, failure) => {
try {
const file = blobInfo.blob();
const storageRef = ref(storage, file.name);
const metadata = {
contentType: file.type,
};
// Create a new Blob object with the file data
const blob2 = await new Blob([file], { type: file.type });
// Upload the Blob to Firebase Storage
await uploadBytes(storageRef, blob2, metadata);
const url = await getDownloadURL(storageRef);
console.log(url);
return url;
} catch (error) {
// Call the failure callback with the error message;;
console.log(error.message)
}
};

Related

Trying to upload files to firebase storage but its not working. Error object is empty, how do I figure out what's wrong?

So i'm trying to send image files uploaded by my users to firebase storage using the file type input element. Like this:
<input
className={inputStyle}
{...register("donorPhotoFile")}
type="file"
accept=".png, .jpg,.jpeg"
></input>
So when I try to console.log the value returned of that input, im getting an object with the following properties:
name: "file_name.jpg",
size: ,
type: "image/png"
webkitRelativePath: ""
The /api/firebase is my api endpoint in next.js to upload my form data to firestore. From the firebase documentation, the 'file' should come from File API which I've did but its always unsuccessful and im not sure what im doing wrong.
const submitForm = async (data) => {
const imageFile = data.donorPhotoFile[0] //this is the file
const imageUpload = await fetch("/api/firestorage", {
method: "POST",
body: imageFile
});
const res = await imageUpload.json()
console.log(res)
}
//in my firestorage endpoint i've done this:
const storage = getStrorage(app) //app here is an instance of my firebase initialized
const handler = async (req, res) => {
const storageRef= ref(storage)
const imageFile = req.body
try {
uploadBytes(storageRef, imageFile);
res.status(200).json({statusRes: "success"})
} catch(error) {
res.status(400).json({statusRes: "failed", errorMessage: error})
}
}
Doing that returns a storage/invalid-root-operation error code with a message of:
"Firebase Storage: The operation 'uploadBytes' cannot be performed on a root reference, create a non-root reference using child, such as .child('file.png')
So tried to make a reference to a specific file and inserted the file name as a second parameter to storageRef like this:
const storageRef = ref(storage).child(`images/${req.body.name}`)
but its still not working but now i'm getting an empty error object so I can't figure out what's wrong now. So i actually tried checking what req.body is and it's returning this:
file object in my api endpoint
I don't understand why is it like that? And what im actually looking at? What i've sent in my post request is a File object. Like this:
File object i attached to my post request
You can create a reference to a path using Modular SDK as shown below:
const storageRef= ref(storage, `images/${req.body.name}`)
The .child() method is used in older name-spaced syntax.

How to handle very large file downloads using React js

We are trying to download large files say 1GB or 2 GB but after certain time though the backend still goes on the UI gives error as Failed to fetch for large files.
So how can we handle large file downloads using React js
Please help!
Code as below:
getFile = async (endpoint: string, id: string, params?: any) => {
const response = await fetch(
this.createUrl(endpoint + "/" + id, params),
this.getRequest("get", {
Accept: "application/octet-stream",
"Content-Type": "application/octet-stream",
}),
);
if (response.status === 200) {
return await response.blob();
} else {
throw Error(errorObj.error);
}
};
downloadFile = (filepath: any) => {
this.props.api.getFile(resource, filepath, {}).then((res: any) =>
this.setState(() => {
const url = window.URL.createObjectURL(new Blob([res]));
const link = document.createElement("a");
link.href = url;
link.setAttribute("download", path.basename(filepath));
document.body.appendChild(link);
link.click();
link.parentNode!.removeChild(link);
toaster.success("Successfully downloaded ");
}),
);
};
Using fetch will buffer the response into memory, and you can't quite expect to buffer 1 to 2 gigabytes in memory. (You could do something clever with IndexedDB like e.g. Mega does, but it's likely not worth it.)
Instead of fetching the data from an URL (let's call it URL A) and creating an Object URL from the content blob to put in a download link you click, simply put URL A in the download link.
If the endpoint at URL A requires some authentication or similar, you will need to change that to something that can be encoded into query parameters; maybe a token with a signature akin to what AWS S3 does with presigned URLs.

upload image to S3 presigned url using react-native-image-picker and axios

I am trying to get an presigned url image upload working correctly. Currently the upload succeeds when selecting an image from the IOS simulator, however when I actually try to view the file it seems the file is corrupted and will not open as an image. I suspect it has something to do with my FormData but not sure.
export async function receiptUpload(file) {
const date = new Date();
const headers = await getAWSHeaders();
const presignUrl = await request.post(
urls.fileUpload.presignUpload,
{file_name: `${date.getTime()}.jpg`},
{headers}
)
.then(res => res.data);
const formData = new FormData();
formData.append('file', {
name: `${date.getTime()}.jpg`,
uri: file.uri,
type: file.type
});
const fileUpload = presignUrl.presignUrl && await request.put(
presignUrl.presignUrl,
formData
)
.then(res => res.status === 200);
}
I have tried from other fixes to change the file uri like so...
Platform.OS === 'android' ? file.uri : file.uri.replace('file://', '');
however this does not seem to work either.
I did this just recently in my current project and the following code is a working example for my use case. I didn't need to convert to a blob either though I am uploading to AWS S3 so if you are uploading elsewhere that may be the issue.
export const uploadMedia = async (fileData, s3Data, setUploadProgress = () => {}) => {
let sendData = { ...fileData };
sendData.data.type = sendData.type;
let formData = new FormData();
formData.append('key', s3Data.s3Key);
formData.append('Content-Type', fileData.type);
formData.append('AWSAccessKeyId', s3Data.awsAccessKey);
formData.append('acl', 'public-read');
formData.append('policy', s3Data.s3Policy);
formData.append('signature', s3Data.s3Signature);
formData.append('file', sendData.data);
return axios({
method: 'POST',
url: `https://${s3Data.s3Bucket}.s3.amazonaws.com/`,
data: formData,
onUploadProgress: progressEvent => {
let percentCompleted = Math.floor((progressEvent.loaded * 100) / progressEvent.total)
setUploadProgress(percentCompleted);
}
})
}
I would first check to see where the issue is occurring. After uploading can you view it on whatever storage service you are trying to upload it to. If so it's something on React Native side. If it doesn't ever get uploaded to the location you know its an error in your upload process. Might help you track the exact location of the error.
I had to do this recently for a project. I believe the data is a base64 string when coming directly from the file input. So the issue is your are uploading a base64 string not the image by simply passing the data field. I had to process it before uploading to the signed URL with the following method.
private dataUriToBlob(dataUri) {
const binary = atob(dataUri.split(',')[1]);
const array = [];
for (let i = 0; i < binary.length; i++) {
array.push(binary.charCodeAt(i));
}
return new Blob([new Uint8Array(array)], { type: 'image/jpeg' });
}
This answer fixed it for me: How can I upload image directly on Amazon S3 in React Native?
I had tried uploading with axios and fetch with FormData. The download went through but the image file was not readable, even when downloaded to my Mac from the S3 console:
The file "yourfile.jpg" could not be opened. It may be damaged or use a file format that Preview doesn’t recognize.
Only after trying to upload with XHR with the correct Content-Type header did it work. Your signedUrl should be correct as well, which seems to be the case if the download goes through.

Why I cannot show my image(.svg) that uploaded to S3

I've uploaded few svg image to a S3 bucket(I'v set to public-all).After I uploaded all svg images.I get each image's URL.When I clicked on those Url it just download the images for me.
Also. Does anyone know why when I use those Url in a img tag (e.g.<img src='https://***.s3.***.amazonaws.com/***.svg />`).It just shows a broken image
Here is my lambda function
'use strict'
const aws = require('aws-sdk')
const s3 = new aws.S3()
const { parse } = require('aws-multipart-parser')
const response = (statusCode, data) => ({
statusCode,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*'
},
body: JSON.stringify(data)
})
exports.handler = async event => {
const inputData = parse(event, true)
if (inputData.file) {
try {
const params = {
Bucket: ***,
region: ***,
Key: `${inputData.file.filename}`,
Body: inputData.file.content,
ACL: 'public-read'
}
const s3Response = await s3.upload(params).promise()
return response(200, { statusCode: 200, url: s3Response['Location'] })
} catch (error) {
console.log('Error: ', error)
return response(500, {
error: error.message
})
}
} else {
return response(400, {
error: 'Please provide input file.'
})
}
}
You need to set the content type for you svg image on S3 to "image/svg+xml".
To change the content-type through S3 console :
Select the object on the S3 console
Click on actions
Click on change metadata
Change the content-type to image/svg+xml. The value is already available in the drop down.
As you are using the API gateway to upload the images, you can set the respective content type in you putObject request.
Reference : AWS S3 Put Object API documentation
You can refer the following AWS documentation to upload image with ContentType through JS :
https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#putObject-property
I faced a similar issue with SVGs being served from S3. On opening the svg url on the browser, it was working fine, but on passing the url in the imgs src attribute gave a broken image.
After comparing my svg with other working svgs I found that below attributes were missing in my svg.
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
On adding them it started working fine.
Something like this it has to be -
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
...
...
</svg>
My Front-End developers were complaining about this same issue. Blame AWS!
For some reason, they mark SVG images as binary/octet-stream instead of marking them as image/svg+xml.
My Back-End is developed in Golang and I use the official AWS SDK to Upload the files (https://github.com/aws/aws-sdk-go). What I did to fix the problem was pass by parameter the MimeType (ContentType) of the file.
If you don't pass AWS any MimeTypes then they will try to automatically identify a MimeType for your file, however they can make a mistake in some cases. If you pass a MimeType to AWS they will use your MimeType instead of trying to identify it, and that will likely solve the problem.
In my case in Golang I used this package to be able to automatically identify the MimeType https://github.com/gabriel-vasile/mimetype
var mimeTypePtr *string
mime := mimetype.Detect(file)
if mime != nil {
mimeType := mime.String()
mimeTypePtr = &mimeType
}
uploader := s3manager.NewUploader(cfg)
result, err := uploader.Upload(&s3manager.UploadInput{
Bucket: os.Getenv("AWS_BUCKET"),
Key: filename,
Body: file,
ContentType: mimeTypePtr,
})
In my case SVG image still gets forces download if its mime-type is image/svg
You can fix it by set the mime-type for the file is image/svg+xml

Upload to Firebase Storage executes but image comes back as a black square. Tried everything

Hey I've been building out a full-stack tinder app using react native + firebase auth/storage/realtimedb.
Everything has been going great so far but I've ran into an issue a few days ago and I don't know what's wrong with it.
I get back the correct uri of the image and pass it in as parameters to my uploadImage function and convert that to a blob. It uploads a file to firebase storage but it's not my image. This is what gets uploaded:
Image that is getting uploaded.
Weird things going on in the file description of my 'image'
The first things I notice is when I upload the image and look at the description of the supposed image I see that the size is 600,000 bytes which is strange because when I upload the pictures manually through the firebase storage console they are a few megabytes.
The second thing is the image preview is not working.
editAvi = async () => {
console.log('wtf')
await Permissions.askAsync(Permissions.CAMERA_ROLL);
const { cancelled, uri } = await ImagePicker.launchImageLibraryAsync({
allowsEditing: true,
});
if (!cancelled) {
this.setState({ image: uri });
}
console.log('The image is' + this.state.image)
};
uploadImage = async (uri, imageName) => {
// Create file metadata including the content type
var metadata = {
contentType: 'image/jpeg',
}
// Points to the root reference
var storageRef = firebase.storage().ref();
// Points to 'images'
const response = await fetch(uri);
const blob = await response.blob();
var ref = storageRef.child('images/' + this.state.currentID);
ref.put(uri, metadata);
console.log('This is the blob: ' + blob)
}
I've been researching this extensively for two days and have asked about it multiple times in a web development discord I'm in and I still can't fix it.
Please help me fix this! This is one of the last things I need to get this app done. :)
Found this question when I was also searching for an answer. I was able to solve this following the recommendation from a Github issue https://github.com/expo/expo/issues/2402#issuecomment-443726662
The main idea is to replace
const response = await fetch(uri);
const blob = await response.blob();
var ref = storageRef.child('images/' + this.state.currentID);
ref.put(uri, metadata);
with
const blob = await new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.onload = () => {
resolve(xhr.response);
};
xhr.onerror = (e) => {
reject(new TypeError("Network request failed"));
};
xhr.responseType = "blob";
xhr.open("GET", uri, true);
xhr.send(null);
});
var ref = storageRef.child('images/' + this.state.currentID);
ref.put(blob, metadata);
Fetch in know to have a problem in ReacNative when using Expo.
Hope this solves it.
Found this question when I was searching around for a solution to the exact same issue. I fixed it after about 12 hours of trial and error by adding 'application/octet-stream;BASE64' a the type when creating the blob (using rn-fetch-blob).
Blob.build(data, { type: 'application/octet-stream;BASE64' });
Not sure if that's the method you're using to create the blob, but if so, using that as the type fixed the issue for me.

Resources