AWS Amplify breaks binary posts to AWS API-Gateway - react-native

I'm creating a web application using React / Aws Amplify as the front end and AWS API-Gateway and S3 in the back end and Cognito as our user authentication. I have a page where the user needs to submit a form and a file. I was able to set this up for text files but once I started to work on binary files bad things happened.
I build the API in AWS and tested it using Postman as well as Curl and I'm able to post binary files. When I make the call through Amplify it stops working. I can make the call through Axios but then I need to turn off the Authentication, hence why I'm trying to do this through amplify. I also do not want to use amplify storage as it does not meet my needs. What typically happens is the file size is larger then the file sent and when I download it out of S3 it does not work any longer.
import React, { Component } from "react";
import "./Dashboard.css";
import { API } from "aws-amplify";
import { saveAs } from 'file-saver';
import axios from 'axios';
export default class Home extends Component {
uploadLambda = async (event) => {
//This one work if I turn off User Authentication
let file = event.target.files[0];
let reader = new FileReader();
reader.readAsArrayBuffer(file);
reader.onload = async () => {
try
{
return axios({
method: 'POST',
url: 'https://XXXXXXXXXX.execute-api.us-east-1.amazonaws.com/dev/upload',
headers: {
'Content-Type': 'application/pdf'
},
data: reader.result
});
}
catch(e)
{
console.log(e);
}
}
}
uploadImageLambda = async(event) => {
//This is the one I'm trying to get to work with binary files
var file_name = event.target.files[0].name;
console.log('Saving File Via Lambda: ' + file_name);
var reader = new FileReader();
reader.readAsDataURL(event.target.files[0]);
//reader.readAsBinaryString(event.target.files[0]);
//reader.readAsArrayBuffer(event.target.files[0]);
reader.onload = async () =>
{
try
{
/**
Someone suggested this but it does not fix the problem
let encoded = reader.result.toString().replace(/^data:(.*,)?/, '');
if ((encoded.length % 4) > 0) {
encoded += '='.repeat(4 - (encoded.length % 4));
}
console.log(encoded);
//"isBase64Encoded": "true",
**/
return await API.post("lambdadocshell", 'upload', { 'headers': { 'Content-Type': 'application/pdf', }, 'body': reader.result });
}
catch (e)
{
console.log(e);
}
}
}
render() {
return (
<div className="FileTest">
<h1>Upload A File</h1>
<form onSubmit={this.handleSubmit} enctype="multipart/form-data">
Select File: <input type="file" onChange={this.uploadLambda} />
</form>
</div>
);
}
}
In the code above you can see 2 upload functions, both hit the same API-Gateway. uploadLambda works but only if authentication on the API-Gateway is turned off. uploadImageLambda does not work regardless of authentication. We do use the Amplify in a number of other pages to move JSON back and forth to the API without issues. You can also see commented code as we tried a number of different ways to get amplify to work.

After talking with AWS support, they said that amplify apparently does a JSON.stringify to the data which then increases the length of the file. Currently there does not seem to be a workaround for this issue. As such they suggested that I use Axios to make the request to API Gateway. Hopefully this will be resolved in the future.

Related

react-native (Expo) upload file on background

