Node + Express + PDF getting downloaded instead of opening - node.js

Although it may be a duplicate question but I have done everything I can without getting the solution.
I'm using Node + Express.
We use to send link of PDF file as CDN url in SMS
The link looks like this:
/api/v1/cdn/pdf/24
To get PDf and return, I have this code:
let parameter = await this.parameterDB.readRecord(where);
if (parameter) {
let fileName = parameter.id + '_' + parameter.patient_id + '.pdf';
// METHOD 1
let data = fs.readFileSync(constants.PATH.PDFS + fileName);
res.setHeader('Content-disposition', 'inline; filename="' + fileName + '"');
res.contentType('application/pdf');
res.send(data);
// METHOD 2
res.writeHead(200, {
'Content-Type': 'application/pdf',
'Content-Disposition': 'inline; filename=report_' + fileName,
'Content-Length': data.length
});
res.end(data, 'binary');
// METHOD 3
let file = fs.createReadStream(constants.PATH.PDFS + fileName);
var stat = fs.statSync(constants.PATH.PDFS + fileName);
res.setHeader('Content-Length', stat.size);
res.setHeader('Content-Type', 'application/pdf');
res.setHeader('Content-Disposition', 'inline; filename=' + fileName);
file.pipe(res);
} else {
return this.responseUtil.sendReadResponse(req, res, {
message: 'not found'
}, 200);
}
I all of the above 3 methods, when I click on the link in SMS, it downloads the PDF file instead of opening in web browser (which is annoying for the customers).
File is returned properly.
What can I do here?

I assume, by sending the API link in SMS means you want your customers to view the PDF in mobile browser.
Browsers can view the PDF file due to the plugins they support or have installed by default, but this is not the case with phone browsers, atleast
not all browsers on smartphones support PDF viewer, especially android. I can view PDF files in safari and chrome browser on iPhone though, because they have support for PDF view.
source: https://www.quora.com/Why-does-Chrome-on-Android-not-open-PDF-files-like-Chrome-on-Windows-Linux-can
Any one of the method in your code can show the PDF file on desktop browser with no modification, however when you try to call the API URI on phone browser,
you'll see the PDF file getting downloaded as there is no support for that in the browser, instead browser looks for the native application on phone,
downloads the file and opens in it.
Although there is a way/work-around for this if you want to see the PDF files in the browser using google docs.
You can simply embed your URL location of the PDF in the google docs link itself and redirect to it when customer hits your API URI.
source: How to display a PDF via Android web browser without "downloading" first
So you can modify your code something like this:
let parameter = await this.parameterDB.readRecord(where);
if (parameter) {
res.redirect("https://docs.google.com/gview?embedded=true&url=http://host...your-pdf-path.pdf")
} else {
return this.responseUtil.sendReadResponse(req, res, {
message: 'not found'
}, 200);
}
There is an another alternative for this, although it would require more efforts, but it is worth if you have lots of documents in your application that need to be seen on phone browser.
You can make use of PDF.js by Mozilla and create your own custom pdf viewer, this viewer is completely created in vanilla JavaScript and thus, it is supported by almost all browsers.
Once you have your own viewer implemented, all you have to do is :
res.sendfFile('show an html file eg index.html', { pdfURL: <path of the pdf file> });
and inside this index.html file you need to pass the path of the pdf file, make use of PDF.js functions to get the document data and create custom viewer.
You can use ejs to achieve this in Express JS.

Related

How to ask for permission to download files on NextJS?

I have the following endpoint:
baseurl.com/api/v1/datasheet/format
Accesing this url will download specific information based on the datasheet. In Express, for example, I return this method if the format = json:
const generateJSON = (res, data) => {
res.header('Content-Type', 'application/json');
res.attachment('example.json');
return res.send(data);
}
This works fine, but it downloads automatically the file as "example.json" without asking the user for his permission to download the file
My purpose is to have a button on my frontend (using NextJS) that will ask the user if he wants to download the file (the classic download or cancel prompt for downloads) and then open the file explorer, etc.
How can I do this? Is this something from the backend or frontend?
Use Content-disposition: attachment; filename=example.json to trigger a Save as DIalog box.
const generateJSON = (res, data) => {
res.header('Content-Type', 'application/json');
res.header('Content-disposition', 'attachment');
res.attachment('example.json');
return res.send(data);
}
The first parameter in the HTTP context is either inline (default value, indicating it can be displayed inside the Web page, or as the Web page) or attachment (indicating it should be downloaded; most browsers presenting a 'Save as' dialog, prefilled with the value of the filename parameters if present).
via https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition#:~:text=The%20first%20parameter,parameters%20if%20present).

