Conversation
`clonedHeaders` is a `Map` built by iterating `this.headers`, and `Headers`
lower-cases the names it yields — so the clone is already keyed `content-type`.
Setting `Content-Type` on it adds a *second* entry rather than replacing the
first, `Object.fromEntries` keeps both, and fetch joins them back together:
content-type: application/json, application/json
Every request with a body — every POST/PUT in the library — goes out that way.
Discogs rejects some of them, and the same shape breaks other servers too
(fastapi/fastapi#6682 (comment)).
`Content-Length` was duplicated the same way, but its value happens to be
identical to the one the runtime computes, so it was harmless.
Set both in lower case so they replace the entries already in the map.
`Buffer` is Node-only. In a browser bundle the identifier is not defined, so referencing it throws a `ReferenceError` on every request that carries a body — which is the remaining thing that stops a bundled Discojs from working in a browser after `allowUnsafeHeaders: false`. Setting it there would be pointless anyway: `Content-Length` is a forbidden header name in the fetch spec, so the user agent computes it and drops whatever the caller supplied. Guard on `typeof Buffer` so the header is only set on Node.
pyrogenic
marked this pull request as draft
August 23, 2026 00:49
pyrogenic
marked this pull request as ready for review
August 23, 2026 02:58
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
(Part 2 answers your review question about the
Bufferguard from #60.)1. Duplicate "Content-Type"
In
Fetcher.schedule:this.headersis aHeaders, andHeaderslower-cases the names it yields when iterated, so theMapalready has acontent-type. Setting'Content-Type'on aMapadds a second entry under a different key.Object.fromEntrieskeeps both, andfetchthen joins them:This would go out on every request that carries a body (every POST and PUT). Some servers reject it outright; it's the same shape as this FastAPI report.
The fix is to set both in lower case so they replace the entries already in the map.
Tests:
src/utils/fetch.headers.spec.tsmockscross-fetchand asserts (a) no header name issent twice under two casings, and (b)
content-typeis exactlyapplication/json. Both fail onmasterand pass with the fix, at each commit individually. They takeHeadersfromcross-fetchrather than the global so they work on older jest environments too.
2.
Content-LengthandBufferBufferis Node-only. In a browser bundle it's undefined, so referencing itthrows a
ReferenceErroron every request with a body. That's the other thing that stops abundled Discojs working in a browser after
allowUnsafeHeaders: false.Content-Lengthis aforbidden header name (the user agent computes it on its own), so guarding that set on
typeof Buffer !== 'undefined'lets the code work in a browser, which is now explained in the code.Tested running in browsers (Chrome + iOS Safari) against the live API with
allowUnsafeHeaders: falserunning POST routes (note edits and listing updates).