From 2af1a2ece843176ce559547bf674bb51ca0345a1 Mon Sep 17 00:00:00 2001 From: breken-ai <312387581+breken-ai@users.noreply.github.com> Date: Fri, 25 Sep 2026 20:40:23 -0700 Subject: [PATCH] fix(http): keep existing query on web params The web CapacitorHttp request joined params with '?' even when the url already had a query string, producing url?a=1?b=2. Use '&' in that case, matching Android and iOS. Co-Authored-By: Claude Opus 5.5 (1M context) --- core/src/core-plugins.ts | 3 ++- core/src/tests/http.spec.ts | 42 +++++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 core/src/tests/http.spec.ts diff --git a/core/src/core-plugins.ts b/core/src/core-plugins.ts index a1efe9c901..2ed3d0abce 100644 --- a/core/src/core-plugins.ts +++ b/core/src/core-plugins.ts @@ -404,7 +404,8 @@ export class CapacitorHttpPluginWeb extends WebPlugin implements CapacitorHttpPl async request(options: HttpOptions): Promise { const requestInit = buildRequestInit(options, options.webFetchExtra); const urlParams = buildUrlParams(options.params, options.shouldEncodeUrlParams); - const url = urlParams ? `${options.url}?${urlParams}` : options.url; + const separator = options.url.includes('?') ? '&' : '?'; + const url = urlParams ? `${options.url}${separator}${urlParams}` : options.url; const response = await fetch(url, requestInit); const contentType = response.headers.get('content-type') || ''; diff --git a/core/src/tests/http.spec.ts b/core/src/tests/http.spec.ts new file mode 100644 index 0000000000..290c2d64a8 --- /dev/null +++ b/core/src/tests/http.spec.ts @@ -0,0 +1,42 @@ +/** + * @jest-environment jsdom + */ + +import { CapacitorHttpPluginWeb } from '../core-plugins'; + +describe('CapacitorHttpPluginWeb', () => { + const originalFetch = (globalThis as any).fetch; + let fetchMock: jest.Mock; + + beforeEach(() => { + fetchMock = jest.fn(async (url: string) => ({ + ok: true, + status: 200, + url, + headers: { + get: () => 'text/plain', + forEach: () => undefined, + }, + text: async () => 'ok', + })); + (globalThis as any).fetch = fetchMock; + }); + + afterEach(() => { + (globalThis as any).fetch = originalFetch; + }); + + it('appends params to a url without a query string', async () => { + const http = new CapacitorHttpPluginWeb(); + await http.get({ url: 'https://example.com/api', params: { b: '2' } }); + + expect(fetchMock.mock.calls[0][0]).toEqual('https://example.com/api?b=2'); + }); + + it('keeps the existing query string when adding params', async () => { + const http = new CapacitorHttpPluginWeb(); + await http.get({ url: 'https://example.com/api?a=1', params: { b: '2' } }); + + expect(fetchMock.mock.calls[0][0]).toEqual('https://example.com/api?a=1&b=2'); + }); +});