Using Fetch instead of XMLHttpRequest in React Native - express

I was trying to upload images to S3 from my react native app by following and adapting this guide by heroku: https://devcenter.heroku.com/articles/s3-upload-node
Essentially I am using the aws-sdk on my express.js backend to generate pre-signed request for uploading images to S3 from react native.
Everything works well, so then I tried to convert the XMLHttpRequests into fetch requests, which seem to be favoured by react native. After the conversion, the files are still being uploaded to S3, but when I click on the image links, then the images wouldn't not show properly, instead an empty square is shown:
Empty square shown instead of image
More specifically it seems to be this piece of code conversion that causes it to happen:
From:
_uploadFile(file, signedRequest, url){
const xhr = new XMLHttpRequest();
xhr.open('PUT', signedRequest);
xhr.onreadystatechange = () => {
if(xhr.readyState === 4){
if(xhr.status === 200){
console.log("UPLOAD DONE");
} else {
alert('ERROR UPLOADING');
}
}
};
xhr.send(file);
}
To:
_uploadFile(file, signedRequest, url) {
let option = {
method: "PUT",
headers: {
"Content-Type": "image/jpeg",
},
body: JSON.stringify(file)
}
fetch(signedRequest, option)
.then(res => console.log("UPLOAD DONE"))
.catch(err => console.log("ERROR UPLOADING: ", err))
}
The file object being uploaded:
{
name: "profileImage",
type: "image/jpeg",
uri: 'data:image/jpeg;base64,' + response.data, //just a base64 image string
isStatic: true
}
Could anyone shed some light on why this could be happening, or have had similar experiences? Many thanks!

In your fetch example you put a JSON string in your body. It will be sent to S3 but it will not be interpreted as an image upload. You should be able to construct a FormData object yourself and pass it to fetch as the request body, but I think using XHR is the simpler option. According to this comment it's what Facebook does as well (the comment is over a year old).
If at all possible you should also try to use local URIs instead of passing Base64 encoded data. It takes quite a while to transfer a few MB of image data between JS and native.

Related

How to get a stream while a video is being recorded?

I'd like to upload data chunk to server while recording a video(not after recording).
I tried to go with react-native-camera or react-native-vision-camera.
but as far as I know, recordAsync method only resolves the final version of recorded video.
Is there any smart way to get video chunk or stream during recording.
or should I use react-native-fs or rn-fetch-blob or something like that?
== update ==
I could probably achieve it like it gets done in the link below.
https://medium.com/react-native-training/build-youtube-alike-livestreams-with-react-native-8dde24adf543
If your problem is with regards to uploading a large file to the server, maybe you can go with react-native-background-upload, and show a progress notification, this will upload the file even when the app is in background.
There is also a package where in chunck upload is possible by breaking the file into multiple chunks : react-native-chunk-upload
import ChunkUpload from 'react-native-chunk-upload';
const chunk = new ChunkUpload({
path: response.path, // Path to the file
size: 10095, // Chunk size (must be multiples of 3)
fileName: response.fileName, // Original file name
fileSize: response.size, // Original file size
// Errors
onFetchBlobError: (e) => console.log(e),
onWriteFileError: (e) => console.log(e),
});
chunk.digIn(this.upload.bind(this));
upload(file, next, retry, unlink) {
const body = new FormData();
body.append('video', file.blob); // param name
axios.post('url', body, {
headers: {
"Content-Type": "multipart/form-data",
"Accept": 'application/json',
// Customize the headers
"x-chunk-number": file.headers["x-chunk-number"],
"x-chunk-total-number": file.headers["x-chunk-total-number"],
"x-chunk-size": file.headers["x-chunk-size"],
"x-file-name": file.headers["x-file-name"],
"x-file-size": file.headers["x-file-size"],
"x-file-identity": file.headers["x-file-identity"]
}
}).then((res) => {
...
})

Axios network error even though the post request returns 200

After enabling CORS and everything on my server, the error persists.
In other forms inside my app, uploading pictures works... but in this exact form, on iPhone it works absolutely fine, but on android after submitting, all I get is a "network error" although the post returns 200. I think this is an axios problem. Only on android I get this issue.
my code is the following:
const data = new FormData()
data.append('subject_id', this.props.navigation.getParam('id'))
data.append('name', this.state.title)
data.append('progress', this.state.progress * 100)
data.append('description', this.state.description)
data.append('date', this.state.date)
data.append('image', {
uri: this.state.image,
type: 'image/jpeg',
name: 'image'
});
axios.post('https://example.com/api/auth/createTask', data, {
headers: {
'Authorization': access,
"Content-Type": "multipart/form-data"
},
}).then(res => {
this.props.navigation.navigate('ViewHW', { id: res.data.id })
}).catch(res => {
console.log(res)
})
I would really appreciate the help on this one.
I doubt that it's an axios issue.
If you're using an image picker or camera make sure you check the documentation as the path to the file selected differs between android and iOS.
Make sure you change the path of the item based on platform.OS === 'android'.
It should be clearly described in the docs of whatever you're using.
This ended up being a problem with react-native. The bug is now patched (the new version 0.63.3)

React Native Uploading Video to YouTube (Stuck at Processing)

I am attempting to upload video files to YouTube using their v3 API and their MediaUploader. It works in my ReactJS application, but not in my React Native application. When uploading via React Native, the upload completes, then stalls at 100%. In my YouTube account, I can see the new video file, but it is stuck at "Video is still being processed."
I believe the issue may be that I need to send a video file and not an object with a video uri but I don't know how to get around that.
I am using the YouTube MediaUploader from the CORS example at https://github.com/youtube/api-samples/blob/master/javascript/cors_upload.js I am using an OAuth 2.0 client Id, and this setup works correctly when using the ReactJS app via my website. I am using React Native Expo with Camera, which returns me an Object with a URI, for example:
Video File: Object {
"uri": "file:///var/mobile/Containers/Data/Application/353A7969-E2A8-4C80-B641-C80B2B029555/Library/Caches/ExponentExperienceData/%2540dj_walksalot%252Fwandereo/Camera/E971DFEC-AB3E-4B6D-892F-9027AFE47A1A.mov",
}
This file can be viewed in the application, and I can even successfully send this to my server for playback on the web app and in the React Native app. However, sending this object in the MediaUploader does not work. It will take an appropriate amount of time to upload, but then sits at 100%, while my YouTube account will show it has received the video with the correct metadata, but the video itself remains stuck at "Video is still being processed."
video_file: Object {
"uri": "file:///var/mobile/Containers/Data/Application/353A7969-E2A8-4C80-B641-C80B2B029555/Library/Caches/ExponentExperienceData/%2540dj_walksalot%252Fwandereo/Camera/E971DFEC-AB3E-4B6D-892F-9027AFE47A1A.mov",
}
export const uploadToYouTube = (access_token, video_file, metadata) => async (dispatch) => {
...cors_upload...
var uploader = new MediaUploader({
baseUrl: `https://www.googleapis.com/upload/youtube/v3/videos?part=snippet%2Cstatus&key=API_KEY`,
file: video_file,
token: access_token,
metadata: metadata,
contentType: 'video/quicktime',
// contentType: 'application/octet-stream',//"video/*",
// contentType = options.contentType || this.file.type || 'application/octet-stream';
params: {
part: Object.keys(metadata).join(',')
},
onError: function(data) {
// onError code
let err = JSON.parse(data);
dispatch(returnErrors(err.message, err.code))
console.log('Error: ', err);
},
onProgress: function(progressEvent){
// onProgress code
let percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total);
dispatch({
type: UPLOAD_PROGRESS,
payload: percentCompleted
});
},
onComplete: function(data) {
console.log('Complete');
// onComplete code
let responseData = JSON.parse(data);
dispatch({
type: UPLOAD_YOUTUBE_VIDEO,
payload: JSON.parse(data)
})
dispatch({
type: UPLOAD_PROGRESS,
payload: 0
});
}
});
uploader.upload();
}
Similar to my currently-working web app, after completing the upload, the "onComplete" function should fire, and YouTube should process the video. This does not happen. I believe it's because I'm attaching an object with a URI and not the actual file.
I was able to solve this from a post at Expert Mill by Joe Edgar at https://www.expertmill.com/2018/10/19/using-and-uploading-dynamically-created-local-files-in-react-native-and-expo/
By using fetch and .blob() I was able to convert the URI object to a data object and upload. Additional code:
const file = await fetch(video_file.uri);
const file_blob = await file.blob();
No need to install RNFetchBlob since this is in the Expo SDK.

