Reputation: 727
We are developing an app with Recurly integration and trying to use it PDF Invoice function.
The app is based on Node.js (Meteor platform).
It receives correct answer from Recurly with binary file:
But I can't correctly save it. I've tried two ways: print it on the client side in browser:
var file = window.URL.createObjectURL(new Blob([r.content], {type: "application/pdf"}));
var a = document.createElement("a");
a.href = file;
a.download = "invoicePDF";
document.body.appendChild(a);
a.click();
window.onfocus = function () {
document.body.removeChild(a)
}
And save it directly on server (just for test):
var fs = require('fs');
var wstream = fs.createWriteStream('C:/recurly.pdf');
wstream.write(result.content);
wstream.end();
But in both cases I've ended up with non-working pdf file. Acrobat, Foxit reader and Chrome cann't open this file — it's damaged.
Do you have any suggestions where I'm wrong? Maybe I need some content convertation before saving it or anything else?
Added
The result of this request I've sent to client and printed in console (image above).
try {
result = HTTP.call(
'GET',
'https://' + Meteor.settings.recurly.SUBDOMAIN + '.recurly.com/v2/invoices/' + invoiceId,
{
headers: {
Authorization: "Basic " + (new Buffer(Meteor.settings.recurly.API_KEY)).toString('base64'),
Accept: 'application/pdf'
}
}
);
} catch (err) {
result = e;
}
Upvotes: 1
Views: 873
Reputation: 16478
The problem is you are trying to obtain a binary file with a request that expects an encoded string as a response.
Your best option is to tell the request library what it should expect, otherwise you will have to manually pry the binary data out of the UTF-16
- or UTF-8
-encoded string.
There is a difference between the client and server implementations.
The server implementation uses node's request
module. You can supply options for it using npmRequestOptions
.
As stated in its documentation:
encoding - Encoding to be used on setEncoding of response data. If null, the body is returned as a Buffer. Anything else (including the default value of undefined) will be passed as the encoding parameter to toString() (meaning this is effectively utf8 by default). (Note: if you expect binary data, you should set encoding: null.)
So, on the server you can do something like:
try {
result = HTTP.call(
'GET',
'https://' + Meteor.settings.recurly.SUBDOMAIN + '.recurly.com/v2/invoices/' + invoiceId,
{
headers: {
Authorization: "Basic " + (new Buffer(Meteor.settings.recurly.API_KEY)).toString('base64'),
Accept: 'application/pdf'
},
npmRequestOptions: {
encoding: null // will cause the result to be stored in a binary Buffer
}
}
);
// will write the file in binary mode
fs.writeFile(outFileName, res.content, 'binary');
} catch (err) {
result = e;
}
The client implementation uses XHR.
In order to handle binary response, you need to change the XHR's responseType
to (preferably) 'blob'
.
Unfortunately, I see no way getting a binary blob in Meteor's current implementation of the HTTP package, as it expect the response to have a responseText
.
You can directly use an XMLHttpRequest
object, but you may need add some wrapper code to support dying browsers (IE6, I'm looking at you! - the usual new ActiveXObject('Microsoft.XMLHttp');
dance).
This could be achieved using something like the following code:
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.setRequestHeader('Authorization', ...);
xhr.responseType = 'blob'; // this is key
xhr.onload = function(e) {
if (this.status == 200) {
// this.response is a Blob. If you are sure that it is of the
// correct content-type, you can use it to construct the URL directly
let blob = new Blob([this.response], {type: 'application/pdf'});
let url = URL.createObjectURL(blob);
let a = document.createElement("a");
a.href = url;
a.download = "invoice.pdf";
document.body.appendChild(a);
...
}
};
xhr.send();
Which sends an XHR that encodes the response into a binary Blob and generates an ObjectURL with the correct data.
Upvotes: 2