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

generation works only once: [Error] TypeError: Cannot read properties of undefined (reading '0') #650

Open
SwapnilSoni1999 opened this issue Nov 27, 2024 · 3 comments · May be fixed by #651

Comments

@SwapnilSoni1999
Copy link

SwapnilSoni1999 commented Nov 27, 2024

Bug report

Description / Observed Behavior

I'm using satori in nodejs environment and the svg generation only works once then it gives an error

Expected Behavior

It should generate svg

Here's the code snippet to regenerate the issue

const poppinsExtraBoldPath = path.join(ASSETS_DIR, 'Poppins-ExtraBold.ttf')
const boldFont = readFileSync(poppinsExtraBoldPath)

const generateHexagonProfilePictureSvg = async (
  imageUrl: string,
  dimensions: {
    width: number
    height: number
  } = {
    width: 130,
    height: 120
  }
) => {
  const JSX = {
    type: 'div',
    props: {
      style: {
        display: 'flex',
        justifyContent: 'center',
        alignItems: 'center',
        width: `${dimensions.width}px`,
        height: `${dimensions.height}px`,
        backgroundColor: 'transparent'
      },
      children: [
        {
          type: 'svg',
          props: {
            width: `${dimensions.width}`,
            height: `${dimensions.height}`,
            viewBox: '0 0 135.99488 126.66667',
            xmlns: 'http://www.w3.org/2000/svg',
            children: [
              {
                type: 'defs',
                props: {
                  children: [
                    {
                      type: 'clipPath',
                      props: {
                        id: 'svgPath',
                        children: [
                          {
                            type: 'path',
                            props: {
                              d: 'm 0,0 h -26.12 c -8.174,0 -15.727,-4.361 -19.814,-11.44 l -13.06,-22.62 c -4.087,-7.079 -4.087,-15.801 0,-22.88 l 13.06,-22.62 C -41.847,-86.639 -34.294,-91 -26.12,-91 H 0 c 8.174,0 15.727,4.361 19.814,11.44 l 13.06,22.62 c 4.087,7.079 4.087,15.801 0,22.88 l -13.06,22.62 C 15.727,-4.361 8.174,0 0,0',
                              transform:
                                'matrix(1.3333333,0,0,-1.3333333,85.410866,2.6666667)'
                            }
                          }
                        ]
                      }
                    }
                  ]
                }
              },
              {
                type: 'image',
                props: {
                  href: imageUrl,
                  width: '100%',
                  height: '100%',
                  preserveAspectRatio: 'xMidYMid slice',
                  clipPath: 'url(#svgPath)'
                }
              },
              {
                type: 'path',
                props: {
                  d: 'm 0,0 h -26.12 c -8.174,0 -15.727,-4.361 -19.814,-11.44 l -13.06,-22.62 c -4.087,-7.079 -4.087,-15.801 0,-22.88 l 13.06,-22.62 C -41.847,-86.639 -34.294,-91 -26.12,-91 H 0 c 8.174,0 15.727,4.361 19.814,11.44 l 13.06,22.62 c 4.087,7.079 4.087,15.801 0,22.88 l -13.06,22.62 C 15.727,-4.361 8.174,0 0,0',
                  transform:
                    'matrix(1.3333333,0,0,-1.3333333,85.410866,2.6666667)',
                  fill: 'none',
                  stroke: 'white',
                  strokeWidth: '5'
                }
              }
            ]
          }
        }
      ]
    }
  }

  console.log({
    JSX
  })

  const svg = await satori(JSX, {
    width: dimensions.width,
    height: dimensions.height,
    fonts: [
      {
        name: 'Poppins',
        style: 'normal',
        data: boldFont
      }
    ],
    debug: true
  })

  return svg
}

// FIRST CALL WORKS!!
const profilePictureSvg = await generateHexagonProfilePictureSvg(
    data.profileUrl,
    {
      width: 140,
      height: 130
    }
  )

// 2nd CALL FAILS!
const profilePictureSvg2 = await generateHexagonProfilePictureSvg(
    data.profileUrl,
    {
      width: 140,
      height: 130
    }
  )

Additional Context

"satori": "^0.12.0"

@SwapnilSoni1999
Copy link
Author

I managed to fix by setting base64 encoded image data in href of image tag inside svg
However setting a direct url isn't working for the 2nd time
I suspect the LRU caching is the culprit

@erxclau
Copy link

erxclau commented Nov 28, 2024

Here's a reproduction in the playground.

Making an update in the code (e.g. adding a space somewhere) will trigger this error:

undefined is not an object (evaluating 'Pt.get(t)[0]')

The browser console traces back to here:

Screenshot 2024-11-28 at 3 36 24 PM

The relevant lines are here:

const attrs = `${Object.entries(restProps)
.map(([k, _v]) => {
if (typeof _v === 'string' && _v.toLowerCase() === 'currentcolor') {
_v = currentColor
}
if (k === 'href' && type === 'image') {
return ` ${ATTRIBUTE_MAPPING[k] || k}="${cache.get(_v as string)[0]}"`
}
return ` ${ATTRIBUTE_MAPPING[k] || k}="${_v}"`
})
.join('')}`

@erxclau
Copy link

erxclau commented Nov 28, 2024

This mainly has to do with how resolveImageData is implemented in the image handler.

  1. Start by preprocessing, which handles image data resolution.
  2. On the first render, the function will fetch the image URL. While the function fetches, we also mark it down in a map of inflight requests (that maps the URL to the fetch promise).
  3. When the image is fetched, we generate a base64 version of the image and add it to the cache. If we need to resolve the same URL, we simply return the promise in the inflight requests map here.
  4. At this point, we properly generate the SVG element attributes.
  5. At the second render, we start by clearing the cache. However, we do not clear the inflight requests map. This results in an early return before we add the image data back to the cache.

I think the most straightforward solution would be to clear the map of inflight requests along with the cache clear.

@erxclau erxclau linked a pull request Nov 28, 2024 that will close this issue
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants