How to display a PDF generated with express/pdfkit in browser from api-endpoint - express

I generate a pdf with with express and pdfkit like:
router.get("/create_pdf", (req, res) => {
var foo = req.body.foo;
var bar = req.body.bar;
// query Postgres with foo and bar to get some custom values
//create pdf
const doc = new PDFDocument();
doc.pipe(res);
doc.fontSize(25).text("Just a header", 100, 80);
// build some more stuff depending of Postgres query
doc.end();
})
My frontend using vue receives the response
print: function() {
data = {foo: "foo", bar: "bar"};
this.axios({
method: "get",
url: "get_recipe_as_pdf",
data: data,
})
.then((response) => {
console.log(response);
window.open(response.data);
})
.catch(() => {
//some error
});
},
The console shows data: "%PDF-1.3↵%����↵7 0 obj↵<...
window.open(...) opens a blank tab.
I found similar questions but could not find an answer. I probably have some understanding problems, so I'd appreciate a hint. Thanks.

In your express app: res.setHeader('Content-Type', 'application/pdf');
In your frontend just open the link "manually" like clicking on an anchor: click me
The pdf should open in a new tab if your browser supports that, else it will be downloaded.
Edit: for clarification: window.open expects a url as first parameter, not the content of a pdf.

Related

Cypress: login through magic link error with cy.origin()

Devs at my startup have switched login to a magic link system, in which you get inside after clicking a link on the email body.
I have set up a Mailsac email to receive mails containing magic links but I haven't been able to actually follow those links because of the following:
cy.request({
method: "GET",
url: "https://mailsac.com/api/addresses/xxxx#mailsac.com/messages",
headers: {
"Mailsac-Key": "here-goes-the-key",
},
}).then((res) => {
const magicLink = res.body[0].links[0];
cy.origin(magicLink, () => {
cy.visit('/')
});
});
I wasn't able to use cy.visit() either because the magic link URL is slightly different from the baseURL in this testing environment.
So my question is:
How could I follow this cumbersome link to find myself logged in home, or else, is there another way to deal with magic links?
Thanks
The docs say
A URL specifying the secondary origin in which the callback is to be executed. This should at the very least contain a hostname, and may also include the protocol, port number & path. Query params are not supported.
Not sure if this means the cy.visit() argument should not have query params, of just the cy.origin() parameter.
Try passing in the link
cy.request({
...
}).then((res) => {
const magicLink = res.body[0].links[0];
const magicOrigin = new URL(magicLink).origin
cy.origin(magicOrigin, { args: { magicLink } }, ({ magicLink }) => {
cy.visit(magicLink)
});
});
If that doesn't fix it, you could try using cy.request() but you'll have to observe where the token is stored after using the magicLink.
cy.request({
...
}).then((res) => {
const magicLink = res.body[0].links[0];
cy.request(magicLink).then(response =>
const token = response??? // find out where the auth token ends up
cy.setCookie(name, value) // for example
});
});
You need to pass the domain as the first parameter to origin, and do the visit within the callback function, something like this:
const magicLinkDomain = new Url(magicLink).hostname
cy.origin(magicLinkDomain, {args: magicLink}, ({ magicLink }) => {
cy.visit(magicLink);
//...
})
Reference: https://docs.cypress.io/api/commands/origin#Usage

Is there a way to generate pdf with remix.run