In my Expo (react-native) application, I want to do the upload task even if the application is in the background or killed.
the upload should be done to firebase storage, so we don't have a REST API.
checked out the Expo task manager library, but I could not figure out how it should be done. is it even possible to achieve this goal with Expo? is the TaskManager the correct package for this task?
there are only some Expo packages that could be registered as a task (e.g. backgroundFetch), and it is not possible to register a custom function (in this case uploadFile method).
I even got more confused as we should enable add UIBackgroundModes key for iOS but it only has audio,location,voip,external-accessory,bluetooth-central,bluetooth-peripheral,fetch,remote-notification,processing as possible values.
I would appreciate it if you can at least guide me on where to start or what to search for, to be able to upload the file even if the app is in the background is killed/terminated.
import { getStorage, ref, uploadBytes } from "firebase/storage";
const storage = getStorage();
const storageRef = ref(storage, 'videos');
const uploadFile = async (file)=>{
// the file is Blob object
await uploadBytes(storageRef, file);
}
I have already reviewed react-native-background-fetch, react-native-background-upload, react-native-background-job . upload should eject Expo, job does not support iOS, and fetch is a fetching task designed for doing task in intervals.
if there is a way to use mentioned libraries for my purpose, please guide me :)
to my understanding, the Firebase Cloud JSON API does not accept files, does it ? if so please give me an example. If I can make storage json API work with file upload, then I can use Expo asyncUpload probably without ejecting.
I have done something similar like you want, you can use expo-task-manager and expo-background-fetch. Here is the code as I used it. I Hope this would be useful for you.
import * as BackgroundFetch from 'expo-background-fetch';
import * as TaskManager from 'expo-task-manager';
const BACKGROUND_FETCH_TASK = 'background-fetch';
const [isRegistered, setIsRegistered] = useState(false);
const [status, setStatus] = useState(null);
//Valor para que se ejecute en IOS
BackgroundFetch.setMinimumIntervalAsync(60 * 15);
// Define the task to execute
TaskManager.defineTask(BACKGROUND_FETCH_TASK, async () => {
const now = Date.now();
console.log(`Got background fetch call at date: ${new Date(now).toISOString()}`);
// Your function or instructions you want
return BackgroundFetch.Result.NewData;
});
// Register the task in BACKGROUND_FETCH_TASK
async function registerBackgroundFetchAsync() {
return BackgroundFetch.registerTaskAsync(BACKGROUND_FETCH_TASK, {
minimumInterval: 60 * 15, // 1 minutes
stopOnTerminate: false, // android only,
startOnBoot: true, // android only
});
}
// Task Status
const checkStatusAsync = async () => {
const status = await BackgroundFetch.getStatusAsync();
const isRegistered = await TaskManager.isTaskRegisteredAsync(
BACKGROUND_FETCH_TASK
);
setStatus(status);
setIsRegistered(isRegistered);
};
// Check if the task is already register
const toggleFetchTask = async () => {
if (isRegistered) {
console.log('Task ready');
} else {
await registerBackgroundFetchAsync();
console.log('Task registered');
}
checkStatusAsync();
};
useEffect(() => {
toggleFetchTask();
}, []);
Hope this isn't too late to be helpful.
I've been dealing with a variety of expo <-> firebase storage integrations recently, and here's some info that might be helpful.
First, I'd recommend not using the uploadBytes / uploadBytesResumable methods from Firebase. This Thread has a long ongoing discussion about it, but basically it's broken in v9. Maybe in the future the Firebase team will solve the issues, but it's pretty broken with Expo right now.
Instead, I'd recommend either going down the route of writing a small Firebase function that either gives a signed-upload-url or handles the upload itself.
Basically, if you can get storage uploads to work via an http endpoint, you can get any kind of upload mechanism working. (e.g. the FileSystem.uploadAsync() method you're probably looking for here, like #brentvatne pointed out, or fetch, or axios. I'll show a basic wiring at the end).
Server Side
Option 1: Signed URL Upload.
Basically, have a small firebase function that returns a signed url. Your app calls a cloud function like /get-signed-upload-url , which returns the url, which you then use. Check out: https://cloud.google.com/storage/docs/access-control/signed-urls for how you'd go about this.
This might work well for your use case. It can be configured just like any httpsCallable function, so it's not much work to set up, compared to option 2.
However, this doesn't work for the firebase storage / functions emulator! For this reason, I don't use this method, because I like to intensively use the emulators, and they only offer a subset of all the functionalities.
Option 2: Upload the file entirely through a function
This is a little hairier, but gives you a lot more fidelity over your uploads, and will work on an emulator! I like this too because it allows doing upload process within the endpoint execution, instead of as a side effect.
For example, you can have a photo-upload endpoint generate thumbnails, and if the endpoint 201's, then you're good! Rather than the traditional Firebase approach of having a listener to cloud storage which would generate thumbnails as a side effect, which then has all kinds of bad race conditions (checking for processing completion via exponentiational backoff? Gross!)
Here are three resources I'd recommend to go about this approach:
https://cloud.google.com/functions/docs/writing/http#multipart_data
https://github.com/firebase/firebase-js-sdk/issues/5848
https://github.com/mscdex/busboy
Basically, if you can make a Firebase cloud endpoint that accepts a File within formdata, you can have busboy parse it, and then you can do anything you want with it... like upload it to Cloud Storage!
an outline of this:
import * as functions from "firebase-functions";
import * as busboy from "busboy";
import * as os from "os";
import * as path from "path";
import * as fs from "fs";
type FieldMap = {
[fieldKey: string]: string;
};
type Upload = {
filepath: string;
mimeType: string;
};
type UploadMap = {
[fileName: string]: Upload;
};
const MAX_FILE_SIZE = 2 * 1024 * 1024; // 2MB
export const uploadPhoto = functions.https.onRequest(async (req, res) => {
verifyRequest(req); // Verify parameters, auth, etc. Better yet, use a middleware system for this like express.
// This object will accumulate all the fields, keyed by their name
const fields: FieldMap = {};
// This object will accumulate all the uploaded files, keyed by their name.
const uploads: UploadMap = {};
// This will accumulator errors during the busboy process, allowing us to end early.
const errors: string[] = [];
const tmpdir = os.tmpdir();
const fileWrites: Promise<unknown>[] = [];
function cleanup() {
Object.entries(uploads).forEach(([filename, { filepath }]) => {
console.log(`unlinking: ${filename} from ${path}`);
fs.unlinkSync(filepath);
});
}
const bb = busboy({
headers: req.headers,
limits: {
files: 1,
fields: 1,
fileSize: MAX_FILE_SIZE,
},
});
bb.on("file", (name, file, info) => {
verifyFile(name, file, info); // Verify your mimeType / filename, etc.
file.on("limit", () => {
console.log("too big of file!");
});
const { filename, mimeType } = info;
// Note: os.tmpdir() points to an in-memory file system on GCF
// Thus, any files in it must fit in the instance's memory.
console.log(`Processed file ${filename}`);
const filepath = path.join(tmpdir, filename);
uploads[filename] = {
filepath,
mimeType,
};
const writeStream = fs.createWriteStream(filepath);
file.pipe(writeStream);
// File was processed by Busboy; wait for it to be written.
// Note: GCF may not persist saved files across invocations.
// Persistent files must be kept in other locations
// (such as Cloud Storage buckets).
const promise = new Promise((resolve, reject) => {
file.on("end", () => {
writeStream.end();
});
writeStream.on("finish", resolve);
writeStream.on("error", reject);
});
fileWrites.push(promise);
});
bb.on("close", async () => {
await Promise.all(fileWrites);
// Fail if errors:
if (errors.length > 0) {
functions.logger.error("Upload failed", errors);
res.status(400).send(errors.join());
} else {
try {
const upload = Object.values(uploads)[0];
if (!upload) {
functions.logger.debug("No upload found");
res.status(400).send("No file uploaded");
return;
}
const { uploadId } = await processUpload(upload, userId);
cleanup();
res.status(201).send({
uploadId,
});
} catch (error) {
cleanup();
functions.logger.error("Error processing file", error);
res.status(500).send("Error processing file");
}
}
});
bb.end(req.rawBody);
});
Then, that processUpload function can do anything you want with the file, like upload it to cloud storage:
async function processUpload({ filepath, mimeType }: Upload, userId: string) {
const fileId = uuidv4();
const bucket = admin.storage().bucket();
await bucket.upload(filepath, {
destination: `users/${userId}/${fileId}`,
{
contentType: mimeType,
},
});
return { fileId };
}
Mobile Side
Then, on the mobile side, you can interact with it like this:
async function uploadFile(uri: string) {
function getFunctionsUrl(): string {
if (USE_EMULATOR) {
const origin =
Constants?.manifest?.debuggerHost?.split(":").shift() || "localhost";
const functionsPort = 5001;
const functionsHost = `http://${origin}:${functionsPort}/{PROJECT_NAME}/${PROJECT_LOCATION}`;
return functionsHost;
} else {
return `https://{PROJECT_LOCATION}-{PROJECT_NAME}.cloudfunctions.net`;
}
}
// The url of your endpoint. Make this as smart as you want.
const url = `${getFunctionsUrl()}/uploadPhoto`;
await FileSystem.uploadAsync(uploadUrl, uri, {
httpMethod: "POST",
uploadType: FileSystem.FileSystemUploadType.MULTIPART,
fieldName: "file", // Important! make sure this matches however you want bussboy to validate the "name" field on file.
mimeType,
headers: {
"content-type": "multipart/form-data",
Authorization: `${idToken}`,
},
});
});
TLDR
Wrap Cloud Storage in your own endpoint, treat it like a normal http upload, everything plays nice.

