Send a POST request, receive a PDF in return and save it to the filesystem in NativeScript - nativescript-vue

I want to send a POST request containing JSON to a server for processing. The server will then return a PDF which I want to receive, save to the filesystem and display using nativescript-pdf-view. However, File.writeSync() will not accept the ArrayBuffer that I get from the server response. Instead, it expects the native variants NSData (iOS) and ByteArray (Android). How can I convert the ArrayBuffer to the native byte arrays?

Sending the POST request
As explained by the official documentation, a POST request can be sent using the getBinary() function:
import { getBinary } from 'tns-core-modules/http'
getBinary({
url: 'https://example.com/foo/bar/generate-pdf',
method: 'POST',
headers: { 'Content-Type': 'application/json' },
content: JSON.stringify(someObject),
})
Here, someObject is the object you want to convert to JSON and send to the server.
Receiving, converting and saving the file / PDF
The response will contain the PDF or any other file as an ArrayBuffer. As mentioned in the question, the File.writeSync() method expects NSData (iOS) and ByteArray (Android). So, we need to get a file handler, convert our file and save it to the filesystem.
import { getBinary } from 'tns-core-modules/http'
import { isAndroid } from 'tns-core-modules/platform'
import { File, knownFolders, path } from 'tns-core-modules/file-system'
getBinary({
url: 'https://example.com/foo/bar/generate-pdf',
method: 'POST',
headers: { 'Content-Type': 'application/json' },
content: JSON.stringify(someObject),
}).then((buffer) => {
// Get file handler
const documentsFolder = knownFolders.documents()
const destination = path.join(documentsFolder.path, 'result.pdf')
const file = File.fromPath(destination)
// Convert buffer to byte array
let byteArray
if (isAndroid) {
byteArray = Array.create('byte', buffer.byteLength)
for (let i = 0; i < buffer.length; i++) {
byteArray[i] = new java.lang.Byte(buffer[i])
}
} else {
byteArray = NSData.dataWithBytesLength(buffer, buffer.byteLength)
}
// Save file
file.writeSync(byteArray, (error) => {
console.log(error)
});
})
Why the File.writeSync() method doesn't convert this automatically is a miracle to me. Maybe someone else can explain!
Displaying the PDF
To display the PDF, you can use nativescript-pdf-view. Just use the filehandler to get the path file.path or the destination variable and pass it to the src property of the PDFView component. This might look like this:
<template>
<Page>
<ActionBar title="PDF" />
<GridLayout rows="*" columns="*">
<PDFView :src="path" row="0" col="0" />
</GridLayout>
</Page>
</template>
<script lang="ts">
export default {
name: 'PdfViewer',
props: {
path: {
type: String,
required: true,
},
},
}
</script>
Make sure to install the plugin, of course and introduce the element to Vue.js, first:
// main.ts
Vue.registerElement('PDFView', () => require('nativescript-pdf-view').PDFView)

Related

SvelteKit Pass Data From Server to Browser