Hosted remix app with supabase as db on netlify. Is there a way to generate pdf document using remix ?
Remix has a feature called Resource Routes which let you create endpoints returning anything.
Using them, you could return a Response with a PDF, how to generate the PDF will depend on what libraries you are using, if you use something like React PDF you could do something like this:
// routes/pdf.tsx
import { renderToStream } from "#react-pdf/renderer";
// this is your PDF document component created with React PDF
import { PDFDocument } from "~/components/pdf";
import type { LoaderFunction } from "remix";
export let loader: LoaderFunction = async ({ request, params }) => {
// you can get any data you need to generate the PDF inside the loader
// however you want, e.g. fetch an API or query a DB or read the FS
let data = await getDataForThePDFSomehow({ request, params });
// render the PDF as a stream so you do it async
let stream = await renderToStream(<PDFDocument {...data} />);
// and transform it to a Buffer to send in the Response
let body: Buffer = await new Promise((resolve, reject) => {
let buffers: Uint8Array[] = [];
stream.on("data", (data) => {
buffers.push(data);
});
stream.on("end", () => {
resolve(Buffer.concat(buffers));
});
stream.on("error", reject);
});
// finally create the Response with the correct Content-Type header for
// a PDF
let headers = new Headers({ "Content-Type": "application/pdf" });
return new Response(body, { status: 200, headers });
}
Now when the user goes to /pdf it will get the PDF file back, you could also use an iframe to show it on the HTML.
If you don't use React PDF, change the render part to use the library you are using, and keep the headers and the Response creation part.

How do we display images from s3 on browser using vue/nuxt?

