NodeJS sendFile return empty file from server - javascript

Server Side
I trying to send file from NodeJs
/**
* Exports data to PDF format route.
*/
app.post('/export/pdf', upload.single('imageBlob'), function (request, response) {
var PDF = require('./services/PdfService').PDF;
var fileUrl = PDF.generatePDFExport(request.body, request.file.buffer);
setTimeout(() => {
response.sendFile(fileUrl);
}, 200);
});
This piece of code creates a valid pdf file (I can open it browsers URL hit file)
But some browser hides the pop-up window and I wanted to download a file instead of opening it.
I check response in client and it is some BLOB looking response.
Client Side
I try to create a file from the response but there is only an empty pdf file.
return axios.post('http://172.18.0.2:8001/export/pdf', formData).then(response => {
let blob = new Blob([response.data]);
FileSaver.saveAs(blob, "st-seatmap-shop.pdf");
})
What is a mistake here? On the server side with a sending file or on the client with saving file?

The only problem was in sending a request to the server.
Server by default returns stream and for saving file on client response needs to be a BLOB so I just updated request.
let requestOptions = {
responseType: 'blob'
};
return axios.post('http://172.18.0.2:8001/export/pdf', formData, requestOptions).then(response => {
let blob = new Blob([response.data]);
FileSaver.saveAs(blob, "st-seatmap-shop.pdf");
}).catch(error => {
console.log("error.response is : ", error);
});

Related

Pinata IPFS's pinFileToIPFS method not accepting a user uploaded file

