Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rendering fails when using remote pictures #338

Closed
WaldemarLehner opened this issue Feb 14, 2023 · 5 comments · Fixed by #357
Closed

Rendering fails when using remote pictures #338

WaldemarLehner opened this issue Feb 14, 2023 · 5 comments · Fixed by #357
Assignees
Labels
a-2d Relates to the 2d package b-enhancement New feature or request c-accepted The issue is ready to be worked on

Comments

@WaldemarLehner
Copy link
Contributor

WaldemarLehner commented Feb 14, 2023

Describe the bug
If a scene has images from remote source, trying to render will result in an error:

Failed to execute 'toDataURL' on 'HTMLCanvasElement': Tainted canvases may not be exported.
image

To Reproduce
Have a scene with a remote Image, then try to render.

import { makeScene2D } from "@motion-canvas/2d";
import { Image, Layout } from "@motion-canvas/2d/lib/components";
import { waitFor } from "@motion-canvas/core/lib/flow";

export default makeScene2D(function* (scene) {
    scene.add(<Layout>
        <Image scale={.2} src={"https://images.unsplash.com/photo-1515229144611-617d3ce8e108"}/> {/* any remote picture works */}
    </Layout>)
    yield* waitFor(1)
});

Additional context
Error occurs both on Firefox and Chromium.

@WaldemarLehner WaldemarLehner changed the title Loading in remote and local images causes Tainted canvases error Rendering fails when using remote pictures Feb 14, 2023
@aarthificial
Copy link
Contributor

Kinda similar to #234
Not sure if we can fix this since it seems like a CORS issue on the image server side

@aarthificial aarthificial removed their assignment Feb 14, 2023
@aarthificial aarthificial added b-enhancement New feature or request c-discussion The issue is being discussed a-2d Relates to the 2d package labels Feb 14, 2023
@WaldemarLehner
Copy link
Contributor Author

WaldemarLehner commented Feb 14, 2023

What might work is defining a proxy in vite.config.ts that does all requests on behalf of the Browser.

I would add a new helper utility that wraps a src string, like https://via.placeholder.com/300.png/09f/fff into a base64, escaped string that gets passed to a proxy endpoint on vite, say /proxy.

const originalSrc: string;
return `/proxy/${encodeURIComponent(originalSrc)}`

I will have to look into how proxying works with vite.
A quick glance at the docs tells me I need to specify a server, which would not work in this use case as we would need to define all endpoints.

@WaldemarLehner
Copy link
Contributor Author

WaldemarLehner commented Feb 14, 2023

image

Proof of Concept works

Click to expand / Outdated: I basically implemented a Plugin for Vite which adds some a Middleware to Vite's Server that intercepts any calls starting with `/proxy/`.
const proxyPlugin = (): Plugin => {
  
  return {
    name: "proxy-server",
    configureServer(server) {

      server.middlewares.use((req, res, next) => {
        if (!req.url || !req.url.startsWith("/proxy/")) {
          return next()
        }

        if(req.method !== "GET") {
          const msg = "Only GET requests are allowed"
          res.writeHead(400, msg)
          res.write(msg)
          res.end()
          return;
        }
        
        // Extract destination, e.g 
        function extractDestinationUrl(url: string) {
          try {
            const withoutPrefix = url.replace("/proxy/", "")
            const asUrl = new URL(decodeURIComponent(withoutPrefix))
            console.log(`"${asUrl.protocol}"`)
            if(asUrl.protocol !== "http:" && asUrl.protocol !== "https:") {
              throw new Error("Only supported protocols are http and https") // TODO: Is this enough to prevent access to file:// ?
            }

            return [asUrl, undefined] as const
          }
          catch(err) {
            return [undefined, err+""] as const
          }
        }

        const [sourceUrl, error] = extractDestinationUrl(req.url)

        if (error || !sourceUrl) {
          res.writeHead(400, error)
          res.write(error)
          res.end()
          return
        }

        
        // Call URL on behalf of callee
        const proxy_req = http.request( {
          hostname: sourceUrl.hostname,
          path: sourceUrl.pathname,
          method: "get"
        }, (proxy_res) => { 
          let buffer: any[] = []
          console.log("ping")
          proxy_res.on("data", (chunk) => {
            buffer.push(chunk )
          })

          proxy_res.on("error", () => {
            res.statusCode = 400
            res.statusMessage = "Proxying failed"
            res.end()
          })

          proxy_res.on("close", () => {
            const body = Buffer.concat(buffer)
            // only proxy headers we actually need
            const allowedHeaders = [
              "content-type", "content-length", "cache-control"
            ]
            for(const header of allowedHeaders) {
              const headerValue = proxy_res.headers[header]
              if(!headerValue) {
                continue // Header does not exist. Ignore
              }
              res.setHeader(header, headerValue)
            }
            // Additionally set the X-Proxied-Url Header for debugging
            res.setHeader("X-Proxied-Url", sourceUrl.toString())

            
            res.end(body)
            console.log("done")
          })

        }).end()

      })
    }
  }
}