I am trying to pass data from the server to the client to load my app faster and prevent multiple calls to the database.
Via Fetch
SvelteKit is made to do this via the fetch function. This is great if you have an endpoint that allows for custom fetch. But what if you don't?
Firebase is a perfect example of not having a custom fetch function.
Cookies
I would think I could use cookies, but when I set the cookie, it just prints 'undefined' and never gets set.
<script lang="ts" context="module">
import Cookies from 'js-cookie';
import { browser } from '$app/env';
import { getResources } from '../modules/resource';
export async function load() {
if (browser) {
// working code would use JSON.parse
const c = Cookies.get('r');
return {
props: {
resources: c
}
};
} else {
// server
const r = await getResources();
// working code would use JSON.stringify
Cookies.set('resources', r);
// no cookies were set?
console.log(Cookies.get());
return {
props: {
resources: r
}
};
}
}
</script>
So my code loads correctly, then dissapears when the browser load function is loaded...
Surely there is a functioning way to do this?
J
So it seems the official answer by Rich Harris is to use and a rest api endpoint AND fetch.
routes/something.ts
import { getFirebaseDoc } from "../modules/posts";
export async function get() {
return {
body: await getFirebaseDoc()
};
}
routes/content.svelte
export async function load({ fetch }) {
const res = await fetch('/resources');
if (res.ok) {
return {
props: { resources: await res.json() }
};
}
return {
status: res.status,
error: new Error()
};
}
This seems extraneous and problematic as I speak of here, but it also seems like the only way.
J
You need to use a handler that injects the cookie into the server response (because load functions do not expose the request or headers to the browser, they are just used for loading props I believe). Example here: https://github.com/sveltejs/kit/blob/59358960ff2c32d714c47957a2350f459b9ccba8/packages/kit/test/apps/basics/src/hooks.js#L42
https://kit.svelte.dev/docs/hooks#handle
export async function handle({ event, resolve }) {
event.locals.user = await getUserInformation(event.request.headers.get('cookie'));
const response = await resolve(event);
response.headers.set('x-custom-header', 'potato');
response.headers.append('set-cookie', 'name=SvelteKit; path=/; HttpOnly');
return response;
}
FYI: This functionality was only added 11 days ago in #sveltejs/kit#1.0.0-next.267: https://github.com/sveltejs/kit/pull/3631
No need to use fetch!
You can get the data however you like!
<script context="module">
import db from '$/firebaseConfig'
export async function load() {
const eventref = db.ref('cats/whiskers');
const snapshot = await eventref.once('value');
const res = snapshot.val();
return { props: { myData: res.data } } // return data under `props` key will be passed to component
}
</script>
<script>
export let myData //data gets injected into your component
</script>
<pre>{JSON.stringify(myData, null, 4)}</pre>
Here's a quick demo on how to fetch data using axios, same principle applies for firebase: https://stackblitz.com/edit/sveltejs-kit-template-default-bpr1uq?file=src/routes/index.svelte
If you want to only load data on the server you should use an "endpoint" (https://kit.svelte.dev/docs/routing#endpoints)
My solution might solve it especially for those who work with (e.g: laravel_session), actually in your case if you want to retain the cookie data when loading on each endpoint.
What you should gonna do is to create an interface to pass the event on every api() call
interface ApiParams {
method: string;
event: RequestEvent<Record<string, string>>;
resource?: string;
data?: Record<string, unknown>;
}
Now we need to modify the default sveltekit api(), provide the whole event.
// localhost:3000/users
export const get: RequestHandler = async (event) => {
const response = await api({method: 'get', resource: 'users', event});
// ...
});
Inside your api() function, set your event.locals but make sure to update your app.d.ts
// app.d.ts
declare namespace App {
interface Locals {
r: string;
}
//...
}
// api.ts
export async function api(params: ApiParams) {
// ...
params.event.locals.r = response.headers.get('r')
});
Lastly, update your hooks.ts
/** #type {import('#sveltejs/kit').Handle} */
export const handle: Handle = async ({ event, resolve }) => {
const cookies = cookie.parse(event.request.headers.get('cookie') || '');
const response = await resolve(event);
if (!cookies.whatevercookie && event.locals.r) {
response.headers.set(
'set-cookie',
cookie.serialize('whatevercookie', event.locals.r, {
path: '/',
httpOnly: true
})
);
}
return response;
});
Refer to my project:
hooks.ts
app.d.ts
_api.ts
index.ts

How to stream mp4 videos from Django 3 to Vue.js