I am working on a project (using React.js Express.js and Node.js) to convert a user uploaded image into and NFT on Ethereum blockchain and for that, I need to upload the image on an IPFS (I am using Pinata) and then use the pinata URI in the metadata to mint a new NFT. (Do let me know if I am wrong here, I am still newbie to web3)
For uploading my image onto the Pinata IPFS, I am sending the base64 string of the image from the client side to the server side and then calling the pinFileToIPFS method. This is the code of my server side file
const axios = require('axios');
const fs = require('fs');
const FormData = require('form-data');
const router = require('express').Router();
const { Readable } = require('stream');
const pinFileToIPFS = (image) => {
const url = `https://api.pinata.cloud/pinning/pinJSONToIPFS`;
const buffer = Buffer.from(image);
const stream = Readable.from(buffer);
const filename = `an-awesome-nft_${Date.now()}.png`;
stream.path = filename;
const formData = new FormData();
formData.append("file", stream);
return axios.post(url,
formData,
{
headers: {
'Content-Type': `multipart/form-data; boundary= ${formData._boundary}`,
'pinata_api_key': "*******************",
'pinata_secret_api_key': "**********************************",
}
}
).then(function (response) {
console.log("Success: ", response);
}).catch(function (error) {
console.log("Fail! ", error.response.data);
});
};
router.route('/').post((req, res) => {
const image = req.body.image;
pinFileToIPFS(image);
});
module.exports = router;
Here req.body.image contains the base64 string of the user uploaded file.
I have tried to convert the base64 string into a buffer and then convert the buffer into a readable stream (as done in the official Pianata documentation but for a localy file) and then wrap it up in FormData(), but I keep getting the following error.
data: {
error: 'This API endpoint requires valid JSON, and a JSON content-type'
}
I know the problem is with the format my image/file is being sent to the API but I can't figure out. I am still a newbie to web3 and blockchains so please help!
The recommended way of interacting with Pinata, is by using their Node.JS SDK. This SDK has a pinFileToIPFS function, allows you to upload an image to their IPFS nodes in the form of a readableStream.
A sample of this would look like
const fs = require('fs');
const readableStreamForFile = fs.createReadStream('./yourfile.png');
const options = {
pinataMetadata: {
name: MyCustomName,
keyvalues: {
customKey: 'customValue',
customKey2: 'customValue2'
}
},
pinataOptions: {
cidVersion: 0
}
};
pinata.pinFileToIPFS(readableStreamForFile, options).then((result) => {
//handle results here
console.log(result);
}).catch((err) => {
//handle error here
console.log(err);
});
However, if you are deadset on using their API endpoints and simply posting to them via axios, there is a seperate API endpoint. /pinning/pinFileToIPFS. Examples of this method can be found in their API Docs.
You may want to consider changing the following two lines and using the https://api.pinata.cloud/pinning/pinFileToIPFS endpoint instead:
const buffer = Buffer.from(image); -> const buffer = Buffer.from(image, "base64");
and
formData.append("file", stream); -> formData.append("file", stream, "fileNameOfChoiche.png);
When you are uploading an image or file to Pinata IPFS with node js. These are the steps that even don't need Pinata Node.js SDK.
1- You can upload an image from the front end with React or Next.js. Code is given below.
const uploadAttachment = async (data, token) => {
try {
return await Api.post(`${ApiRoutes.upload_attachment}`, data, {
headers: {
Authorization: "Bearer " + token, //the token is a variable which holds the token
},
});
} catch (error) {
return {
status: 404,
};
}
};
export default uploadAttachment;
2- You need to install multer to upload an image.
const multer = require("multer");
global.uploadSingleFile = multer({ dest: "uploads/" });
3- Set up your route with multer middleware and action which you are going to call.
.post(
"/attachments/upload",
uploadSingleFile.single("file"),
actions.attachments.upload.pinFileToIPFSLocal
);
4- Last step where you will hit the Pinata endpoint with Pinata API & Secret key.
pinFileToIPFSLocal: async (req, res, next) => {
try {
const url = "https://api.pinata.cloud/pinning/pinFileToIPFS";
let formData = new FormData();
formData.append("file", JSON.stringify(req.file), req.file.originalname);
axios
.post(url, formData, {
maxContentLength: -1,
headers: {
"Content-Type": `multipart/form-data; boundary=${formData._boundary}`,
pinata_api_key: process.env.PINATA_API_KEY,
pinata_secret_api_key: process.env.PINATA_API_SECRET,
path: "somename",
},
})
.then((data) => {
console.log("Result...", data);
return utils.response.response(
res,
"Upload image to ipfs.",
true,
200,
data.data
);
})
.catch((err) => {
return utils.response.response(
res,
"Image not upload to ipfs",
false,
200,
err
);
});
} catch (error) {
next(error);
}
The error message is clear. You are using url that used for json file upload. this is the url you should use to upload image
const url = `https://api.pinata.cloud/pinning/pinFileToIPFS`;
you don't have to convert buffer to a readable stream.
I am not sure if this is correct ${formData._boundary}. should be
"Content-Type": `multipart/form-data: boundary=${formData.getBoundary()}`,
There must be an error on the image parameter. A simple buffer representation of the image should work. The readable stream is not necessary.
Instead of creating the buffer, you could use middleware like express-fileupload to access the buffer representation of the file uploaded on the client-side directly.
const file = req.files;
const url = "https://api.pinata.cloud/pinning/pinFileToIPFS";
const data = new FormData();
data.append("file", file.file.data, { filepath: "anyname" });
const result = await axios.post(url, data, {
maxContentLength: -1,
headers: {
"Content-Type": `multipart/form-data; boundary=${data._boundary}`,
pinata_api_key: process.env.PINATA_API_KEY,
pinata_secret_api_key: process.env.PINATA_API_SECRET,
path: "somename",
},
});

Node.js: create a txt file without writing to the device

I have a problem. In my project I get a text and send this text to remote API in .txt file. Now the program does this: getting a text, saving a text in a .txt file in filesystem, uploading a .txt file to remote API. Unfortunately, remote API accepts only files, I can't send plain text in request.
//get the wallPost with the field text
fs.writeFileSync(`./tmp/${wallPostId}.txt`, wallPost.text)
remoteAPI.uploadFileFromStorage(
`${wallPostPath}/${wallPostId}.txt`,
`./tmp/${wallPostId}.txt`
)
UPD: In function uploadFileFromStorage, I made a PUT request to remote api with writing a file. Remote API is API of cloud storage which can save only files.
const uploadFileFromStorage = (path, filePath) =>{
let pathEncoded = encodeURIComponent(path)
const requestUrl = `https://cloud-api.yandex.net/v1/disk/resources/upload?&path=%2F${pathEncoded}`
const options = {
headers: headers
}
axios.get(requestUrl, options)
.then((response) => {
const uploadUrl = response.data.href
const headersUpload = {
'Content-Type': 'text/plain',
'Accept': 'application/json',
'Authorization': `${auth_type} ${access_token}`
}
const uploadOptions = {
headers: headersUpload
}
axios.put(
uploadUrl,
fs.createReadStream(filePath),
uploadOptions
).then(response =>
console.log('uploadingFile: data '+response.status+" "+response.statusText)
).catch((error) =>
console.log('error uploadFileFromStorage '+ +error.status+" "+error.statusText)
)
})
But i guess in the future such a process will be slow. I want to create and upload a .txt file in RAM memory (without writing on drive). Thanks for your time.
You're using the Yandex Disk API, which expects files because that's what it's for: it explicitly stores files on a remote disk.
So, if you look at that code, the part that supplies the file content is supplied via fs.createReadStream(filePath), which is a Stream. The function doesn't care what builds that stream, it just cares that it is a stream, so build your own from your in-memory data:
const { Readable } = require("stream");
...
const streamContent = [wallPost.text];
const pretendFileStream = Readable.from(streamContent);
...
axios.put(
uploadUrl,
pretendFileStream,
uploadOptions
).then(response =>
console.log('uploadingFile: data '+response.status+" "+response.statusText)
)
Although I don't see anything in your code that tells the Yandex Disk API what the filename is supposed to be, but I'm sure that's just because you edited the post for brevity.

Unable to get video file for S3 upload. (using Expo Camera)

I have been stuck with trying to upload a video to S3 for a while and was hoping to get some pointers. Currently, what I've read and was told is that we need to send an actual file to S3 and not the url (which we might do if we were sending it to the backend before aws).
I am trying to do this by
const getBlob = async (fileURi) => {
console.log('THIS IS IT', fileURi);
const resp = await fetch(fileURi);
const videoBody = await resp.blob();
console.log(videoBody);
};
getBlob(video.uri);
The problem I am having is I am unable to actually get the video file. When I stop recording a video with await camera.stopRecording(); what I get in return is
Object {
"uri": "file:///path/20DD0E08-11CA-423D-B83D-BD5ED40DFB25.mov",
}
Is there a recommended approach in order to successfully get the actual file in order to send it to S3 through the client?
The way I am trying to currently send the video which doesn't work is:
const formData = new FormData();
formData.append('file', video.uri);
await fetch(url, {
method: 'POST',
body: formData,
headers: {
'Content-Type': 'multipart/form-data'
}
url: refers to the presignedUrl we get in return from aws.
P.S - Sending to the server through a fetch call does work but I noticed this approach also leave the User waiting for 10+ seconds since I need to send the video to the server then wait for it to finish uploading in AWS.
Thank you for all the help.
If I understand correctly you know how to upload file to your own server, but you want to send it directly to S3.
In that case I would suggest to use presigned URLs. https://docs.aws.amazon.com/AmazonS3/latest/dev/PresignedUrlUploadObject.html
You can generate presigned URL on your backend, it is basically regular URL pointing to S3 file and some key values. You need to send those values to mobile app and do the same fetch call you are already using, but replace url with the one generated on backend and add all key-values to FormData.
Example for node backend would look like this
import AWS from 'aws-sdk';
...
const client = new AWS.S3(config);
...
const presignedUrl = client.createPresignedPost({
Bucket: 'example-bucket-name',
Fields: { key: 'example-file-name' },
});
and in mobile app you would
const form = new FormData();
Object.keys(presignedUrl.fields).forEach(key => {
form.append(key, presignedUrl.fields[key]);
})
form.append('file', fileToUpload);
await fetch(presignedUrl.url, {
method: 'POST',
body: form,
headers: {
'Content-Type': 'multipart/form-data'
}
})
My solutions. Please review the sample application. https://github.com/expo/examples/tree/master/with-aws-storage-upload
const response = await fetch(video.uri);
const blob = await response.blob();
const params = {
Bucket: myBucket,
Metadata: {
long: long.toString(),
lat: lat.toString(),
size: videoSize.toString()
},
Key: myKey,
Body: blob
};

Download file with filesaver.js with angularJS from web api response c#

Im using the filesaver js library for download files... i'm generating a endpoint with web api that returns my file from base64 but i don't know how to return base64 as Blob for download with filesaver....
Im tried make differents responses types
try to understand how to does it works the Blob data
public HttpResponseMessage GetFile(int id)
{
string mybase64file = GetBase64File(id)
byte[] bytes = Convert.FromBase64String(mybase64file );
MemoryStream ms = new MemoryStream(bytes);
HttpResponseMessage result = new HttpResponseMessage(HttpStatusCode.OK);
result.Content = new StreamContent(ms);
result.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment");
result.Content.Headers.ContentDisposition.FileName = "someFileName.pdf";
result.Content.Headers.ContentType = new MediaTypeHeaderValue("application/pdf");
return result;
}
AngularJS service:
function download() {
var deferred = $q.defer()
$http.get('url', {
headers: {
'Content-type': 'application/octet-stream',
},
responseType: 'blob'
})
.success(function (response) {
saveAs(response, 'someName.pdf')
})
.error(function (err) {
deferred.reject(err)
})
return deferred.promise
}
When the angularjs service recieve to response, im call the "saveAs" function, but this generate the next error:
"angular.min.js:118 TypeError: Failed to execute 'createObjectURL' on 'URL': No function was found that matched the signature provided."
To return a file from ASP MVC CORE, use:
return File(stream, "application/octet-stream");
return FileStreamResult(stream, "application/octet-stream");
return PhysicalFileResult("path to file", "application/octet-stream");
With that, you dont even need filesaver js library, the file will download automatically once that link is requested.
One more thing: You cant download a file with Ajax (....$http.get('url', {....)!
You could use Fetch API do get the blob, see implementation here: https://stackoverflow.com/a/9970672/3563013
To address the specific error ( "angular.min.js:118 TypeError: Failed to execute 'createObjectURL' on 'URL': No function was found that matched the signature provided." ), its possible window.URL.createObjectURL is not supported in the browser you are using to test.

Response(xls file) of a POST Rest is corrupted using fileSaver.js 's saveAs method(Javascript)

Below is the detail of JAVA rest service which downloads a file from server:
Method prototype:
#POST
#Path("/prop/export")
#Produces(MediaType.APPLICATION_OCTET_STREAM)
#Consumes(MediaType.APPLICATION_JSON)
public Response exportItemsToFile(Map<String, String> params);
The Response is build from byte array in its implementation:
Response.ok(someByteArray)
I am using FileSaver.saveAs to download the response in a xls file using below code
var requestUri = '/wtk/rc/v1/pfm/prop/export';
var payload={"context":addCtx,"language":addLang,"country":addCountry,"swimlane":addSL};
$http.post(requestUri, payload, {
headers: {
'Content-Type': 'application/json'
},
responseType: 'arraybuffer',
}).success(function (data) {
var blob = new Blob([data],{type : 'application/vnd.ms-excel'});
var fileName = addLang+"_"+addCountry+".xls";
filesaver.saveAs(blob,fileName);
}).error(function () {
//download failed
});
The response which is xls file is corrupt using the above code. **
It is because the promise returned by $http.post have blank response
in case of if rest client produces octet-stream
**
But if I use some rest client like Postman, and select "Send and Download" option and save the response as xls. It is coming up fine.
Any help in this will be appreciated.

Categories