diff --git a/CHANGELOG.md b/CHANGELOG.md index 188b038f..e2f0c3f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ### Fixed * [#766](https://github.com/shlinkio/shlink-web-client/issues/766) Fixed visits query being lost when switching between sub-sections. +* [#765](https://github.com/shlinkio/shlink-web-client/issues/765) Added missing `"Content-Type": "application/json"` to requests with payload, making older Shlink versions fail. ## [3.8.1] - 2022-12-06 diff --git a/src/common/services/HttpClient.ts b/src/common/services/HttpClient.ts index af8aedf3..9a07cfea 100644 --- a/src/common/services/HttpClient.ts +++ b/src/common/services/HttpClient.ts @@ -1,10 +1,27 @@ import { Fetch } from '../../utils/types'; +const applicationJsonHeader = { 'Content-Type': 'application/json' }; +const withJsonContentType = (options?: RequestInit): RequestInit | undefined => { + if (!options?.body) { + return options; + } + + return options ? { + ...options, + headers: { + ...(options.headers ?? {}), + ...applicationJsonHeader, + }, + } : { + headers: applicationJsonHeader, + }; +}; + export class HttpClient { constructor(private readonly fetch: Fetch) {} public readonly fetchJson = (url: string, options?: RequestInit): Promise => - this.fetch(url, options).then(async (resp) => { + this.fetch(url, withJsonContentType(options)).then(async (resp) => { const json = await resp.json(); if (!resp.ok) { @@ -15,7 +32,7 @@ export class HttpClient { }); public readonly fetchEmpty = (url: string, options?: RequestInit): Promise => - this.fetch(url, options).then(async (resp) => { + this.fetch(url, withJsonContentType(options)).then(async (resp) => { if (!resp.ok) { throw await resp.json(); } diff --git a/test/common/services/HttpClient.test.ts b/test/common/services/HttpClient.test.ts index b894cd32..ea01faf5 100644 --- a/test/common/services/HttpClient.test.ts +++ b/test/common/services/HttpClient.test.ts @@ -14,13 +14,39 @@ describe('HttpClient', () => { await expect(httpClient.fetchJson('')).rejects.toEqual(theError); }); - it('return json on failure', async () => { + it.each([ + [undefined], + [{}], + [{ body: undefined }], + [{ body: '' }], + ])('return json on failure', async (options) => { const theJson = { foo: 'bar' }; fetch.mockResolvedValue({ json: () => theJson, ok: true }); - const result = await httpClient.fetchJson(''); + const result = await httpClient.fetchJson('the_url', options); expect(result).toEqual(theJson); + expect(fetch).toHaveBeenCalledWith('the_url', options); + }); + + it.each([ + [{ body: 'the_body' }], + [{ + body: 'the_body', + headers: { + 'Content-Type': 'text/plain', + }, + }], + ])('forwards JSON content-type when appropriate', async (options) => { + const theJson = { foo: 'bar' }; + fetch.mockResolvedValue({ json: () => theJson, ok: true }); + + const result = await httpClient.fetchJson('the_url', options); + + expect(result).toEqual(theJson); + expect(fetch).toHaveBeenCalledWith('the_url', expect.objectContaining({ + headers: { 'Content-Type': 'application/json' }, + })); }); });