Photo uploaded to firebase does not open correctly - reactjs

In storage it creates a hierarchy of folders, the same is in the path of the image, inside these folders it creates the image file, but when I download this image it does not open.
const finalizarAlbum = () => {
let albumNumber = Date.now();
let username = email.split('#')[0] + '_' + albumNumber;
let pathImage =
'file:///data/user/0/com.master/cache/tm-rn-image-crop-picker/8f403c24-c538-43f4-bb8d-e6c83be46a36.jpg';
firebase
.storage()
.ref()
.child('imagens')
.putString(pathImage)
.on(
'state_changed',
snapshot => {
let pct = (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
},
error => {
alert(error.code);
},
() => {}
);
};

Since your code calls putString() it is writing the string "file:///data/user/0/com.master/cache/tm-rn-image-crop-picker/8f403c24-c538-43f4-bb8d-e6c83be46a36.jpg" to Cloud Storage, not the contents of the file that the string refers to.
To upload the actual contents of the file, you'll want to call the putFile() method (if you're using react-native-firebase, or the put method with a File or Blob reference (if you're using the regular JavaScript SDK.

Related

Using progress handler when uploading files to AWS S3 with React

I am only recently dealing with the AWS SDK and thus please excuse if my approach is complete nonsense.
I want to upload a simple media file to my S3. I was following this tutorial and so far I am able to upload files without a problem. For userbility a progress bar would be a nice extra and therefore I was researching how to achieve this. I quickly found that the current AWS SDK v3 does not support httpUploadProgress anymore but we should use #aws-sdk/lib-storage instead. Using this library, I am still able to upload files to the S3 but I can't get the progress tracker to work! I assume this has something to do with me not fully understanding how to deal with async within a React component.
So here is my minified component example (I am using Chakra UI here)
const TestAWS: React.FC = () => {
const inputRef = useRef<HTMLInputElement | null>(null);
const [progr, setProgr] = useState<number>();
const region = "eu-west-1";
const bucketname = "upload-test";
const handleClick = async () => {
inputRef.current?.click();
};
const handleChange = (e: any) => {
console.log('Start file upload');
const file = e.target.files[0];
const target = {
Bucket: bucketname,
Key: `jobs/${file.name}`,
Body: file,
};
const s3 = new S3Client({
region: region,
credentials: fromCognitoIdentityPool({
client: new CognitoIdentityClient({ region: region }),
identityPoolId: "---MY ID---",
}),
});
const upload = new Upload({
client: s3,
params: target,
});
const t = upload.on("httpUploadProgress", progress => {
console.log("Progress", progress);
if (progress.loaded && progress.total) {
console.log("loaded/total", progress.loaded, progress.total);
setProgr(Math.round((progress.loaded / progress.total) * 100)); // I was expecting this line to be sufficient for updating my component
}
});
await upload.done().then(r => console.log(r));
};
console.log('Progress', progr);
return (
<InputGroup onClick={handleClick}>
<input ref={inputRef} type={"file"} multiple={false} hidden accept='video/*' onChange={e => handleChange(e)} />
<Flex layerStyle='uploadField'>
<Center w='100%'>
<VStack>
<PlusIcon />
<Text>Choose Video File</Text>
</VStack>
</Center>
</Flex>
{progr && <Progress value={progr} />}
</InputGroup>
);
};
export default TestAWS;
So basically I see the event getting fired (start file upload). Then it takes a while and I see the Promise result and the Progress, 100 in my console. This means to me that the state variable gets updated (at least once) but the component does not re-render?
What is it what I am doing wrong here? Any help appreciated!
Alright, I have found the solution. The callback on the state variable works fine and does what it should. But the configuration of the Upload object was off. After digging into the source I found out that the event listener only gets triggered if the uploader has uploaded more data. Because Uploader chunks the uploads you have two separate config parameters which allow you to split your upload into separate chunks. So
const upload = new Upload({
client: s3,
params: target,
queueSize: 4, // 4 is minimum
partSize: 5*1024*1024 // 5MB is minimum
});
basically does the job when the file we upload is larger than 5MB! Only then the event gets triggered again and updates the state variable.
Since this uploader is made for handling large file uploads, this totally makes sense and we could simply adjust queueSize and partSize according to the file we want to upload. Something like
let queueSize = 10;
const file = event.target.files[0];
let partSize = file.size / (10 * 1024 * 1024); // 1/10th of the file size in MB
const upload = new Upload({
client: s3,
params: target,
queueSize: partSize > 5 queueSize : undefined,
partSize: partSize > 5 ? partsize : undefined
});
Obviously, this can be done much more sophisticated but I did not want to spend too much time on this since it is not part of the original question.
Conclusion
If your file is large enough (>5MB), you will see progress update, depending on the number of chunks (of 5MB or more) you have chosen to split your file.
Since this only affects the handleChange method from the original example, I post this for completeness
const handleChange = async ( event ) => {
const file = event.target.files[0]
const target = {
Bucket: 'some-S3-bucket',
Key: `jobs/${file.name}`,
Body: file,
};
const s3 = new S3Client({
region: 'your-region',
credentials: fromCognitoIdentityPool({
client: new CognitoIdentityClient({ region: 'your-region' }),
identityPoolId: "your-id",
}),
});
// this will default to queueSize=4 and partSize=5MB
const upload = new Upload({
client: s3,
params: target
});
upload.on("httpUploadProgress", progress => {
console.log('Current Progress', progress);
setProgr(progress);
});
await upload.done().then(r => console.log(r));
}
Maybe this helps someone who has the same problem.
I came across your answer after having exactly the same problem (with Vue) today!
Indeed you are right: the AWS SDK JS v3 event only fires per part which is not at all clear and I wasted time debugging that too. Like for a 4MB file, it would only ever fire at 100%.
As you say, you can experiment with the part size but the minimum is 5MB and so on a slow connection I found it can appear that an upload is stuck as you have to wait for 5MB to get any data. Hmm. So what I did was look at the size of the file being uploaded. And if it is under a threshold (say 25MB, or whatever is applicable), well it's probably safe to upload that all in one go as you don't really need multipart uploading. And so I also made a presigned URL (https://aws.amazon.com/blogs/developer/generate-presigned-url-modular-aws-sdk-javascript/) which can be used to PUT using axios (since fetch does not support progress events yet).
So that way you can use upload for large files (where you actually need multipart uploading and where 5MB as a percentage of the file size is small), and use a presigned URL for small files and so get much more frequent updates.
The same progress event handler can be used by both.
this.$axios
.request({
method: "PUT",
url: SIGNED-URL-HERE,
data: file,
timeout: 3600 * 1000,
onUploadProgress: this.uploadProgress,
})
.then((data) => {
console.log("Success", data);
})
.catch((error) => {
console.log("Error", error.code, error.message);
});
Not ideal but it helps.

SAS key permissions getting change when using blobserviceclient

I am trying to upload files to blob storage in azure from a react webapp but am having issues with the signature in the authorization header.
This is how the sasToken looks in my code
const sasToken = `sv=2020-08-04&ss=bfqt&srt=sco&sp=rwdlacupx&se=2021-09-22T00:41:33Z&st=2021-09-20T16:41:33Z&spr=https&sig=svP%2FecNOoteE%2**************%3D`;
const containerName = `containername`;
const storageAccountName = "acountname";
This is what it looks like the the GET and PUT requests of getBlobsInContainer and createBlobinContainer run.
sv=2020-08-04&ss=bfqt&srt=sco&sp=ghostery&se=2021-09-22T00:41:33Z&st=2021-09-20T16:41:33Z&spr=https&sig=svP/FecNOoteE/**************=
It's somehow overwriting the permission parameter in the token.
https://accountname.blob.core.windows.net/containername?SAS&comp=list&restype=container&_=1632199288178
The 3 functions I have to deal with it.
// return list of blobs in container to display
const getBlobsInContainer = async (containerClient) => {
const returnedBlobUrls = [];
// get list of blobs in container
// eslint-disable-next-line
for await (const blob of containerClient.listBlobsFlat()) {
// if image is public, just construct URL
returnedBlobUrls.push(
`https://${storageAccountName}.blob.core.windows.net/${containerName}/${blob.name}`
);
}
return returnedBlobUrls;
};
const createBlobInContainer = async (containerClient, file) => {
console.log(`initialising blobclient for ${file.name}`);
// create blobClient for container
const blobClient = containerClient.getBlockBlobClient(file.name);
console.log("blobclient generated");
// set mimetype as determined from browser with file upload control
const options = { blobHTTPHeaders: { blobContentType: file.type } };
// upload file
await blobClient.uploadBrowserData(file, options);
console.log("Adding Metadata");
await blobClient.setMetadata({UserName : 'Reynolds'});
};
const uploadFileToBlob = async (file) => {
if (!file) return [];
// get BlobService = notice `?` is pulled out of sasToken - if created in Azure portal
const blobService = new BlobServiceClient(
`https://${storageAccountName}.blob.core.windows.net?${sasToken}`
);
console.log(`blobservice: https://${storageAccountName}.blob.core.windows.net/?${sasToken}`);
// get Container - full public read access
const containerClient = blobService.getContainerClient(containerName);
// upload file
await createBlobInContainer(containerClient, file);
// // get list of blobs in container
return getBlobsInContainer(containerClient);
};
I'm basically trying to figure out why this is happening and how to prevent/avoid it. The code runs till the console.log(`blobservice: https://${storageAccountName}.blob.core.windows.net/?${sasToken}`); before breaking due to Error 403 from invalid signature.

Firebase Storage, JSPDF Not Keeping Images

I am building a PDF in JSPDF with react. I am adding an image to this pdf using takescreenshot() from ArcGIS API (it is an image of a map). After creating the PDF I can download it no problem, but if I upload the PDF to firebase storage and automate an email with the downloadURL from firebase storage, the PDF no longer has the image in it.
I'm assuming there is some issue with firebase incorporating the image due to CORS, but I'm curious if anyone else has run into something like this.
Here is my code:
var blob = pdf.output('blob');
var id = uuid();
var storageRef = firebase.storage().ref(`${userInfo.currentCompany}`);
var storageRefName = `${moment().format('YYYY-MM-DD')}_${userInfo.currentAccount}.pdf`;
var noSpaceRef = storageRefName.replace(/\s/g, '_');
var uploadTask = storageRef.child(noSpaceRef).put(blob);
uploadTask.on('state_changed', (snapshot) =>
{
var progress = (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
console.log(progress);
}, function (error)
{
console.log(error);
}, () =>
{
uploadTask.snapshot.ref.getDownloadURL().then((downloadURL) =>
{
this.sendEmail(downloadURL)
});
});
Any help is appreciated!
When in doubt, make sure you're still passing the correct props. My blob was technically different when I would hit my download PDF button compared to email PDF button.
Classic.

Where is the Ionic/Cordova dataDirectory folder?

I'm building an Ionic app using the Ionic Native File plugin. I need the app to be able to read a file that I've saved in the app itself, but I don't understand where I need to store the file. The official Cordova documentation says something about a certain cordova.file.dataDirectory, whereas the Ionic documentation uses this example:
import { File } from '#ionic-native/file';
constructor(private file: File) { }
...
this.file.checkDir(this.file.dataDirectory, 'mydir').then( //stuff).catch( //stuff);
I don't understand where I need to put my file so that the app can read them.
Do not use file:///android_asset/
You can store the file in file.dataDirectory
Storing the file
I am using http to download a zip. I then unzip and delete the zipped file afterwards.
const down = this.http.downloadFile(uri, {}, {}, path + 'data.zip')
.then(data => {
console.log(data.status);
console.log("DOWNLOAD SUCCESS");
this.desc = "Extracting";
this.loading = 100;
this.downloading = false;
this.zip.unzip(path + 'data.zip', path, (this.UnzipProgress))
.then((result) => {
if(result === 0) this.file.removeFile(path,'data.zip');
if(result === -1) console.log('UNZIP FAILED');
});
})
.catch(error => {
console.log("ERROR::");
console.log(error.status);
console.log(error.error); // error message as string
console.log(error.headers);
});
Retrieving the file
The only thing that worked for me was convertFileSrc()
let win: any = window;
let safeURL = win.Ionic.WebView.convertFileSrc(this.file.dataDirectory+'data/yourFile.png');
Hope this helps

How to upload audio file to Firebase Storage?

I'm trying to upload audio file to Firebase Storage in my Ionic2 project.
First I recorded a audio file using Media plugin (Cordova plugin), and this file is playing well. From the Android storage and from the media plugin method (this.media.play()...;).
Second I need to push the recorded file to Firebase Storage.
this is my code:
let storageRef = firebase.storage().ref();
let metadata = {
contentType: 'audio/mp3',
};
let filePath = `${this.file.externalDataDirectory}`+`${this.fileName}`;
const voiceRef = storageRef.child(`voices/${this.fileName}`);
var blob = new Blob([filePath], {type: 'audio/mp3'});
voiceRef.put(blob);
After reading the Firebase doc, I can push blob to Firebase.
The file is successfully pushed to Firebase Storage with empty data (95 Byte).
this is screenshot:
The problem isn't a Firebase issue
My problem is solved by using the File cordova plugin method (readAsDataURL()) and the putString(fileBase64,firebase.storage.StringFormat.DATA_URL) method.
First, I create a file reference:
let filePath = "this.file.externalDataDirectory" + "this.fileName";
Then I transform the file to a base64 string by using the readAsDataURL method that returns a promise containing the file as a string base64. Also, I push the file to Firebase using the putString method that has two parameters the File that returned by the readAsDataURL and the second is firebase.storage.StringFormat.DATA_URL.
My Final code:
let storageRef = firebase.storage().ref();
let metadata = {
contentType: 'audio/mp3',
};
let filePath = `${this.file.externalDataDirectory}` + `${this.fileName}`;
this.file.readAsDataURL(this.file.externalDataDirectory, this.fileName).then((file) => {
let voiceRef = storageRef.child(`voices/${this.fileName}`).putString(file, firebase.storage.StringFormat.DATA_URL);
voiceRef.on(firebase.storage.TaskEvent.STATE_CHANGED, (snapshot) => {
console.log("uploading");
}, (e) => {
reject(e);
console.log(JSON.stringify(e, null, 2));
}, () => {
var downloadURL = voiceRef.snapshot.downloadURL;
resolve(downloadURL);
});
});
That's working fine for me.
Thanks.

Resources