sindresorhus / ky

🌳 Tiny & elegant JavaScript HTTP client based on the browser Fetch API
MIT License
11.82k stars 341 forks source link

New request in afterResponse hook with formData content #594

Open jeanmatthieud opened 2 weeks ago

jeanmatthieud commented 2 weeks ago

Hi,

I tried to upload some content with the FormData Web API and Ky (latest release).

Something like this:

const formData = new FormData();
formData.append('title', title);
formData.append('validityDate', validityDate);
formData.append('uploadFile[file]', new Blob([fileContent]));

return await api.post(`participant/${participantToken}/document`, { body: formData }).json<Document[]>();

I also have a custom ky instance, named in my example "api". This instance is based on the afterResponseHook example, to manage API authentication.

afterResponse: [
  async (request, options, response) => {
    if (response.status === 401) {
      // Get a fresh token
      const token = await ky('https://example.com/token').text();

      // Retry with the token
      request.headers.set('Authorization', `token ${token}`);

      return ky(request, options);
    }
  }
]

I discovered that the call to ky(request, options) doesn't regenerate the "content-type" header, which creates a boundary mismatch between the header and the request content.

I looked in the ky source code, and it seems that a patch already exists, but only for requests with searchParams, which is not my case:

https://github.com/sindresorhus/ky/blob/585ebcb80545d784b31033e5a70326a0eb202468/source/core/Ky.ts#L194

// To provide correct form boundary, Content-Type header should be deleted each time when new Request instantiated from another one
if (
    ((supportsFormData && this._options.body instanceof globalThis.FormData)
        || this._options.body instanceof URLSearchParams) && !(this._options.headers && (this._options.headers as Record<string, string>)['content-type'])
) {
    this.request.headers.delete('content-type');
}

https://github.com/sindresorhus/ky/blob/585ebcb80545d784b31033e5a70326a0eb202468/source/core/Ky.ts#L184

sholladay commented 1 week ago

A quick, non-ideal workaround on your end might be to change ky(request, options) to just ky(request). Does that solve it?

As for a more permanent fix, perhaps we should move the code for deleting content-type out of the searchParams condition. Hopefully that doesn't break anything...