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

 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.

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

string data can't be updated without changing the image in vue js

I have a problem with updating data using vue js as the frontend and django as the backend
when updating the data with the data image is changed successfully. but when updating data without an image with an error.
i've fire test using postman and managed to update data without changing image. please find a solution
this is the method for updating the data
ubah() {
let datapengajarstaf = new FormData();
datapengajarstaf.append("nama", this.datapengajarstaf.nama);
datapengajarstaf.append("nip", this.datapengajarstaf.nip);
datapengajarstaf.append("jobs", this.datapengajarstaf.jobs);
datapengajarstaf.append("picture", this.gambar);
_.each(this.datapengajarstaf, (value, key) => {
datapengajarstaf.append(key, value);
});
axios
.put("http://127.0.0.1:8000/api/Detailpengajarstaff/"+this.id, datapengajarstaf,
{
headers: {
"Content-Type":"multipart/form-data"
}
}
)
.then(response => {
this.$router.push("/indexpengajarstaff");
this.$q.notify({
type: "positive",
message: `Data berhasil ditambah.`
});
})
.catch(err => {
if (err.response.status === 422) {
this.errors = [];
_.each(err.response.data.errors, error => {
_.each(error, e => {
this.errors.push(e);
});
});
}
});
}
this is for form-data when the data is updated

testing multiple http request using mocha

I've been trying to solve this issue for days;
create the test for this case using mocha:
app.post('/approval', function(req, response){
request.post('https://git.ecommchannel.com/api/v4/users/' + req.body.content.id + '/' + req.body.content.state + '?private_token=blabla', function (error, resp, body) {
if (resp.statusCode == 201) {
//do something
} else {
response.send("failed"), response.end();
}
});
} else {
response.send("failed"), response.end();
}
});
});
I've tried several ways, using supertest to test the '/approval' and using nock to test the post request to git api. But it always turn "statusCode" is undefined. I think that's because the request to git api in index.js is not inside a certain function(?)
So I can't implement something like this :
https://codeburst.io/testing-mocking-http-requests-with-nock-480e3f164851 or
https://scotch.io/tutorials/nodejs-tests-mocking-http-requests
const nockingGit = () => {
nock('https://git.ecommchannel.com/api/v4/users')
.post('/1/yes', 'private_token=blabla')
.reply(201, { "statusCode": 201 });
};
it('approval', (done) => {
let req = {
content: {
id: 1,
state: 'yes'
},
_id: 1
}
request(_import.app)
.post('/approval')
.send(req)
.expect(200)
.expect('Content-Type', /html/)
.end(function (err, res) {
if (!err) {
nockingGit();
} else {
done(err);
}
});
done();
})
Then I tried to use supertest as promise
it('approve-block-using-promise', () => {
return promise(_import.app)
.post('/approval')
.send(req = {
content: {
id: 1,
state: 'yes'
},
_id: 1
})
.expect(200)
.then(function(res){
return promise(_import.app)
.post("https://git.ecommchannel.com/api/v4/users/")
.send('1/yes', 'private_token=blabla')
.expect(201);
})
})
But it gives error: ECONNEREFUSED: Connection refused. I didn't find any solution to solve the error. Some sources said that it needs done() .. but it gives another error message, 'ensure "done()" is called" >.<
So then I've found another way, using async (https://code-examples.net/en/q/141ce32)
it('should respond to only certain methods', function(done) {
async.series([
function(cb) { request(_import.app).post('/approval')
.send(req = {
content: {
id: 1,
state: 'yes'
},
_id: 1
})
.expect(200, cb); },
function(cb) { request(_import.app).post('/https://git.ecommchannel.com/api/v4/users/').send('1/yes', 'private_token=blabla').expect(201, cb); },
], done);
});
and it gives this error : expected 201 "Created", got 404 "Not Found". Well, if I open https://git.ecommchannel.com/api/v4/users/1/yes?private_token=blabla in the browser it does return 404. But what I expect is I've injected the response to 201 from the unit test; so whatever the actual response is, the statusCode suppose to be 201, right?
But then since it gives that error, is it means the unit test really send the request to the api?
Pls help me to solve this; how to test the first code I shared.
I really new into unit test.
There are a few things wrong with your posted code, I'll try to list them out but I'm also including a full, passing example below.
First off, your call to git.ecommchannel in the controller, it's a POST with no body. While this isn't causing the errors you're seeing and is technically not incorrect, it is odd. So you should double check what the data you should be sending is.
Next, I'm assuming this was a copy/paste issue when you created the question, but the callback for the request in your controller is not valid JS. The brackets don't match up and the send "failed" is there twice.
Your Nock setup had two issues. First the argument to nock should only have origin, none of the path. So /api/v4/users had to be moved into the first argument of the post method. The other issue was with the second argument passed to post that is an optional match of the POST body. As stated above, you aren't currently sending a body so Nock will always fail to match and replace that request. In the example below, the private_token has been moved to match against the query string of the request, as that what was shown as happening.
The calling of nockingGit was happening too late. Nock needs to register the mock before you use Supertest to call your Express app. You have it being called in the end method, by that time it's too late.
The test labeled approve-block-using-promise has an issue with the second call to the app. It's calling post via Supertest on the Express app, however, the first argument to that post method is the path of the request you're making to your app. It has nothing to do with the call to git.ecommchannel. So in that case your Express app should have returned a 404 Not Found.
const express = require('express')
const nock = require('nock')
const request = require('request')
const supertest = require('supertest')
const app = express()
app.use(express.json())
app.post('/approval', function(req, response) {
const url = 'https://git.ecommchannel.com/api/v4/users/' + req.body.content.id + '/' + req.body.content.state
request.post({
url,
qs: {private_token: 'blabla'}
// body: {} // no body?
},
function(error, resp, body) {
if (error) {
response.status(500).json({message: error.message})
} else if (resp.statusCode === 201) {
response.status(200).send("OK")
} else {
response.status(500).send("failed").end();
}
});
});
const nockingGit = () => {
nock('https://git.ecommchannel.com')
.post('/api/v4/users/1/yes')
.query({private_token: 'blabla'})
.reply(201, {"data": "hello world"});
};
it('approval', (done) => {
const reqPayload = {
content: {
id: 1,
state: 'yes'
},
_id: 1
}
nockingGit();
supertest(app)
.post('/approval')
.send(reqPayload)
.expect(200)
.expect('Content-Type', /html/)
.end(function(err) {
done(err);
})
})