How do we display images from s3 on browser using vue/nuxt?

I am displaying profile picture of a user on a vue/nuxt app. This picture is uploaded to S3 through a file API.
While receiving the image from file API and displaying the profile pic, I am not able to convert it to the right format.
My problems:
s3 says the file content type is application/octet-stream. I was expecting a specific type like image/jpeg or image/png. There's an npm that came to rescue here.
Wrapping the returned file in a blob using createObjectURL creates a dummy URL / link. We can set the mime type to blob while creating like so const blob = new Blob([response.data],{type=response.type}
Using responseType - Is it blob, arrayBuffer or stream? I went ahead with experimenting on blob
Step 1: File API - Reading from S3 using AWS client v3 (File: FileService.js)
let goc = new GetObjectCommand({
...
});
s3client.send(goc)
.then(response => {
if(response){
const body = response.Body;
const tempFileName = path.join(config.FILE_DOWNLOAD_PATH, file_name);
const tempFile = fs.createWriteStream(tempFileName);
body.pipe(tempFile);
resolve(Service.createSuccessResponse(
{
file_name_local: tempFileName,
file_name: file_name,
content_length: response.ContentLength,
}
,"SUCCESS")); // This is **responsePayload** in the next snippet.
...
Step 2: Send the response using expressjs (File: Controller.js)
if(responsePayload.file_name_local){
response.set('Content-Length',responsePayload.content_length);
response.write(fs.readFileSync(responsePayload.file_name_local));
response.end();
response.connection.end();
}
Step 3: Define image (File: view-profile.vue)
<template>
...
<v-img src="dpURL"/>
...
</template>
Step 4 - EAGER DOWNLOAD: Receive image as blob (File: view-profile.vue)
<script>
...
mounted(){
...
this.getDP(this.profileInfo.dp_url).then(r => {
this.dpURL = window.URL.createObjectURL(new Blob([r.data])); // Do we need explicit MIME here?
}).catch(e => {
console.error("DP not retrieved: "+JSON.stringify(e));
})
...
},
methods: {
...
getDP(file_name){
return new Promise(
async(resolve, reject) => {
callGenericAPI({
url: this.$config.API_HOST_URL+'/file',
configObj: {
method: 'get',
params: {
file_name: file_name,
file_category: 'user-dp'
},
headers: {
'api_token': idToken,
'Cache-Control':'no-cache'
},
responseType: 'blob'
},
$axios: this.$axios //Just some crazy way of calling axios. Don't judge me here.
})
.then(r => {
console.log("DP received.");
resolve(r.data);
})
.catch(e => {
console.error("DP not received." + JSON.stringify(e));
reject(e);
})
}
)
},
...
}
Step 5 - LAZY DOWNLOAD: Receive image as blob (File: view-profile.vue > custom-link.vue - child) after clicking the link
File: view-profile.vue
<template>
...
<CustomLink file_name="fileName" file_category="USER_DP" label="User Profile Picture"/>
...
</template>
File: custom-link.vue
<template>
<a v-text="label"
#click.prevent="downloadItem()">
</template>
<script>
...
methods:{
downloadItem(){
this.$axios.get(...)
.then(response => {
const blob = new Blob([response.data],{ type: response.data.type }) // Here I tried Uint8Array.from(response.data) as well
const link = document.createElement('a')
link.href = window.URL.createObjectURL(blob,{ type: response.data.type });
link.download = this.file_name
link.click()
URL.revokeObjectURL(link.href)
}
...
}
...
}
...
</script>
The problem here is that image is being downloaded. I can see it from devtools network tab. But I just can't link/display it reactively (shown in Step 3).
After Step 5, the file downloads but has a bloated size compared to original. When I convert response.data to Uint8Array, the file shrinks compared to original.
It looks like a very simple get and display image problem but haven't got the combination of mimetypes and utilities right. Express supports sendFile and download options for the files. But both the calls don't seem to work for some reason!
Do you have any pointers?
Can't we just download the image somewhere on the webserver directory and reactive link it? Even that should be fine with me. Can avoid some API calls.
The following code is taken from https://aws.plainenglish.io/using-node-js-to-display-images-in-a-private-aws-s3-bucket-4c043ed5c5d0 :
function encode(data){
let buf = Buffer.from(data);
let base64 = buf.toString('base64');
return base64
}
getImage()
.then((img)=>{
let image="<img src='data:image/jpeg;base64," + encode(img.Body) + "'" + "/>";
let startHTML="<html><body></body>";
let endHTML="</body></html>";
let html=startHTML + image + endHTML;
res.send(html)
}).catch((e)=>{
res.send(e)
})
#coder.in.me's post leads us to the answer - obtain S3 Object's presignedURL.
Added benefit - you can also expire the URL beyond a time-limit.

Nuxt ServerMiddleware Returning HTML Doc

I am building out a webpage which needs to make a call to the Google Geocoder api.
In order to hide the api key from public view, I am trying to set up server middleware to act as a REST api endpoint.
I have checked through all of the documentation and copied all of it, but the response is always the same. I receive the entirety of the html body back from the axios request rather than anything else I send back via express.
In my component I have the following code:
computed: {
normalizedAddress() {
return `${this.member.address.street} ${this.member.address.city}, ${this.member.address.state} ${this.member.address.zip}`.replace(
/\s/g,
'+'
)
}
},
methods: {
async getLocation() {
try {
const res = await axios.get(
`/api/geocode/${this.normalizedAddress}`
)
console.log(res)
} catch (err) {
console.log(err)
}
}
},
In nuxt.config.js I have this setup
serverMiddleware: ['~/api/geocode.js'],
In the root of my project I have an api folder with geocode.js stored there.
geocode.js is below
import express from 'express';
import axios from "axios";
let GEO_API = "MY_API_KEY"
const app = express();
app.use(express.json());
app.get("/", async (req, res) => {
const uri = `https://maps.googleapis.com/maps/api/geocode/json?address=${req.params.address}&key=${GEO_API}`
try {
const code = await axios.get(uri);
if (code.status !== "OK") {
return res.status(500).send(code.status)
}
return res.status(200).send(code);
} catch (err) {
return res.status(500).send(err);
}
});
export default {
path: "/api/geocode/:address",
handler: app
}
Again. The response always has the entire html document from the website sent back (some 100 pages of code).
Even when I set the response to fixed text, that is not sent.
The only detail I can think of that might be interrupting it is that I have my own custom routing setup using the #nuxtjs/router build module in use.

How to upload image to strapi?

How can I upload images to Strapi server?
I use the GraphQL plugin.
I prefer a single endpoint for all of API
I found an article about how to manage file uploads but I have some questions
Do I need to convert my image to a Base64 string?
My production server will be PostgreSQL. I was planning to store images as Blob. But now it turns out I can only send Form-Data to Strapi servers.
Do I need something like apollo-upload-client?
Note: Currently I use vue-apollo and nativescript-vue as frontend.
import VueApollo from "vue-apollo";
import { ApolloClient, InMemoryCache, HttpLink } from "apollo-boost";
import { setContext } from "apollo-link-context";
If you are trying to upload files with GraphQL, I suggest you check this gist - https://gist.github.com/alexandrebodin/fedc71c8513bfbb6283cc90ae62755c5
You should have all the information you need to achieve that.
Thanks to the answer of #Jim LAURIE I made my node work:
import { GraphQLClient, gql } from "graphql-request"
import { createReadStream } from "fs";
[...]
const endpoint = 'http://localhost:1337/graphql';
const graphQLClient = new GraphQLClient(endpoint, {
headers: {
authorization: `Bearer ${jwt}`,
},
});
try {
const data = await graphQLClient.request( gql`
mutation($file: Upload!) {
upload(file: $file) {
id
name
}
}
`, {
file: createReadStream(__dirname + '/Strapi/test/picture.jpg') // ⚠ Ensure path is correct or the stream will never send data and you will have Socket Hang out error
});
console.info("ID of file:" + data.upload.id);
console.info(data)
console.info("data")
} catch (error) {
console.error(error)
console.error("error")
}
If you don't know how get the JWT check the units testing docs of Strapi, translate to GraphQL should be easy.

How to upload file to server using react-native

I am developing a app where i need to upload an image to the server. Based on the image i get a response which i need to render?.
Can you please help me how to upload an image using react-native?.
There is file uploading built into React Native.
Example from React Native code:
var photo = {
uri: uriFromCameraRoll,
type: 'image/jpeg',
name: 'photo.jpg',
};
var body = new FormData();
body.append('authToken', 'secret');
body.append('photo', photo);
body.append('title', 'A beautiful photo!');
var xhr = new XMLHttpRequest();
xhr.open('POST', serverURL);
xhr.send(body);
My solution is using fetch API and FormData.
Tested on Android.
const file = {
uri, // e.g. 'file:///path/to/file/image123.jpg'
name, // e.g. 'image123.jpg',
type // e.g. 'image/jpg'
}
const body = new FormData()
body.append('file', file)
fetch(url, {
method: 'POST',
body
})
I wrote something like that. Check out https://github.com/kamilkp/react-native-file-transfer
I have been struggling to upload images recently on react-native. I didn't seem to get the images uploaded. This is actually because i was using the react-native-debugger and network inspect on while sending the requests. Immediately i switch off network inspect, the request were successful and the files uploaded.
I am using the example from this answer above it works for me.
This article on github about the limitations of network inspect feature may clear things for you.
Just to build on the answer by Dev1, this is a good way to upload files from react native if you also want to show upload progress. It's pure JS, so this would actually work on any Javascript file.
(Note that in step #4 you have to replace the variables inside the strings with the type and file endings. That said, you could just take those fields out.)
Here's a gist I made on Github: https://gist.github.com/nandorojo/c641c176a053a9ab43462c6da1553a1b
1. for uploading one file:
// 1. initialize request
const xhr = new XMLHttpRequest();
// 2. open request
xhr.open('POST', uploadUrl);
// 3. set up callback for request
xhr.onload = () => {
const response = JSON.parse(xhr.response);
console.log(response);
// ... do something with the successful response
};
// 4. catch for request error
xhr.onerror = e => {
console.log(e, 'upload failed');
};
// 4. catch for request timeout
xhr.ontimeout = e => {
console.log(e, 'cloudinary timeout');
};
// 4. create formData to upload
const formData = new FormData();
formData.append('file', {
uri: 'some-file-path', // this is the path to your file. see Expo ImagePicker or React Native ImagePicker
type: `${type}/${fileEnding}`, // example: image/jpg
name: `upload.${fileEnding}` // example: upload.jpg
});
// 6. upload the request
xhr.send(formData);
// 7. track upload progress
if (xhr.upload) {
// track the upload progress
xhr.upload.onprogress = ({ total, loaded }) => {
const uploadProgress = (loaded / total);
console.log(uploadProgress);
};
}
2. uploading multiple files
Assuming you have an array of files you want to upload, you'd just change #4 from the code above to look like this:
// 4. create formData to upload
const arrayOfFilesToUpload = [
// ...
];
const formData = new FormData();
arrayOfFilesToUpload.forEach(file => {
formData.append('file', {
uri: file.uri, // this is the path to your file. see Expo ImagePicker or React Native ImagePicker
type: `${type}/${fileEnding}`, // example: image/jpg
name: `upload.${fileEnding}` // example: upload.jpg
});
})
In my opinion, the best way to send the file to the server is to use react-native-fs package, so install the package
with the following command
npm install react-native-fs
then create a file called file.service.js and modify it as follow:
import { uploadFiles } from "react-native-fs";
export async function sendFileToServer(files) {
return uploadFiles({
toUrl: `http://xxx/YOUR_URL`,
files: files,
method: "POST",
headers: { Accept: "application/json" },
begin: () => {
// console.log('File Uploading Started...')
},
progress: ({ totalBytesSent, totalBytesExpectedToSend }) => {
// console.log({ totalBytesSent, totalBytesExpectedToSend })
},
})
.promise.then(({ body }) => {
// Response Here...
// const data = JSON.parse(body); => You can access to body here....
})
.catch(_ => {
// console.log('Error')
})
}
NOTE: do not forget to change the URL.
NOTE: You can use this service to send any file to the server.
then call that service like the following:
var files = [{ name: "xx", filename:"xx", filepath: "xx", filetype: "xx" }];
await sendFileToServer(files)
NOTE: each object must have name,filename,filepath,filetype
A couple of potential alternatives are available. Firstly, you could use the XHR polyfill:
http://facebook.github.io/react-native/docs/network.html
Secondly, just ask the question: how would I upload a file in Obj-C? Answer that and then you could just implement a native module to call it from JavaScript.
There's some further discussion on all of this on this Github issue.
Tom's answer didn't work for me. So I implemented a native FilePickerModule which helps me choose the file and then use the remobile's react-native-file-transfer package to upload it. FilePickerModule returns the path of the selected file (FileURL) which is used by react-native-file-transfer to upload it.
Here's the code:
var FileTransfer = require('#remobile/react-native-file-transfer');
var FilePickerModule = NativeModules.FilePickerModule;
var that = this;
var fileTransfer = new FileTransfer();
FilePickerModule.chooseFile()
.then(function(fileURL){
var options = {};
options.fileKey = 'file';
options.fileName = fileURL.substr(fileURL.lastIndexOf('/')+1);
options.mimeType = 'text/plain';
var headers = {
'X-XSRF-TOKEN':that.state.token
};
options.headers = headers;
var url = "Set the URL here" ;
fileTransfer.upload(fileURL, encodeURI(url),(result)=>
{
console.log(result);
}, (error)=>{
console.log(error);
}, options);
})
Upload Files : using expo-image-picker npm module. Here we can upload any files or images etc. The files in a device can be accessed using the launchImageLibrary method. Then access the media on that device.
import * as ImagePicker from "expo-image-picker";
const loadFile = async () => {
let result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ImagePicker.MediaTypeOptions.All,
aspect: [4, 3],
});
return <Button title="Pick an image from camera roll" onPress={loadFile} />
}
The above code used to access the files on a device.
Also, use the camera to capture the image/video to upload by using
launchCameraAsync with mediaTypeOptions to videos or photos.