How to display Image from camera roll url using react-native-camera?

I use react-native-camera for capturing images and saving them in cameraRoll (CaptureTarget) for iOS devices, on capture I get image path in the following form
"assets-library://asset/asset.JPG?id=8B55D3E5-11CE-439A-8FC6-D55EB1A88D7E&ext=JPG"
How to use this path to display the image in Image component (from react-native)?
Previously I was using disk as CaptureTarget option, and I was able to show that image url Image component but now the requirements are to save image in camera roll?
I have used RNFetchBlob to get base64 from "assets-library://.." url, my capture function is
this.camera.capture()
.then((data) => {
// console.log(data);
RNFetchBlob.fs.readFile(data.path, 'base64')
.then((base64data) => {
let base64Image = `data:image/jpeg;base64,${base64data}`;
this.props.addImagesToUntagged(data.path);
})
})
after that i give user some in-app functionality on this base64 data and finally when I need to send this to s3 server, I use axios & RNFetchBlob to send this data.following code gives me signed url for s3
axios.get(ENDPOINT_TO_GET_SIGNED_URL, {params:{'file-name': file.name,'file-type': file.type,"content-type": 'evidence'}})
.then(function (result) {
// console.log(result);
returnUrl = result.data.url;
var signedUrl = result.data.signedRequest;
return uploadFile(file,signedUrl)
})
and in my uploadFile function i upload images through following code
RNFetchBlob.fetch('PUT', signedUrl,
{'Content-Type' : file.type},
RNFetchBlob.wrap(file.uri)
)
.then(resolve)

Uploading videos using formdata in react native

Has anyone successfully uploaded a video via React Native Formdata()? The code below attempts to upload a .mov file from the camera roll URI but in fact only the first frame of the video (a JPEG) gets uploaded. What's the issue here?
var movVideo = {
uri: uriFromCameraRoll,
type: 'video/quicktime',
name: 'something.mov',
};
var body = new FormData();
body.append('video', movVideo);
body.append('title', 'A beautiful video!');
fetch('https://mysite/upload_asset', {
method: "POST",
headers: {
'Accept': 'application/json',
'Content-Type': 'multipart/form-data'
},
body: body,
}).then((response) => response.json())
.then((responseJson) => {
//only the first frame of the video got uploaded
console.log(responseJson);
});
Had the same issue. Looks like React Native does not return the correct stream for videos with asset library URIs. Pictures seem to work fine. I would need to dig deeper before submitting an issue though.
I suggest you take a look at react-native-fetch-blob, which provides an improved fetch polyfill with Blob support. This implementation handles videos from the camera roll just fine. Also, the changes needed to use this module are minimal (include the polyfill, wrap URI with RNFetchBlob.wrap).