I am displaying profile picture of a user on a vue/nuxt app. This picture is uploaded to S3 through a file API.
While receiving the image from file API and displaying the profile pic, I am not able to convert it to the right format.
My problems:
s3 says the file content type is application/octet-stream. I was expecting a specific type like image/jpeg or image/png. There's an npm that came to rescue here.
Wrapping the returned file in a blob using createObjectURL creates a dummy URL / link. We can set the mime type to blob while creating like so const blob = new Blob([response.data],{type=response.type}
Using responseType - Is it blob, arrayBuffer or stream? I went ahead with experimenting on blob
Step 1: File API - Reading from S3 using AWS client v3 (File: FileService.js)
let goc = new GetObjectCommand({
...
});
s3client.send(goc)
.then(response => {
if(response){
const body = response.Body;
const tempFileName = path.join(config.FILE_DOWNLOAD_PATH, file_name);
const tempFile = fs.createWriteStream(tempFileName);
body.pipe(tempFile);
resolve(Service.createSuccessResponse(
{
file_name_local: tempFileName,
file_name: file_name,
content_length: response.ContentLength,
}
,"SUCCESS")); // This is **responsePayload** in the next snippet.
...
Step 2: Send the response using expressjs (File: Controller.js)
if(responsePayload.file_name_local){
response.set('Content-Length',responsePayload.content_length);
response.write(fs.readFileSync(responsePayload.file_name_local));
response.end();
response.connection.end();
}
Step 3: Define image (File: view-profile.vue)
<template>
...
<v-img src="dpURL"/>
...
</template>
Step 4 - EAGER DOWNLOAD: Receive image as blob (File: view-profile.vue)
<script>
...
mounted(){
...
this.getDP(this.profileInfo.dp_url).then(r => {
this.dpURL = window.URL.createObjectURL(new Blob([r.data])); // Do we need explicit MIME here?
}).catch(e => {
console.error("DP not retrieved: "+JSON.stringify(e));
})
...
},
methods: {
...
getDP(file_name){
return new Promise(
async(resolve, reject) => {
callGenericAPI({
url: this.$config.API_HOST_URL+'/file',
configObj: {
method: 'get',
params: {
file_name: file_name,
file_category: 'user-dp'
},
headers: {
'api_token': idToken,
'Cache-Control':'no-cache'
},
responseType: 'blob'
},
$axios: this.$axios //Just some crazy way of calling axios. Don't judge me here.
})
.then(r => {
console.log("DP received.");
resolve(r.data);
})
.catch(e => {
console.error("DP not received." + JSON.stringify(e));
reject(e);
})
}
)
},
...
}
Step 5 - LAZY DOWNLOAD: Receive image as blob (File: view-profile.vue > custom-link.vue - child) after clicking the link
File: view-profile.vue
<template>
...
<CustomLink file_name="fileName" file_category="USER_DP" label="User Profile Picture"/>
...
</template>
File: custom-link.vue
<template>
<a v-text="label"
#click.prevent="downloadItem()">
</template>
<script>
...
methods:{
downloadItem(){
this.$axios.get(...)
.then(response => {
const blob = new Blob([response.data],{ type: response.data.type }) // Here I tried Uint8Array.from(response.data) as well
const link = document.createElement('a')
link.href = window.URL.createObjectURL(blob,{ type: response.data.type });
link.download = this.file_name
link.click()
URL.revokeObjectURL(link.href)
}
...
}
...
}
...
</script>
The problem here is that image is being downloaded. I can see it from devtools network tab. But I just can't link/display it reactively (shown in Step 3).
After Step 5, the file downloads but has a bloated size compared to original. When I convert response.data to Uint8Array, the file shrinks compared to original.
It looks like a very simple get and display image problem but haven't got the combination of mimetypes and utilities right. Express supports sendFile and download options for the files. But both the calls don't seem to work for some reason!
Do you have any pointers?
Can't we just download the image somewhere on the webserver directory and reactive link it? Even that should be fine with me. Can avoid some API calls.
The following code is taken from https://aws.plainenglish.io/using-node-js-to-display-images-in-a-private-aws-s3-bucket-4c043ed5c5d0 :
function encode(data){
let buf = Buffer.from(data);
let base64 = buf.toString('base64');
return base64
}
getImage()
.then((img)=>{
let image="<img src='data:image/jpeg;base64," + encode(img.Body) + "'" + "/>";
let startHTML="<html><body></body>";
let endHTML="</body></html>";
let html=startHTML + image + endHTML;
res.send(html)
}).catch((e)=>{
res.send(e)
})
#coder.in.me's post leads us to the answer - obtain S3 Object's presignedURL.
Added benefit - you can also expire the URL beyond a time-limit.

How to mock an image with a fixture in cypress

I'm using cypress to test my VueJS application. The one thing I'm having trouble with is mocking an image to be displayed on the page. For my use case, I'm simply loading a user profile with the following code:
describe('Test Login', () => {
it('Can Login', () => {
cy.server();
cy.route({
method: 'GET',
url: '/api/account/',
response: 'fx:profile.json',
});
cy.route('**/media/demo1.png', 'fx:demo1.png');
});
});
fixtures/profile.json
{
"avatar": "http://localhost:8080/media/demo1.png",
"username": "cypress",
"email": "email#cypress.io",
"pk": 1,
"is_staff": true,
"is_superuser": true,
"is_active": true
}
The profile fixture data is loading correctly in the test. In my fixtures folder, I also have a demo1.png file. I am expecting this image to be loaded and displayed on the page during my test, but it is being displayed as a broken image.
In the network tab, it shows demo1.png as a broken image with a 200 response code and type of text/html.
The cypress documentation mostly discusses images in the context of uploading images, but I haven't been able to find an example of how I can mock an image that is loaded through a <img> tag. Is there an easier way of doing this?
I am not sure if this answer can help you. But at least it is a workaround for this problem ;-)
Say we have a HTML like this:
<html>
<body>
<button id="button">load</button>
<div id="profile">
</div>
<script>
function httpGetAsync(theUrl, callback)
{
var xmlHttp = new XMLHttpRequest();
xmlHttp.onreadystatechange = function() {
if (xmlHttp.readyState == 4 && xmlHttp.status == 200)
callback(JSON.parse(xmlHttp.responseText));
}
xmlHttp.open("GET", theUrl, true); // true for asynchronous
xmlHttp.send(null);
}
document.getElementById("button").addEventListener("click", () => {
httpGetAsync("/api/account/", (result) => {
var div = document.querySelector("#profile");
var img = document.createElement("img");
img.src = result.avatar;
div.appendChild(img)
})
})
</script>
</body>
</html>
source: HTTP GET request in JavaScript?
And you want to load the profile after the click was done. Then you can use MutationObserver to replace the img.src.
First, write the MutationObserver:
var observeDOM = (function(){
var MutationObserver = window.MutationObserver || window.WebKitMutationObserver;
return function( obj, callback ){
if( !obj || !obj.nodeType === 1 ) return; // validation
if( MutationObserver ){
// define a new observer
var obs = new MutationObserver(function(mutations, observer){
callback(mutations);
})
// have the observer observe foo for changes in children
obs.observe( obj, { childList:true, subtree:true });
}
else if( window.addEventListener ){
obj.addEventListener('DOMNodeInserted', callback, false);
obj.addEventListener('DOMNodeRemoved', callback, false);
}
}
})();
(heavily copy & pasted from Detect changes in the DOM)
Now you are able to do this:
describe('Test Login', () => {
it('Can Login', () => {
var win = null;
cy.server();
cy.route({
method: 'GET',
url: '/api/account/',
response: 'fx:profile.json'
});
cy.visit("index.html").then(w => {
cy.get("#profile").then(pro => {
var e = pro[0];
observeDOM(e, (m) => {
// add a red dot image
m[0].addedNodes[0].src = "data:image/png;base64,"+
"iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAABGdBTUEAALGP"+
"C/xhBQAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9YGARc5KB0XV+IA"+
"AAAddEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIFRoZSBHSU1Q72QlbgAAAF1J"+
"REFUGNO9zL0NglAAxPEfdLTs4BZM4DIO4C7OwQg2JoQ9LE1exdlYvBBeZ7jq"+
"ch9//q1uH4TLzw4d6+ErXMMcXuHWxId3KOETnnXXV6MJpcq2MLaI97CER3N0"+
"vr4MkhoXe0rZigAAAABJRU5ErkJggg=="
})
})
cy.get("button").click()
})
});
});
(yeah at least some lines of code are written on my own ;-P)
You can read the image from the img.src attribute from the fixtures folder. For the sake of simplicity I have used a static base64 string here.
And the result:
We are not using this kind of stuff in our aurelia app but I tried similar things in a private project some time ago.

How to upload file to server using react-native

I am developing a app where i need to upload an image to the server. Based on the image i get a response which i need to render?.
Can you please help me how to upload an image using react-native?.
There is file uploading built into React Native.
Example from React Native code:
var photo = {
uri: uriFromCameraRoll,
type: 'image/jpeg',
name: 'photo.jpg',
};
var body = new FormData();
body.append('authToken', 'secret');
body.append('photo', photo);
body.append('title', 'A beautiful photo!');
var xhr = new XMLHttpRequest();
xhr.open('POST', serverURL);
xhr.send(body);
My solution is using fetch API and FormData.
Tested on Android.
const file = {
uri, // e.g. 'file:///path/to/file/image123.jpg'
name, // e.g. 'image123.jpg',
type // e.g. 'image/jpg'
}
const body = new FormData()
body.append('file', file)
fetch(url, {
method: 'POST',
body
})
I wrote something like that. Check out https://github.com/kamilkp/react-native-file-transfer
I have been struggling to upload images recently on react-native. I didn't seem to get the images uploaded. This is actually because i was using the react-native-debugger and network inspect on while sending the requests. Immediately i switch off network inspect, the request were successful and the files uploaded.
I am using the example from this answer above it works for me.
This article on github about the limitations of network inspect feature may clear things for you.
Just to build on the answer by Dev1, this is a good way to upload files from react native if you also want to show upload progress. It's pure JS, so this would actually work on any Javascript file.
(Note that in step #4 you have to replace the variables inside the strings with the type and file endings. That said, you could just take those fields out.)
Here's a gist I made on Github: https://gist.github.com/nandorojo/c641c176a053a9ab43462c6da1553a1b
1. for uploading one file:
// 1. initialize request
const xhr = new XMLHttpRequest();
// 2. open request
xhr.open('POST', uploadUrl);
// 3. set up callback for request
xhr.onload = () => {
const response = JSON.parse(xhr.response);
console.log(response);
// ... do something with the successful response
};
// 4. catch for request error
xhr.onerror = e => {
console.log(e, 'upload failed');
};
// 4. catch for request timeout
xhr.ontimeout = e => {
console.log(e, 'cloudinary timeout');
};
// 4. create formData to upload
const formData = new FormData();
formData.append('file', {
uri: 'some-file-path', // this is the path to your file. see Expo ImagePicker or React Native ImagePicker
type: `${type}/${fileEnding}`, // example: image/jpg
name: `upload.${fileEnding}` // example: upload.jpg
});
// 6. upload the request
xhr.send(formData);
// 7. track upload progress
if (xhr.upload) {
// track the upload progress
xhr.upload.onprogress = ({ total, loaded }) => {
const uploadProgress = (loaded / total);
console.log(uploadProgress);
};
}
2. uploading multiple files
Assuming you have an array of files you want to upload, you'd just change #4 from the code above to look like this:
// 4. create formData to upload
const arrayOfFilesToUpload = [
// ...
];
const formData = new FormData();
arrayOfFilesToUpload.forEach(file => {
formData.append('file', {
uri: file.uri, // this is the path to your file. see Expo ImagePicker or React Native ImagePicker
type: `${type}/${fileEnding}`, // example: image/jpg
name: `upload.${fileEnding}` // example: upload.jpg
});
})
In my opinion, the best way to send the file to the server is to use react-native-fs package, so install the package
with the following command
npm install react-native-fs
then create a file called file.service.js and modify it as follow:
import { uploadFiles } from "react-native-fs";
export async function sendFileToServer(files) {
return uploadFiles({
toUrl: `http://xxx/YOUR_URL`,
files: files,
method: "POST",
headers: { Accept: "application/json" },
begin: () => {
// console.log('File Uploading Started...')
},
progress: ({ totalBytesSent, totalBytesExpectedToSend }) => {
// console.log({ totalBytesSent, totalBytesExpectedToSend })
},
})
.promise.then(({ body }) => {
// Response Here...
// const data = JSON.parse(body); => You can access to body here....
})
.catch(_ => {
// console.log('Error')
})
}
NOTE: do not forget to change the URL.
NOTE: You can use this service to send any file to the server.
then call that service like the following:
var files = [{ name: "xx", filename:"xx", filepath: "xx", filetype: "xx" }];
await sendFileToServer(files)
NOTE: each object must have name,filename,filepath,filetype
A couple of potential alternatives are available. Firstly, you could use the XHR polyfill:
http://facebook.github.io/react-native/docs/network.html
Secondly, just ask the question: how would I upload a file in Obj-C? Answer that and then you could just implement a native module to call it from JavaScript.
There's some further discussion on all of this on this Github issue.
Tom's answer didn't work for me. So I implemented a native FilePickerModule which helps me choose the file and then use the remobile's react-native-file-transfer package to upload it. FilePickerModule returns the path of the selected file (FileURL) which is used by react-native-file-transfer to upload it.
Here's the code:
var FileTransfer = require('#remobile/react-native-file-transfer');
var FilePickerModule = NativeModules.FilePickerModule;
var that = this;
var fileTransfer = new FileTransfer();
FilePickerModule.chooseFile()
.then(function(fileURL){
var options = {};
options.fileKey = 'file';
options.fileName = fileURL.substr(fileURL.lastIndexOf('/')+1);
options.mimeType = 'text/plain';
var headers = {
'X-XSRF-TOKEN':that.state.token
};
options.headers = headers;
var url = "Set the URL here" ;
fileTransfer.upload(fileURL, encodeURI(url),(result)=>
{
console.log(result);
}, (error)=>{
console.log(error);
}, options);
})
Upload Files : using expo-image-picker npm module. Here we can upload any files or images etc. The files in a device can be accessed using the launchImageLibrary method. Then access the media on that device.
import * as ImagePicker from "expo-image-picker";
const loadFile = async () => {
let result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ImagePicker.MediaTypeOptions.All,
aspect: [4, 3],
});
return <Button title="Pick an image from camera roll" onPress={loadFile} />
}
The above code used to access the files on a device.
Also, use the camera to capture the image/video to upload by using
launchCameraAsync with mediaTypeOptions to videos or photos.