I'm trying to load a mp4 video stream from Django to Vue.js. I followed the solution here and got a byte string via my axios API which I concatenated to data:video/mp4;base64,, and then binded it to the video tag's :src property, but the video is not displaying. Is this the correct way to stream videos from Django 3 to Vue.js? What am I doing wrong?
Api.js code snippet:
import axios from 'axios'
export default () => {
return axios.create({
baseURL: `http://127.0.0.1:8000/`,
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
}
})
}
Video.vue component code snippet:
methods: {
async stream() {
return await VideoService.stream(this.video)
.then(function(data) {
return data.data;
});
}
},
props: ['data'],
watch: {
data: {
deep: true,
immediate: true,
handler(curr, prev) {
this.video = curr;
this.stream().then(data => {
data = Buffer.from(data, 'binary').toString('base64');
this.video = 'data:video/mp4;base64,'+data;
});
}
}
}
As suggested in the comments of that answer, I also set src to the Django api endpoint e.g. src="/video/test.mp4, but I can see in my terminal that it's not reaching that Django route. How do I get the linked solution to work with Vue.js?
Picture of the raw string I get back from Kevin's Django view:
Another image when I convert to base 64 using Buffer:
The final solution needed to stream videos to Vue.js if you're using the same component for videos is to set the src tag using v-html so that you can set src dynamically. Directly doing src="http://localhost:8000/v/video.mp4" won't work once the component is created. So in addition to Kevin's code, just do the following in the video component:
<template>
<div>
<video v-html="src" autoplay="" controls="" loop="" muted="" frameborder="0">
Your browser does not support HTML videos.
</video>
</div>
</template>
<script>
export default {
data() {
return {
src: ''
}
},
props: ['data'],
watch: {
data: {
deep: true,
immediate: true,
handler(curr, prev) {
this.src = '<source src="http://localhost:8000/v/"'+curr+' type="video/mp4">'
}
}
}
}
</script>
<style scoped>
</style>

flutter post request to server with file

Dears, I am new at flutter, I want to send post request from flutter to server
and this is the postman request
Image-Post-Request
Post header:
key Value
Accept: application/json
Content-Type: application/x-www-form-urlencoded
Post Authentication:
Bear Token
Post Body:
key : Value
address : address
description: description
feedback: feedback
media: download.png
I want to make this request from flutter
This is my code:
File _img; // taken by camera
Map<String,String> headers = {
'Content-Type':'application/json',
'Authorization': 'Bearer $token',
};
final msg = jsonEncode({
"address" : _address,
"description": _description,
"feedback" : _techicalSupport,
"media" : _img;
});
try{
var response = await http.post(
"url",
headers: headers,
body: msg,
);
print("${response.toString()}");
}catch(e){
print("${e.toString()}");
}
I got this Error:
Unhandled Exception: Converting object to an encodable object failed: Instance of '_File'
Note: media is not required, when I removed it from the body it works and it create record in the database
I want to include media in the body.
How can I do it please...
This is the answer of my question, I used Dio library :
import 'package:dio/dio.dart';
File myImage;
List<File> _images = [];
// to handle image and make list of images
_handleImage()async{
File imageFile = await ImagePicker.pickImage(source: ImageSource.camera);
if(imageFile != null){
myImage = imageFile;
_images.add(imageFile);
}
}
// for post data with images
void _submit() async{
FormData formData = FormData();
_images.forEach((image) async{
formData.files.addAll(
[
MapEntry(
"media[]",
await dio.MultipartFile.fromFile(image.path),
),
]
);
});
formData.fields.add(MapEntry("address", _address),);
formData.fields.add(MapEntry("description", _description),);
formData.fields.add(MapEntry("feedback", _techicalSupport),);
var response = await new Dio().post(
postUrl,
options: Options(
headers: {
"Content-Type": "application/json",
"Authorization" : 'Bearer $token',
}
),
data: formData
);
print("${response.toString()}");
}
1.Configure FilePicker library (optional step, you may select your file using any library)
Then to pick the file
File file = await FilePicker.getFile();
2.Configure dio library , versions here
import 'package:dio/dio.dart';
then
FormData formData = FormData.fromMap({
"FIELD_NAME_WEBSERVICE_HERE": await MultipartFile.fromFile(imageFile.path,filename: "anyname_or_filename"),
"FIELD_NAME_WEBSERVICE_HERE":"sample value for another field"),
});
var response = await Dio().post("FULL_URL_HERE", data: formData);
print(response);
You need to convert _img to MultipartFile
MultipartFile file=MultipartFile.fromBytes(
'media', await filePath.readAsBytes(),
filename: 'FILE_NAME');
var request = http.MultipartRequest("POST", Uri.parse(url));
if (files != null) request.files.add(file);
You should use MultipartRequest to send file.
import 'dart:io';
import 'package:http/http.dart' as http;
import 'package:http_parser/http_parser.dart';
import 'package:image_picker/image_picker.dart';
import 'dart:convert';
void send() async {
final msg = {
"address": "test",
"description": "test",
"feedback": "test",
};
File _img;
var image = await ImagePicker.pickImage(source: ImageSource.camera);
_img = image;
var response = await http.MultipartRequest(
'POST', Uri.parse("url"))
..fields.addAll(msg)
..files.add(http.MultipartFile.fromBytes('image', _img.readAsBytesSync(),
contentType: MediaType('image', 'jpeg'), filename: 'test.jpg'));
print(response);
}

