cloudflare / cloudflare/workerd
IMAGES.input(ArrayBuffer)` is treated as `text()` and throws `Cannot read properties of undefined (reading 'font')
- Dominant language
- C++
- Stars
- 8.7k
- Forks
- 739
- Avg merge
- 2d 20h
- Merged PRs (30d)
- 174
Description
## Summary
`env.IMAGES.input(arrayBuffer).transform(...).output(...)` throws in production:
```
TypeError: Cannot read properties of undefined (reading 'font')
```
JPEG/PNG from `fetch().arrayBuffer()`, `R2Object.arrayBuffer()`, and `File.arrayBuffer()` all fail. `new Blob([buffer]).stream()` works.
This is not a bad image. The `text()` type guard treats every non-`ReadableStream` as a `TextRasterize`.
## Where
[`src/cloudflare/internal/images-api.ts` @ `6552d999`](https://github.com/cloudflare/workerd/blob/6552d999c79ef651b5463c6d692d065a8ce6f859/src/cloudflare/internal/images-api.ts)
Guard (too wide):
https://github.com/cloudflare/workerd/blob/6552d999c79ef651b5463c6d692d065a8ce6f859/src/cloudflare/internal/images-api.ts#L16-L18
```ts
function isTextSource(source: ImageSource): source is TextRasterize {
return !(source instanceof ReadableStream);
}
```
Crash:
https://github.com/cloudflare/workerd/blob/6552d999c79ef651b5463c6d692d065a8ce6f859/src/cloudflare/internal/images-api.ts#L20-L24
```ts
function serializeTextSource(source: TextRasterize): string {
const obj = {
text: source.content,
font: source.options.font, // ArrayBuffer → options === undefined
};
```
Called from `output()`:
https://github.com/cloudflare/workerd/blob/6552d999c79ef651b5463c6d692d065a8ce6f859/src/cloudflare/internal/images-api.ts#L151-L153
`text()` is the only correct constructor for that shape:
https://github.com/cloudflare/workerd/blob/6552d999c79ef651b5463c6d692d065a8ce6f859/src/cloudflare/internal/images-api.ts#L417-L419
Types already say `ImageSource = ReadableStream | TextRasterize` and `input(stream: ReadableStream)`:
https://github.com/cloudflare/workerd/blob/6552d999c79ef651b5463c6d692d065a8ce6f859/types/defines/images.d.ts#L36
https://github.com/cloudflare/workerd/blob/6552d999c79ef651b5463c6d692d065a8ce6f859/types/defines/images.d.ts#L310-L320
JS still accepts an `ArrayBuffer` at runtime. Same guard is used for draw overlays ([#L219](https://github.com/cloudflare/workerd/blob/6552d999c79ef651b5463c6d692d065a8ce6f859/src/cloudflare/internal/images-api.ts#L219)).
## Why this is a regression
Docs still tell people to pass bytes:
- https://developers.cloudflare.com/images/optimization/binding/#transformoptions
`const bytes = await env.IMAGES.hosted.image("IMAGE_ID").bytes();` then `env.IMAGES.input(bytes)`
- https://blog.cloudflare.com/improve-your-media-pipelines-with-the-images-binding-for-cloudflare-workers/
`const fileBuffer = await file.arrayBuffer();` then `env.IMAGES.input(fileBuffer)`
`hosted.upload()` still accepts `ArrayBuffer` ([images.d.ts#L270](https://github.com/cloudflare/workerd/blob/6552d999c79ef651b5463c6d692d065a8ce6f859/types/defines/images.d.ts#L270)). `input()` used to as well.
## Repro
```js
export default {
async fetch(request, env) {
const origin = await fetch("https://cf-assets.www.cloudflare.com/dzlvafdwdttg/6Lx8xYhQ3h4bS3kYxYzYzY/x/cloudflare.png");
const buf = await origin.arrayBuffer();
return (
await env.IMAGES.input(buf).transform({ width: 200 }).output({ format: "image/webp" })
).response();
},
};
```
Expected: WebP.
Actual: `TypeError: Cannot read properties of undefined (reading 'font')`.
```js
env.IMAGES.input(new Blob([buf]).stream()) // works
```
## Suggested fix
Detect text by shape, not “not a stream”:
```ts
function isTextSource(source: ImageSource): source is TextRasterize {
return (
typeof source === "object" &&
source !== null &&
"content" in source &&
"options" in source &&
typeof source.content === "string"
);
}
```
And/or wrap bytes in `input()` so the blog + R2/`fetch().arrayBuffer()` keep working:
```ts
input(image, options) {
const stream =
image instanceof ReadableStream ? image : new Blob([image]).stream();
// existing decode / ImageTransformerImpl
}
```
If you reject bytes, throw `IMAGES_TRANSFORM_ERROR` (“`input()` expects a ReadableStream”) instead of reading `.font`.
## Workaround
`env.IMAGES.input(new Blob([buffer], { type }).stream())`.
Contributor guide
Research direction
Start in src/cloudflare/internal/images-api.ts with isTextSource(), serializeTextSource(), input(), and output(), then compare the ImageSource and input() declarations in types/defines/images.d.ts. Reproduce the ArrayBuffer path from the issue and verify that it is no longer treated as TextRasterize; completion should preserve the documented byte-input behavior or return the intended explicit input error.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript, typescript
- Domain
- api, backend
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 68/100