Skip to content

Commit 12a9875

Browse files
authored
Include extraParams in all HTTP requests (#4860)
* attaching queryParams from client config in getUrl Signed-off-by: rsb-tbg <[email protected]> * changed client queryParams to QueryDict for consistency and now merging both sets of params in getUrl if one or both exist Signed-off-by: rsb-tbg <[email protected]> * added tests Signed-off-by: rsb-tbg <[email protected]> --------- Signed-off-by: rsb-tbg <[email protected]>
1 parent 74f5efc commit 12a9875

File tree

5 files changed

+190
-4
lines changed

5 files changed

+190
-4
lines changed

spec/integ/matrix-client-opts.spec.ts

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,4 +205,109 @@ describe("MatrixClient opts", function () {
205205
expect(res.event_id).toEqual("foo");
206206
});
207207
});
208+
209+
describe("with opts.queryParams", function () {
210+
let client: MatrixClient;
211+
let httpBackend: HttpBackend;
212+
const userId = "@rsb-tbg:localhost";
213+
214+
beforeEach(function () {
215+
httpBackend = new HttpBackend();
216+
client = new MatrixClient({
217+
fetchFn: httpBackend.fetchFn as typeof globalThis.fetch,
218+
store: new MemoryStore() as IStore,
219+
baseUrl: baseUrl,
220+
userId: userId,
221+
accessToken: accessToken,
222+
queryParams: { user_id: userId },
223+
});
224+
});
225+
226+
afterEach(function () {
227+
client.stopClient();
228+
httpBackend.verifyNoOutstandingExpectation();
229+
return httpBackend.stop();
230+
});
231+
232+
it("should include queryParams in matrix server requests", async () => {
233+
const eventId = "$test:event";
234+
httpBackend
235+
.when("PUT", "/txn1")
236+
.check((req) => {
237+
expect(req.path).toContain(`user_id=${encodeURIComponent(userId)}`);
238+
return true;
239+
})
240+
.respond(200, {
241+
event_id: eventId,
242+
});
243+
244+
const [res] = await Promise.all([
245+
client.sendTextMessage("!foo:bar", "test message", "txn1"),
246+
httpBackend.flush("/txn1", 1),
247+
]);
248+
249+
expect(res.event_id).toEqual(eventId);
250+
});
251+
252+
it("should include queryParams in sync requests", async () => {
253+
httpBackend
254+
.when("GET", "/versions")
255+
.check((req) => {
256+
expect(req.path).toContain(`user_id=${encodeURIComponent(userId)}`);
257+
return true;
258+
})
259+
.respond(200, {});
260+
261+
httpBackend
262+
.when("GET", "/pushrules")
263+
.check((req) => {
264+
expect(req.path).toContain(`user_id=${encodeURIComponent(userId)}`);
265+
return true;
266+
})
267+
.respond(200, {});
268+
269+
httpBackend
270+
.when("POST", "/filter")
271+
.check((req) => {
272+
expect(req.path).toContain(`user_id=${encodeURIComponent(userId)}`);
273+
return true;
274+
})
275+
.respond(200, { filter_id: "foo" });
276+
277+
httpBackend
278+
.when("GET", "/sync")
279+
.check((req) => {
280+
expect(req.path).toContain(`user_id=${encodeURIComponent(userId)}`);
281+
return true;
282+
})
283+
.respond(200, syncData);
284+
285+
client.startClient();
286+
await httpBackend.flush("/versions", 1);
287+
await httpBackend.flush("/pushrules", 1);
288+
await httpBackend.flush("/filter", 1);
289+
await Promise.all([httpBackend.flush("/sync", 1), utils.syncPromise(client)]);
290+
});
291+
292+
it("should merge queryParams with request-specific params", async () => {
293+
const eventId = "$test:event";
294+
httpBackend
295+
.when("PUT", "/txn1")
296+
.check((req) => {
297+
// Should contain both global queryParams and request-specific params
298+
expect(req.path).toContain(`user_id=${encodeURIComponent(userId)}`);
299+
return true;
300+
})
301+
.respond(200, {
302+
event_id: eventId,
303+
});
304+
305+
const [res] = await Promise.all([
306+
client.sendTextMessage("!foo:bar", "test message", "txn1"),
307+
httpBackend.flush("/txn1", 1),
308+
]);
309+
310+
expect(res.event_id).toEqual(eventId);
311+
});
312+
});
208313
});

