G4bri3l
G4bri3l

Reputation: 5146

How do I handle CORS with html2Canvas and AWS S3 images?

I know similar questions have been asked before but I still can't make it work. I have a div with images inside of it loaded from a bucket in AWS s3, they load perfectly no problem.

Now I want to be able to save as a jpeg whatever is in that specific div (like taking a screenshot), the plugin html2canvas helps with that. The problem is that when I try to actually save it (or simply show immediately the result of such screenshot) I run into these issues:

In order to solve this I set up CORS on my AWS S3 bucket, but that didn't seem to work (or it worked partially). I noticed that the response header of those images don't have CORS metadata when the plugin uses them to generate the jpeg. I then tried to set crossOrigin="anonymous" in those images inside the div but it would throw a CORS error right away, which shouldn't happen since the AWS bucket has been set up for that as follows:

<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
    <CORSRule>
        <AllowedOrigin>*</AllowedOrigin>
        <AllowedMethod>GET</AllowedMethod>
        <AllowedHeader>Authorization</AllowedHeader>
    </CORSRule>
</CORSConfiguration>

I am running out of options on how to make this work. Any idea on how to proceed from here would be very appreciated.

EDIT: More details, I am using React and the images urls are retrieved from a server. This means that as soon as I get this array of urls I generate:

<div>
  { urls.map(url => <img src={url} alt="some alt" />) }
</div>

If I add the crossOrigin="anonymous" I get the CORS error. If I leave that out, the images display but then the html2canvas plugin throws a CORS error as well when trying to generate the "screenshot".

More details about the HTTP requests. So the first time I load an image inside the div, this is the Response Header:

Accept-Ranges:bytes
Access-Control-Allow-Methods:GET
Access-Control-Allow-Origin:*
Cache-Control:max-age=2592000
Content-Length:508208
Content-Type:image/png
Date:Thu, 16 Feb 2017 18:25:05 GMT
Last-Modified:Wed, 15 Feb 2017 19:09:44 GMT
Server:AmazonS3
Vary:Origin, Access-Control-Request-Headers, Access-Control-Request-Method 

Now this works if crossOrigin='anonymous' and the picture is not from the cache. If the crossOrigin attribute is not set I get:

Accept-Ranges:bytes
Cache-Control:max-age=2592000
Content-Length:508208
Content-Type:image/png
Date:Thu, 16 Feb 2017 19:03:53 GMT
Last-Modified:Wed, 15 Feb 2017 19:09:44 GMT
Server:AmazonS3

or it throws a CORS error on the console without showing any meta data on the response header. I tried adding a random string at the end of the url (?somethingsomething) so that they would never be grabbed from the cache, and that fixed the issue completely. But this is just a hack and it works for now but it is not definitely the solution I am looking for. I think Chrome is doing something with the cache and I have a hard time tracking the source of the issue, other than the fact that it's hard to reproduce this problem on my machine since it always retrieved the screenshot from cache even if I am using completely new images and disable/clear cache. It's very confusing.

Upvotes: 25

Views: 33854

Answers (7)

Sultan Sharma
Sultan Sharma

Reputation: 13

as i was also facing same issue , so this is working for me

const downloadImage = async (ref: React.RefObject<HTMLDivElement>,fileName: string) => {
 
    if (ref.current) {
      html2canvas(ref.current, { proxy: {imgURL}, scale: 2, useCORS:true }).then((canvas) => {
        const link = document.createElement('a');
        link.href = canvas.toDataURL('image/png');
        link.download = fileName;
        document.body.appendChild(link);
        link.click();
        document.body.removeChild(link);
      });
    }   };

And Image with <Image> tag

And S3 Cors Policy

[
    {
        "AllowedHeaders": [
            "*"
        ],
        "AllowedMethods": [
            "GET",
            "HEAD"
        ],
        "AllowedOrigins": [
            "*"
        ],
        "ExposeHeaders": [],
        "MaxAgeSeconds": 3000
    }
]

Upvotes: 0

gooneraki
gooneraki

Reputation: 81

After many experiments I ended up fetching each image with a no-cache directive and it worked!

Somewhere along my code I added this:

const images = container.querySelectorAll('img');

images.forEach(async img => {
   await fetch(img.getAttribute('src'), { cache: 'no-cache' });
});

Upvotes: 0

Kevin Collins
Kevin Collins

Reputation: 1461

We solved this problem by changing the Response headers policy in the CloudFront distribution that sits in front of our S3 bucket.

Previously we had it set to SimpleCORs, which was responding with the error: No 'Access-Control-Allow-Origin' header is present on the requested resource.