@WaldemarLehner
Copy link
Contributor Author

Yesterday I took the time to create a Plugin that provides a proxy server, and a utility function to wrap remote requests, so that they get sent to the proxy instead. It's basically what I shown in the Proof of Concept, with more checks, configuration and using Axios instead of raw http

https://github.com/WaldemarLehner/motion-canvas-cors-proxy

@Ross-Esmond mentioned it might be a good idea to bring this into the core repo instead.

Relevant discussion on Discord: https://discord.com/channels/1071029581009657896/1074755895529062531/1075236819597267044

@aarthificial aarthificial added c-accepted The issue is ready to be worked on and removed c-discussion The issue is being discussed labels Feb 15, 2023
@exdeejay
Copy link
Contributor

exdeejay commented Feb 15, 2023

The image in the example should work, since it's served with the "Access-Control-Allow-Origin: *" header. I added image.crossOrigin = 'anonymous' to the Image component and rendering works as normal. This won't work for images without the Access-Control-Allow-Origin header, though.

WaldemarLehner added a commit to WaldemarLehner/motion-canvas that referenced this issue Feb 16, 2023
WaldemarLehner added a commit to WaldemarLehner/motion-canvas that referenced this issue Feb 16, 2023
WaldemarLehner added a commit to WaldemarLehner/motion-canvas that referenced this issue Feb 16, 2023
WaldemarLehner added a commit to WaldemarLehner/motion-canvas that referenced this issue Feb 16, 2023
WaldemarLehner added a commit to WaldemarLehner/motion-canvas that referenced this issue Feb 16, 2023
WaldemarLehner added a commit to WaldemarLehner/motion-canvas that referenced this issue Feb 16, 2023
WaldemarLehner added a commit to WaldemarLehner/motion-canvas that referenced this issue Feb 18, 2023
…otion-canvas#338)


Apply proposed changes

Co-authored-by: Jacob <64662184+aarthificial@users.noreply.github.com>
WaldemarLehner added a commit to WaldemarLehner/motion-canvas that referenced this issue Feb 19, 2023
fix(vite-plugin): fix remote sources breaking Rendering by implementing proxy(motion-canvas#338)

feat(core): add viaProxy Utility to rewrite requests to proxy (motion-canvas#338)

Apply proposed changes

Co-authored-by: Jacob <64662184+aarthificial@users.noreply.github.com>
WaldemarLehner added a commit to WaldemarLehner/motion-canvas that referenced this issue Feb 19, 2023
fix(vite-plugin): fix remote sources breaking Rendering by implementing proxy(motion-canvas#338)

feat(core): add viaProxy Utility to rewrite requests to proxy (motion-canvas#338)

Apply proposed changes

Co-authored-by: Jacob <64662184+aarthificial@users.noreply.github.com>
WaldemarLehner added a commit to WaldemarLehner/motion-canvas that referenced this issue Feb 21, 2023
fix(vite-plugin): fix remote sources breaking Rendering by implementing proxy(motion-canvas#338)

feat(core): add viaProxy Utility to rewrite requests to proxy (motion-canvas#338)

Apply proposed changes

Co-authored-by: Jacob <64662184+aarthificial@users.noreply.github.com>
aarthificial pushed a commit that referenced this issue Feb 23, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
a-2d Relates to the 2d package b-enhancement New feature or request c-accepted The issue is ready to be worked on
Projects
None yet
3 participants