Node buffer doesn't properly download file?

I'm successfully sending a get request that generates a pdf on the server, which I now want to send back to client and download it on their browser. The npm pdf generating library I'm using is called html-pdf and has the following options:
pdf.create(html).toFile([filepath, ]function(err, res){
console.log(res.filename);
});
pdf.create('<h1>Hi</h1>').toBuffer(function(err, buffer){
console.log('This is a buffer:', Buffer.isBuffer(buffer));
});
When I use the toFile option, the file gets correctly generated, however, when I use the toBuffer option and send that back to the user, the resulting pdf is blank.
I send the buffer to the user from my ajax handler like this:
module.exports = function(req, res) {
pdf.create(html).toBuffer(function(err, buffer){
res.setHeader('Content-Disposition', 'attachment; filename=panda.pdf');
res.setHeader('Content-Type', 'application/pdf');
res.send(buffer)
});
};
which gets received on the client here:
$.get('generatePdf', function(data, status) {
const url = window.URL.createObjectURL(new Blob([data]));
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', 'file.pdf');
document.body.appendChild(link);
link.click();
})
For some reason though the pdf that is downloaded is blank. Does anyone know what I might be doing wrong?
My downloaded file is corrupt according to this online pdf validator with the following errors:
Result Document does not conform to PDF/A. Details Validating file
"file (8).pdf" for conformance level pdf1.4
The 'xref' keyword was not found or the xref table is malformed. The
file trailer dictionary is missing or invalid. The "Length" key of the
stream object is wrong. Error in Flate stream: data error. The
document does not conform to the requested standard. The file format
(header, trailer, objects, xref, streams) is corrupted. The document
does not conform to the PDF 1.4 standard.

Microsoft Graph API unable to update Excel file

I have an Excel file (.xlsx), which when I have it already in my OneDrive, I can use a REST command like this, to modify it:
/v1.0/me/drive/root:/SpreadSheetName.xlsx:/workbook/worksheets/content_stats/tables('RawStats')/Rows
When I modify my code to first upload the file to OneDrive (rather than using the file that is already there), and I use the REST API, I get the error:
Open navigation properties are not supported on OpenTypes. Property name: 'tables'.
I have searched the web for this message, and cannot find anything related to what I am doing. The REST call for modifying the file which was just uploaded is nearly identical, although I do reference the file by ID instead, as that is what is returned by the upload API. This is the URL I use to modify the file which was just uploaded.
/v1.0/me/drive/items:/<RealExcelSpreadsheetID>:/workbook/worksheets/content_stats/tables('RawStats')/Rows
Both are doing a POST. Exact same file, only difference is it is being uploaded first, rather than already being in OneDrive. The file was definitely uploaded correctly, as when I go through the OneDrive web interface I do find it and can view it online. This is a business account.
It was uploaded as MIME type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.
It uses these scopes delegated via OAuth2:
User.Read
User.ReadWrite
Files.Read
Files.ReadWrite
Files.ReadWrite.All
Sites.ReadWrite.All
Using Node.js and JavaScript, although that should't matter.
Here is the code used to upload the file:
function copyTemplateInOneDrive(res, queryParam, officeAccessToken, callback) {
var fs = require('fs');
var excelExt = ".xlsx";
var excelSpreadsheetFilenameStart = "stats";
var uploadUrl = "https://graph.microsoft.com/v1.0/me/drive/root:/" +
excelSpreadsheetFilename + dateNowFull() + excelExt + ":/content";
var xlsxMimeType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
fs.readFile(excelSpreadsheetTemplateFilename, function read(error, fileContent) {
var request = require('request');
var options = {
url: uploadUrl,
headers: {
'Accept': 'application/json',
'Authorization': 'Bearer ' + officeAccessToken,
'Content-Type': xlsxMimeType,
},
body: fileContent
};
request.put(options, function (error, response, body) {
var result = JSON.parse(body);
if ('id' in result) {
console.log("Successfully uploaded template file to OneDrive");
res.write("Successfully uploaded template file to OneDrive");
var excelSpreadsheetID = result.id;
excelSpreadsheetUrl = result.webUrl;
} else {
console.log("ERROR: unable to upload template file to OneDrive " + body);
res.write("Error: unable to upload template file to OneDrive" + body);
return;
}
callback(null, res, queryParam, officeAccessToken, excelSpreadsheetID);
});
});
}
It uses the async module from node.js (which makes use of callback). It also saves the ID returned, and later passes it into the call to the Microsoft Graph.
Your issue here is the URL you're calling with the id is using syntax expecting the file path (i.e. folder\filename.ext) rather than the id. This is why switching to the file name started working for you.
There are two ways to address a file stored in OneDrive:
drive/items/{item-id}
/drive/root:/path/to/file (note the :)
You correctly switched your URI from drive/root to drive/items but by leaving the : in place you are telling OneDrive to address the file by it's path rather than it's id. In other words, it's looking for a file named "{some-id}".
For addressing a file by it's path, your URL is correct:
/drive/root:/{file-path}:/workbook/worksheets/content_stats/tables('RawStats')/Rows
For addressing a file by it's id however, you need to drop the ::
/drive/items/{file-id}/workbook/worksheets/content_stats/tables('RawStats')/Rows
You can read about how files are addressed in the documentation for DriveItem.