How to replace the authorize method in ember-simple-auth

I'm trying to refactor my Ember acceptance tests to not use the deprecated authorize method, as it is throwing a warning:
The `authorize` method should be overridden in your application adapter
I checked the docs, and numberous other sources, but they don't actually explain how to migrate my code. Here's what I've got at the moment:
// projectname/app/pods/login/controller.js (excerpt)
export default Controller.extend({
session: service(),
sessionToken: null,
onSuccess: function(res) {
res = res.response;
this.set('sessionToken', res.session);
if (res.state === "authenticated") {
document.cookie = "token="+res.session+";path=/;";
var authOptions = {
success: true,
data : {
session : res.session,
}
};
this.get('session').authenticate("authenticator:company", authOptions);
}
}
});
And this must be the part that I'm meant to get rid of:
// project/app/adapters/application.js (excerpt)
export default DS.RESTAdapter.extend(DataAdapterMixin, {
authorize(xhr) { // This is deprecated! I should remove it
let sessionToken = this.get('session.data.authenticated.session');
if (sessionToken && !isEmpty(sessionToken)) {
xhr.setRequestHeader('Authorization', "Token " + sessionToken);
}
},
});
And here is my test:
import { test, module } from 'qunit';
import { visit, currentURL, find, click, fillIn } from '#ember/test-helpers';
import { setupApplicationTest } from 'ember-qunit';
import { authenticateSession} from 'ember-simple-auth/test-support';
module('moduleName', function(hooks) {
setupApplicationTest(hooks);
test('moduleName', async function(assert) {
// await authenticateSession(this.application); // Never works
// await authenticateSession(); // Never works
await authenticateSession({
authenticator: "authenticator:company"
}); // Works slightly more?
await visit('/my/other/page');
await assert.equal(currentURL(), '/my/other/page');
});
});
REMOVING the authorize method and attempting either of the commented out methods yields:
Error: Assertion Failed: The `authorize` method should be overridden in your application adapter. It should accept a single argument, the request object.
If I use the authenticator block as an arg, then regardless of the presence of the authorize method, I simply get:
actual: >
/login
expected: >
/my/other/page
Which, I assume, is because it did not login.
Leaving the authorize method there, and trying the commented methods yields:
Error: Browser timeout exceeded: 10s
Per the docs you linked above: To replace authorizers in an application, simply get the session data from the session service and inject it where needed.
Since you need the session data in your Authorization header, a possible solution for your use case may look like this:
export default DS.RESTAdapter.extend(DataAdapterMixin, {
headers: computed('session.data.authenticated.session', function() {
const headers = {};
let sessionToken = this.get('session.data.authenticated.session');
if (sessionToken && !isEmpty(sessionToken)) {
headers['Authorization'] = "Token " + sessionToken;
}
return headers;
})
});
This should allow you to dynamically set the Authorization header, without doing so via the authorize method.
Ember Simple Auth, has an excellent community and quickly created a guide on how to upgrade to v3.
The latest version fixes this problem completely - If anyone is having this problem, upgrading to 2.1.1 should allow you to use the new format in your application.js:
headers: computed('session.data.authenticated.session', function() {
let headers = {};
let sessionToken = this.get('session.data.authenticated.session');
if (sessionToken && !isEmpty(sessionToken)) {
headers['Authorization'] = "Token " + sessionToken;
}
return headers;
}),
This problem was only present in 2.1.0.

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); }