Django Angular File Upload - Upload works in Postman but does not with angular 7

 I have Django Backend that accepts File with other data as a request.
When I use File Upload API from Postman to submit File and other form data. Postman Request It works fine and Prints
<QueryDict: {u'csv': [<InMemoryUploadedFile: log_2018_10_09-
11_57_16_Summary_subject23_hrm.csv (text/csv)>], u'device_name':
[u'Zephyr']}>
and file successfully get stored.
But when I am trying to do it with Angular it logs empty object.
Below is my Angular Code.
// HTML
<input hidden type="file" id="csv" name="csv" accept=".csv"
(change)="onFileChange($event)" #fileInput>
// On Change Method
onFileChange(evt: any) {
console.log(this.fileNames.toString());
if (evt.target.files && evt.target.files[0]) {
console.log(evt.target.files[0]);
this.file = evt.target.files[0];
this.fileNames.push({ name: this.file.name });
this.processFile(this.file).then(data => this.newMethod(data));
this.showUploadButton = false;
this.showFileName = true;
}
console.log(this.fileNames[0].name);
}
processFile(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsText(file);
reader.onload = () => resolve(reader.result);
reader.onerror = error => reject(error);
});
}
private newMethod(data: any): void | PromiseLike<void> {
// this.uploadData.append('csv', data);
this.form.get('csv').setValue(this.file);
return data;
}
// On Submit Method
onSubmit() {
this.uploadData.append('device_name', this.form.get('device_name').value);
this.uploadData.append('csv', this.form.get('csv').value);
this.backendService.insertCSV(this.uploadData).subscribe(
response => {
console.log(response);
},
error => {
console.log('error', error);
}
);
}
// Call to Backend
HttpUploadOptions =
new HttpHeaders({
'content-type': 'multipart/form-data',
});
insertCSV(fileData): Observable<any> {
for (const iterator of Array.from(fileData.entries())) {
console.log(iterator);
}
// Prints Below on Browser console
// (2) ["csv", File(168761)]
// (2) ["device_name", "Zephyr"]
return this.http.post('http://127.0.0.1:8000/upload/', { data:
fileData, headers: this.HttpUploadOptions});
}
Gives an Error on Console in Browser
And Prints on Django
{u'headers': {u'normalizedNames': {}, u'lazyUpdate': None}, u'data': {}}
Please Help Me!
I'm suspecting that, following lines are not waiting for the callback on reader.onload to finish. Meaning file upload is not finished. Move them inside the reader.onload function.
// On Change Method
onFileChange(evt: any) {
if (evt.target.files && evt.target.files[0]) {
const file = evt.target.files[0];
const reader = new FileReader();
reader.onload = () => {
this.form.get('csv').setValue(file);
reader.readAsText(evt.target.files[0]);
this.uploadData.append('csv', this.form.get('csv').value);
};
}
}
I had a similar problem with React and Django. Turns out, when using a multipart/form-data with front-end, you shouldn't set the header:
// Call to Backend
HttpUploadOptions =
new HttpHeaders({
'content-type': 'multipart/form-data',
});
Not even content-type: '', completely remove it. You have to let the browser set it, since they also add the boundary to it.
More information about why not to set it:
What is boundary and why I had to delete the header?
Multipart form allow transfer of binary data, therefore server needs a
way to know where one field’s data ends and where the next one starts.
That’s where boundary comes in. It defines a delimiter between fields
we are sending in our request (similar to & for GET requests). You can
define it yourself, but it is much easier to let browser do it for
you.