Changing it to CORS-With-Preflight fixed the issue.

https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/using-managed-response-headers-policies.html#managed-response-headers-policies-cors-preflight

Upvotes: 1

Dave Cole
Dave Cole

Reputation: 2765

I tried the allowTaint/useCORS solution but the problem I ran into is Chromium will cache the image after the first load, but not preserve the CORS headers, resulting in no image rendered.

The only solution that worked for me was to load the image server-side and base64-encode it into a string that I sent to the client side to use in an image tag.

For node, this is what that looks like:

import fetch from 'node-fetch'
const response  = await fetch(url);
image_base64 = (await response.buffer()).toString('base64');

with the resulting image tag on the other end as:

<img src=`data:image/jpeg;base64, ${image_base64}` />

Your mimetype may vary; I would write a function to determine that based on the extention.

Upvotes: 0

Sanjil Shakya
Sanjil Shakya

Reputation: 51

I solved this problem by adding proxy:( image src ) option in html2canvas. Now your image is also included in pdf

Typescript Code:

download() {
        var data = document.getElementById('view-eob');
        html2canvas(data, { proxy: this.eobDetail.member.parentCompany.logo })
        .then(canvas => {
            var imgWidth = 208;
            var imgHeight = canvas.height * imgWidth / canvas.width;
            const contentDataURL = canvas.toDataURL('image/png')
            let pdf = new jsPDF('p', 'mm', 'a4');
            var position = 0;
            pdf.addImage(contentDataURL, 'PNG', 0, position, imgWidth, imgHeight)
            pdf.save(`${this.eobDetail.episode.name}-EOB.pdf`);
        });
    }

HTML Code:

<div>
     <img [src]="this.eobDetail.member.parentCompany.logo"/>
</div>
<button type="button" (click)="download()"> Download</button>

Upvotes: 5

I solved this error with adding crossOrigin attribute at IMG tag. So, your code will look like (tag at React js):

<Image crossOrigin="true" />

And my configuration at S3 bucket with CORS:

<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<CORSRule>
    <AllowedOrigin>http://localhost:8000</AllowedOrigin>
    <AllowedMethod>GET</AllowedMethod>
    <AllowedMethod>HEAD</AllowedMethod>
    <AllowedMethod>PUT</AllowedMethod>
    <AllowedMethod>POST</AllowedMethod>
    <AllowedMethod>DELETE</AllowedMethod>
    <MaxAgeSeconds>3000</MaxAgeSeconds>
    <AllowedHeader>Authorization</AllowedHeader>
</CORSRule>
<CORSRule>
    <AllowedOrigin>https://testing.d1wr8lk28mi6l0.amplifyapp.com</AllowedOrigin>
    <AllowedMethod>GET</AllowedMethod>
    <AllowedMethod>HEAD</AllowedMethod>
    <AllowedMethod>PUT</AllowedMethod>
    <AllowedMethod>POST</AllowedMethod>
    <AllowedMethod>DELETE</AllowedMethod>
    <MaxAgeSeconds>3000</MaxAgeSeconds>
    <AllowedHeader>Authorization</AllowedHeader>
</CORSRule>
</CORSConfiguration>

HTML2CANVAS:

 html2canvas(getWrapper, { allowTaint: true, useCORS: true, logging: true })
  .then((canvas) => {
    const imgData = canvas.toDataURL('image/png');
    console.log(imgData);
  });

Upvotes: 3

CrandellWS
CrandellWS

Reputation: 2804

See the edit, I did try setting the crossOrigin attribute with no luck, and I use useCORS set to true (forgot to mention that sorry). Still no luck.

I fixed some cors issues I was having with the combination of Google Chrome, AWS S3, and multiple origins.

I found this stackoverflow thread: Chrome + CORS + cache - requesting same file from two different origins

Which links to this bug report: https://bugs.chromium.org/p/chromium/issues/detail?id=260239

Anyhow as workaround solution you can try this modified version of html2canvas: https://gist.github.com/CrandellWS/6bc2078aced496004d7a045e6360f19b

use the options:

allowTaint : false,
useCORS: true

Hope that helps.

FYI, this will add the current time stamp to cors image urls to sidestep a cache issue I was having on Chrome... https://gist.github.com/CrandellWS/6bc2078aced496004d7a045e6360f19b#file-html2canvas-js-L6838

Which means it will effect performance by re-downloading those images...

original post: https://github.com/niklasvh/html2canvas/issues/1544#issuecomment-435640901

Upvotes: 13

Related Questions