spec/unit/http-api/fetch.spec.ts

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -521,6 +521,83 @@ describe("FetchHttpApi", () => {
521521
describe("when fetch.opts.baseUrl does have a trailing slash", () => {
522522
runTests(baseUrlWithTrailingSlash);
523523
});
524+
525+
describe("extraParams handling", () => {
526+
const makeApiWithExtraParams = (extraParams: QueryDict): FetchHttpApi<any> => {
527+
const fetchFn = jest.fn();
528+
const emitter = new TypedEventEmitter<HttpApiEvent, HttpApiEventHandlerMap>();
529+
return new FetchHttpApi(emitter, { baseUrl: localBaseUrl, prefix, fetchFn, extraParams });
530+
};
531+
532+
const userId = "@rsb-tbg:localhost";
533+
const encodedUserId = encodeURIComponent(userId);
534+
535+
it("should include extraParams in URL when no queryParams provided", () => {
536+
const extraParams = { user_id: userId, version: "1.0" };
537+
const api = makeApiWithExtraParams(extraParams);
538+
539+
const result = api.getUrl("/test");
540+
expect(result.toString()).toBe(`${localBaseUrl}${prefix}/test?user_id=${encodedUserId}&version=1.0`);
541+
});
542+
543+
it("should merge extraParams with queryParams", () => {
544+
const extraParams = { user_id: userId, version: "1.0" };
545+
const api = makeApiWithExtraParams(extraParams);
546+
547+
const queryParams = { userId: "123", filter: "active" };
548+
const result = api.getUrl("/test", queryParams);
549+
550+
expect(result.searchParams.get("user_id")!).toBe(userId);
551+
expect(result.searchParams.get("version")!).toBe("1.0");
552+
expect(result.searchParams.get("userId")!).toBe("123");
553+
expect(result.searchParams.get("filter")!).toBe("active");
554+
});
555+
556+
it("should allow queryParams to override extraParams", () => {
557+
const extraParams = { user_id: "@default:localhost", version: "1.0" };
558+
const api = makeApiWithExtraParams(extraParams);
559+
560+
const queryParams = { user_id: "@override:localhost", userId: "123" };
561+
const result = api.getUrl("/test", queryParams);
562+
563+
expect(result.searchParams.get("user_id")).toBe("@override:localhost");
564+
expect(result.searchParams.get("version")!).toBe("1.0");
565+
expect(result.searchParams.get("userId")!).toBe("123");
566+
});
567+
568+
it("should handle empty extraParams", () => {
569+
const extraParams = {};
570+
const api = makeApiWithExtraParams(extraParams);
571+
572+
const queryParams = { userId: "123" };
573+
const result = api.getUrl("/test", queryParams);
574+
575+
expect(result.searchParams.get("userId")!).toBe("123");
576+
expect(result.searchParams.has("user_id")).toBe(false);
577+
});
578+
579+
it("should work when extraParams is undefined", () => {
580+
const fetchFn = jest.fn();
581+
const emitter = new TypedEventEmitter<HttpApiEvent, HttpApiEventHandlerMap>();
582+
const api = new FetchHttpApi(emitter, { baseUrl: localBaseUrl, prefix, fetchFn });
583+
584+
const queryParams = { userId: "123" };
585+
const result = api.getUrl("/test", queryParams);
586+
587+
expect(result.searchParams.get("userId")!).toBe("123");
588+
expect(result.toString()).toBe(`${localBaseUrl}${prefix}/test?userId=123`);
589+
});
590+
591+
it("should work when queryParams is undefined", () => {
592+
const extraParams = { user_id: userId, version: "1.0" };
593+
const api = makeApiWithExtraParams(extraParams);
594+
595+
const result = api.getUrl("/test");
596+
597+
expect(result.searchParams.get("user_id")!).toBe(userId);
598+
expect(result.toString()).toBe(`${localBaseUrl}${prefix}/test?user_id=${encodedUserId}&version=1.0`);
599+
});
600+
});
524601
});
525602

526603
it("should not log query parameters", async () => {

src/client.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -360,7 +360,7 @@ export interface ICreateClientOpts {
360360
* to all requests with this client. Useful for application services which require
361361
* `?user_id=`.
362362
*/
363-
queryParams?: Record<string, string>;
363+
queryParams?: QueryDict;
364364

365365
/**
366366
* Encryption key used for encrypting sensitive data (such as e2ee keys) in {@link ICreateClientOpts#cryptoStore}.

src/http-api/fetch.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -379,9 +379,12 @@ export class FetchHttpApi<O extends IHttpOpts> {
379379
? baseUrlWithFallback.slice(0, -1)
380380
: baseUrlWithFallback;
381381
const url = new URL(baseUrlWithoutTrailingSlash + (prefix ?? this.opts.prefix) + path);
382-
if (queryParams) {
383-
encodeParams(queryParams, url.searchParams);
382+
// If there are any params, encode and append them to the URL.
383+
if (this.opts.extraParams || queryParams) {
384+
const mergedParams = { ...this.opts.extraParams, ...queryParams };
385+
encodeParams(mergedParams, url.searchParams);
384386
}
387+
385388
return url;
386389
}
387390
}

src/http-api/interface.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ limitations under the License.
1616

1717
import { type MatrixError } from "./errors.ts";
1818
import { type Logger } from "../logger.ts";
19+
import { type QueryDict } from "../utils.ts";
1920

2021
export type Body = Record<string, any> | BodyInit;
2122

@@ -52,7 +53,7 @@ export interface IHttpOpts {
5253
baseUrl: string;
5354
idBaseUrl?: string;
5455
prefix: string;
55-
extraParams?: Record<string, string>;
56+
extraParams?: QueryDict;
5657

5758
accessToken?: string;
5859
/**

0 commit comments

Comments
 (0)