React Native upload to S3 with presigned URL

Been trying with no luck to upload an image to S3 from React Native using pre-signed url. Here is my code:
generate pre-signed url in node:
const s3 = new aws.S3();
const s3Params = {
Bucket: bucket,
Key: fileName,
Expires: 60,
ContentType: 'image/jpeg',
ACL: 'public-read'
};
return s3.getSignedUrl('putObject', s3Params);
here is RN request to S3:
var file = {
uri: game.pictureToSubmitUri,
type: 'image/jpeg',
name: 'image.jpg',
};
const xhr = new XMLHttpRequest();
var body = new FormData();
body.append('file', file);
xhr.open('PUT', signedRequest);
xhr.onreadystatechange = () => {
if(xhr.readyState === 4){
if(xhr.status === 200){
alert('Posted!');
}
else{
alert('Could not upload file.');
}
}
};
xhr.send(body);
game.pictureToSubmitUri = assets-library://asset/asset.JPG?id=A282A2C5-31C8-489F-9652-7D3BD5A1FAA4&ext=JPG
signedRequest = https://my-bucket.s3-us-west-1.amazonaws.com/8bd2d4b9-3206-4bff-944d-e06f872d8be3?AWSAccessKeyId=AKIAIOLHQY4GAXN26FOQ&Content-Type=image%2Fjpeg&Expires=1465671117&Signature=bkQIp5lgzuYrt2vyl7rqpCXPcps%3D&x-amz-acl=public-read
Error message:
<Code>SignatureDoesNotMatch</Code>
<Message>
The request signature we calculated does not match the signature you provided. Check your key and signing method.
</Message>
I can successfully curl and image to S3 using the generated url, and I seem to be able to successfully post to requestb.in from RN (however I can only see the raw data on requestb.in so not 100% sure the image is properly there).
Based on all this, I've narrowed my issue down to 1) my image is not correctly uploading period, or 2) somehow the way S3 wants my request is different then how it is coming in.
Any help would be muuuuuucchhhh appreciated!
UPDATE
Can successfully post from RN to S3 if body is just text ({'data': 'foo'}). Perhaps AWS does not like mutliform data? How can I send as just a file in RN???
To upload pre-signed S3 URL on both iOS and Android use react-native-blob-util lib
Code snippet:
import RNBlobUtil from 'react-native-blob-util'
const preSignedURL = 'pre-signed url'
const pathToImage = '/path/to/image.jpg' // without file:// scheme at the beginning
const headers = {}
RNBlobUtil.fetch('PUT', preSignedURL, headers, RNBlobUtil.wrap(pathToImage))
Edited 19 Oct 2022 and swapped unsupported RN Fetch Blob for React Native Blob Util package.
FormData will create a multipart/form-data request. S3 PUT object needs its request body to be a file.
You just need to send your file in the request body without wrapping it into FormData:
function uploadFile(file, signedRequest, url) {
const xhr = new XMLHttpRequest();
xhr.open('PUT', signedRequest);
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
if(xhr.status === 200) {
alert(url);
} else {
alert('Could not upload file.');
}
}
};
xhr.send(file);
};
See https://devcenter.heroku.com/articles/s3-upload-node for example in a browser. Please also ensure your Content-Type header is matched with the signed URL request.
"rn-fetch-blob": 0.12.0,
"react-native": 0.61.5
This code works for both Android & iOS
const response = await RNFetchBlob.fetch(
'PUT',
presignedUrl,
{
'Content-Type': undefined
},
RNFetchBlob.wrap(file.path.replace('file://', '')),
)
Note {'Content-Type': undefined} is needed for iOS
sorry if none worked for any body. took me 5 days to get this to work . 5 crazy days of no result until my sleepy eyes turned green after little nap. Guess i had a sweet dream that brought the idea. so quickly say u have an end point on ur server to generate the sign url for the request from react native end or from react side or any web frontier. i would be doing this for both react native and react(can serve for html pages and angular pages).
WEB APPROACH
UPLOAD IMAGE TO S3 BUCKET PRESIGNED URI
/*
Function to carry out the actual PUT request to S3 using the signed request from the app.
*/
function uploadFile(file, signedRequest, url){
// document.getElementById('preview').src = url; // THE PREVIEW PORTION
// document.getElementById('avatar-url').value = url; //
const xhr = new XMLHttpRequest();
xhr.open('PUT', signedRequest);
xhr.onreadystatechange = () => {
if(xhr.readyState === 4){
if(xhr.status === 200){
document.getElementById('preview').src = url;
// document.getElementById('avatar-url').value = url;
}
else{
alert('Could not upload file.');
}
}
};
xhr.send(file);
}
/*
Function to get the temporary signed request from the app.
If request successful, continue to upload the file using this signed
request.
*/
function getSignedRequest(file){
const xhr = new XMLHttpRequest();
xhr.open('GET', 'http://localhost:1234'+`/sign-s3?file-name=${file.name}&file-type=${file.type}`);
xhr.setRequestHeader('Access-Control-Allow-Headers', '*');
xhr.setRequestHeader('Content-type', 'application/json');
xhr.setRequestHeader('Access-Control-Allow-Origin', '*');
xhr.onreadystatechange = () => {
if(xhr.readyState === 4){
if(xhr.status === 200){
const response = JSON.parse(xhr.responseText);
uploadFile(file, response.signedRequest, response.url);
}
else{
alert('Could not get signed URL.');
}
}
};
xhr.send();
}
/*
Function called when file input updated. If there is a file selected, then
start upload procedure by asking for a signed request from the app.
*/
function initUpload(){
const files = document.getElementById('file-input').files;
const file = files[0];
if(file == null){
return alert('No file selected.');
}
getSignedRequest(file);
}
/*
Bind listeners when the page loads.
*/
//check if user is actually on the profile page
//just ensure that the id profile page exist on your html
if (document.getElementById('profile-page')) {
document.addEventListener('DOMContentLoaded',() => {
///here is ur upload trigger bttn effect
document.getElementById('file-input').onchange = initUpload;
});
}
FOR REACT NATIVE I WILL NOT BE USING ANY 3RD PARTY LIBS.
i have my pick image function that picks the image and upload using xhr
const pickImage = async () => {
let result = await ImagePicker.launchImageLibraryAsync({
// mediaTypes: ImagePicker.MediaTypeOptions.All,
allowsEditing: true,
aspect: [4, 3],
quality: 1,
base64:true
});
console.log(result);
if (!result.cancelled) {
// setImage(result.uri);
let base64Img = `data:image/jpg;base64,${result.uri}`;
// ImagePicker saves the taken photo to disk and returns a local URI to it
let localUri = result.uri;
let filename = localUri.split('/').pop();
// Infer the type of the image
let match = /\.(\w+)$/.exec(filename);
let type = match ? `image/${match[1]}` : `image`;
// Upload the image using the fetch and FormData APIs
let formData = new FormData();
// Assume "photo" is the name of the form field the server expects
formData.append('file', { uri: base64Img, name: filename, type });
const xhr = new XMLHttpRequest();
xhr.open('GET', ENVIRONMENTS.CLIENT_API+`/sign-s3?file-name=${filename}&file-type=${type}`);
xhr.setRequestHeader('Access-Control-Allow-Headers', '*');
xhr.setRequestHeader('Content-type', 'application/json');
// xhr.setRequestHeader('Content-type', 'multipart/form-data');
xhr.setRequestHeader('Access-Control-Allow-Origin', '*');
xhr.setRequestHeader('X-Amz-ACL', 'public-read') //added
xhr.setRequestHeader('Content-Type', type) //added
xhr.onreadystatechange = () => {
if(xhr.readyState === 4){
if(xhr.status === 200){
const response = JSON.parse(xhr.responseText);
alert(JSON.stringify( response.signedRequest, response.url))
// uploadFile(file, response.signedRequest, response.url);
// this.setState({imagename:file.name})
const xhr2 = new XMLHttpRequest();
xhr2.open('PUT', response.signedRequest);
xhr2.setRequestHeader('Access-Control-Allow-Headers', '*');
xhr2.setRequestHeader('Content-type', 'application/json');
// xhr2.setRequestHeader('Content-type', 'multipart/form-data');
xhr2.setRequestHeader('Access-Control-Allow-Origin', '*');
// xhr2.setRequestHeader('X-Amz-ACL', 'public-read') //added
xhr2.setRequestHeader('Content-Type', type) //added
xhr2.onreadystatechange = () => {
if(xhr2.readyState === 4){
if(xhr2.status === 200){
alert("successful upload ")
}
else{
// alert('Could not upload file.');
var error = new Error(xhr.responseText)
error.code = xhr.status;
for (var key in response) error[key] = response[key]
alert(error)
}
}
};
xhr2.send( result.base64)
}
else{
alert('Could not get signed URL.');
}
}
};
xhr.send();
}
};
then some where in the render method
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Button title="Pick an image from camera roll" onPress={pickImage} />
{image && <Image source={{ uri: image }} style={{ width: 200, height: 200 }} />}
</View>
hope it helps any one who doesnt want sleepless nights like me.
import React from 'react'
import { Button, SafeAreaView } from 'react-native'
import { launchImageLibrary } from 'react-native-image-picker'
const Home = () => {
const getImageFromLibrary = async () => {
const result = await launchImageLibrary()
const { type, uri } = result.assets[0]
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)
})
// Send your blob off to the presigned url
const res = await axios.put(presignedUrl, blob)
}
return (
<SafeAreaView>
<Button onPress={getImageFromLibrary} title="Get from library" />
</SafeAreaView>
)
}
export default Home
Your BE that creates the pre-signed url can look something like this (pseudo code):
const { getSignedUrl } = require('#aws-sdk/s3-request-presigner')
const { S3Client, PutObjectCommand } = require('#aws-sdk/client-s3')
const BUCKET_NAME = process.env.BUCKET_NAME
const REGION = process.env.AWS_REGION
const s3Client = new S3Client({
region: REGION
})
const body = JSON.parse(request.body)
const { type } = body
const uniqueName = uuidv4()
const date = moment().format('MMDDYYYY')
const fileName = `${uniqueName}-${date}`
const params = {
Bucket: BUCKET_NAME,
Key: fileName,
ContentType: type
}
try {
const command = new PutObjectCommand(params)
const signedUrl = await getSignedUrl(s3Client, command, {
expiresIn: 60
})
response.send({ url: signedUrl, fileName })
} catch (err) {
console.log('ERROR putPresignedUrl : ', err)
response.send(err)
}
I am using aws-sdk v3 which is nice because the packages are smaller. I create a filename on the BE and send it to the FE. For the params, you don't need anything listed then those 3. Also, I never did anything with CORS and my bucket is completely private. Again, the BE code is pseudo code ish so you will need to edit a few spots.
Lastly, trying to use the native fetch doesn't work. It's not the same fetch you use in React. Use XHR request like I showed else you cannot create a blob.
First, install two libraries, then the image convert into base64 after that arrayBuffer, then upload it
import RNFS from 'react-native-fs';
import {decode} from 'base64-arraybuffer';
try {
RNFS.readFile(fileUri, 'base64').then(data => {
const arrayBuffer = decode(data);
axios
.put(sThreeApiUrl.signedUrl, arrayBuffer, {
headers: {
'Content-Type': 'image/jpeg',
'Content-Encoding': 'base64',
},
})
.then(res => {
if (res.status == 200) {
console.log('image is uploaded successfully');
}
});
});
} catch (error) {
console.log('this is error', error); }