Generating zip archive on-the-fly using Express and node-archiver

I'm trying to generate a zip archive of icons on-the-fly and stream the response to the user to download, via a JSON POST request.
The zip itself is created and the response is returned, but the client-side is not prompted to download the file, and the response is garbled (which I assume is the contents of the archive).
app.post('/download', function(request, response) {
var icons = request.body;
var filename = 'icons.zip';
response.attachment(filename);
var zip = Archiver('zip');
zip.on('finish', function(error) {
return response.end();
});
zip.pipe(response);
for (var i = 0; i < icons.length; i++) {
var icon = getIcon(icons[i]);
zip.append(fs.createReadStream('public/' + icon.svg), { name: icon.title + '.svg' });
}
zip.finalize();
});
I'm wondering if there's anything missing from the server-side code that's preventing the download on the client-side, but from the example I've followed (https://github.com/archiverjs/node-archiver/blob/master/examples/express.js), it doesn't seem to be the case.
Here's some screenshots of the request made and the response received:
AJAX calls don't trigger file downloads in a browser, so you need to work around that.
One possibility is to change the request from a POST to a GET and put the names of the icons in the URL as parameters.
Your Express route would look like this:
app.get('/download/*?', function(request, response) {
// Make sure icons names were provided:
if (! request.params[0]) {
return response.sendStatus(400);
}
// Split on `/`, which is used as separator between icon names:
var icons = request.params[0].split(/\//);
// The rest can be the same
...
});
Client-side, you would use this:
location.href = 'http://your-server/download/Chevron%20Down/Close/Trash';
(obviously you can also generate that URL dynamically based on user input, as long as you make sure that the icon names are properly URL-encoded)

CasperJS and downloading a file via iFrame and JavaScript

I have a script to test that - on click - generates an iFrame which downloads a file. How can I intercept the response with CasperJS?
I already tried the sequence:
casper.click('element');
casper.withFrame('frame', function(){
console.log(this.getCurrentUrl()); // only return about:blank, but should have URL
console.log("content: " + this.getHTML()); // Yep, empty HMTL
this.on('resource.received', function(resource){
console.log(resource.url); // never executed
});
});
I need the content of the file but can not really produce the URL without clicking the element or changing the script I'm testing.
Ideas?
I tried other events, but none got fired when downloading via the iframe. I found another solution that works - but if you have something better, I'd like to try it.
Here it comes:
// Check downloaded File
.then(function(){
// Fetch URL via internals
var url = this.evaluate(function(){
return $('__saveReportFrame').src; // JavaScript function in the page
});
var file = fs.absolute('plaintext.txt');
this.download(url, file);
var fileString = fs.read(file);
// Whatever test you want to make
test.assert(fileString.match(/STRING/g) !== null, 'Downloaded File is good');
})

Resources