seuling
seuling

Reputation: 2956

aws s3 with html2canvas - CORS issue with multiple browsers

I knew many questions and issues have been already made. But I can't find out clear answer to solve this issue.

I use html2canvas to screenshot my page - with image from amazon s3 (cloudfront, too)

I tried almost every answer from SO and html2canvas issues.

I setup my S3 CORS to allow all / also set my bucket to public. Also I gave all public access to Everyone (Just to test if it works. I will block them after deploy)

Here's my CORS for s3

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

And my bucket policy

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "AddPerm",
            "Effect": "Allow",
            "Principal": "*",
            "Action": [
                "s3:PutObject",
                "s3:PutObjectAcl",
                "s3:GetObject",
                "s3:GetObjectAcl",
                "s3:DeleteObject"
            ],
            "Resource": "arn:aws:s3:::ubkorea/*"
        }
    ]
}

When I send request with curl to my images, it has quiet normal response with Access-Control-Allow-Origin. Below is my response.

HTTP/1.1 200 OK
x-amz-id-2: 2v8iSfy/9yvLRe+CFiUqEjUz96IcRC86t1m7IBy1NDakkYIriumosvVYECeYgcPAcCW1axpwF00=
x-amz-request-id: 4ADD8456071CE5C3
Date: Fri, 13 Jul 2018 02:55:10 GMT
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET
Access-Control-Max-Age: 3000
Vary: Origin, Access-Control-Request-Headers, Access-Control-Request-Method
Last-Modified: Sat, 07 Jul 2018 07:54:35 GMT
ETag: "2374a71498a1066a412565cbb3b03e86"
Accept-Ranges: bytes
Content-Type: image/jpeg
Content-Length: 52134
Server: AmazonS3

When doing this and html2canvas with allowTaint: false, useCORS: true, sometimes it worked well but sometimes not in chrome. Also, it worked in IE but not worked in Safari. I don't know what's problem.

I guess it's kind of CORS problem. Because I saw similar problem in my codepen example. Sometimes it show the image, but sometimes not.

here is my codepen exmaple. (https://codepen.io/anon/pen/YjXbaZ)

It works well whether crossorigin="anonymous" or not, but sometimes it works, sometimes not either.

I also tried http and https, and change url from s3.region_name/bucket_name/... to bucket_name.s3.region_name/....

Is the any problem in my CORS setting or bucket policy? Or is there any possibility about cache issue? I'm very confusing now.

I'll be very appreciate with any comment and answer. Thanks in advance!

Upvotes: 6

Views: 5405

Answers (3)

Dave Cole
Dave Cole

Reputation: 2765

I tried the above suggestion by @seuling but the problem I ran into in Chromium was that the image would load/render in the browser first and then html2canvas would use the same random query string I had supplied it (was using React). The only solution that worked for me was to load the image server-side and supply that to the client.

If you did that in node, it would look like this:

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

And client-side:

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

Your mime-type may vary; it would be best to write a function that picked the mime-type based on the URL's file extension.

Upvotes: 0

Vladyslav Frizen
Vladyslav Frizen

Reputation: 227

The following solution worked for me.

Preconditions - you have the required CORS config in your S3 bucket.

const getBase64FromUrl = async (url) => {
    const img = new Image()
    img.crossOrigin = 'anonymous'
    img.src = `${url}?__v=${Date.now()}`
    return new Promise(resolve => {
        img.onload = function () {
            const canvas = document.createElement('canvas')
            canvas.width = img.width
            canvas.height = img.height
            const ctx = canvas.getContext('2d')
            ctx.drawImage(img, 0, 0)
            const base64String = canvas.toDataURL('image/png')
            resolve(base64String)
        }
    })
}

const html = document.querySelector<HTMLElement>('your-selector')

const canvas = await html2canvas(html, {
onclone: async (_, html) => {
    const images = html.querySelectorAll('img')
    for await (const img of images) {
        if (img.src.includes('data:image')) continue
        img.src = await getBase64FromUrl(img.src)
    }
},
})

const base64 = canvas.toDataURL('')

Upvotes: 0

seuling
seuling

Reputation: 2956

Finally, I found the answer. I share my solution for people who suffered with same problem.

The problem is the cache from s3 - s3 automatically serve cache so second request from the html2canvas not include header origin options, it just use the cached image.

So the solution is avoid cache. I tried to find cache configurations in s3, but I couldn't find something. So I use some trick - change target image's src by add random string after s3 url : like this `s3.xxx.xxx/media/my_img.png?_random'.

After that, it works well with changed image url.

Here is example of my code.

// function for make random string
function makeid() {
  var text = "";
  var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";

  for (var i = 0; i < 5; i++)
    text += possible.charAt(Math.floor(Math.random() * possible.length));

  return text;
}

// and add random string to src
// also add slash at the end of the url for safari
var src = $("#detail_img").attr('src');
document.getElementById('detail_img').src = src + '?' + makeid() + '/';

Upvotes: 6

Related Questions