1
0
mirror of https://github.com/matrix-org/matrix-js-sdk.git synced 2025-07-30 04:23:07 +03:00

Include extraParams in all HTTP requests (#4860)

* attaching queryParams from client config in getUrl

Signed-off-by: rsb-tbg <69879226+rsb-tbg@users.noreply.github.com>

* 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 <69879226+rsb-tbg@users.noreply.github.com>

* added tests

Signed-off-by: rsb-tbg <69879226+rsb-tbg@users.noreply.github.com>

---------

Signed-off-by: rsb-tbg <69879226+rsb-tbg@users.noreply.github.com>
This commit is contained in:
rsb-tbg
2025-05-30 04:09:21 -05:00
committed by GitHub
parent 74f5efc4ef
commit 12a9875c46
5 changed files with 190 additions and 4 deletions

View File

@ -205,4 +205,109 @@ describe("MatrixClient opts", function () {
expect(res.event_id).toEqual("foo"); expect(res.event_id).toEqual("foo");
}); });
}); });
describe("with opts.queryParams", function () {
let client: MatrixClient;
let httpBackend: HttpBackend;
const userId = "@rsb-tbg:localhost";
beforeEach(function () {
httpBackend = new HttpBackend();
client = new MatrixClient({
fetchFn: httpBackend.fetchFn as typeof globalThis.fetch,
store: new MemoryStore() as IStore,
baseUrl: baseUrl,
userId: userId,
accessToken: accessToken,
queryParams: { user_id: userId },
});
});
afterEach(function () {
client.stopClient();
httpBackend.verifyNoOutstandingExpectation();
return httpBackend.stop();
});
it("should include queryParams in matrix server requests", async () => {
const eventId = "$test:event";
httpBackend
.when("PUT", "/txn1")
.check((req) => {
expect(req.path).toContain(`user_id=${encodeURIComponent(userId)}`);
return true;
})
.respond(200, {
event_id: eventId,
});
const [res] = await Promise.all([
client.sendTextMessage("!foo:bar", "test message", "txn1"),
httpBackend.flush("/txn1", 1),
]);
expect(res.event_id).toEqual(eventId);
});
it("should include queryParams in sync requests", async () => {
httpBackend
.when("GET", "/versions")
.check((req) => {
expect(req.path).toContain(`user_id=${encodeURIComponent(userId)}`);
return true;
})
.respond(200, {});
httpBackend
.when("GET", "/pushrules")
.check((req) => {
expect(req.path).toContain(`user_id=${encodeURIComponent(userId)}`);
return true;
})
.respond(200, {});
httpBackend
.when("POST", "/filter")
.check((req) => {
expect(req.path).toContain(`user_id=${encodeURIComponent(userId)}`);
return true;
})
.respond(200, { filter_id: "foo" });
httpBackend
.when("GET", "/sync")
.check((req) => {
expect(req.path).toContain(`user_id=${encodeURIComponent(userId)}`);
return true;
})
.respond(200, syncData);
client.startClient();
await httpBackend.flush("/versions", 1);
await httpBackend.flush("/pushrules", 1);
await httpBackend.flush("/filter", 1);
await Promise.all([httpBackend.flush("/sync", 1), utils.syncPromise(client)]);
});
it("should merge queryParams with request-specific params", async () => {
const eventId = "$test:event";
httpBackend
.when("PUT", "/txn1")
.check((req) => {
// Should contain both global queryParams and request-specific params
expect(req.path).toContain(`user_id=${encodeURIComponent(userId)}`);
return true;
})
.respond(200, {
event_id: eventId,
});
const [res] = await Promise.all([
client.sendTextMessage("!foo:bar", "test message", "txn1"),
httpBackend.flush("/txn1", 1),
]);
expect(res.event_id).toEqual(eventId);
});
});
}); });

View File

@ -521,6 +521,83 @@ describe("FetchHttpApi", () => {
describe("when fetch.opts.baseUrl does have a trailing slash", () => { describe("when fetch.opts.baseUrl does have a trailing slash", () => {
runTests(baseUrlWithTrailingSlash); runTests(baseUrlWithTrailingSlash);
}); });
describe("extraParams handling", () => {
const makeApiWithExtraParams = (extraParams: QueryDict): FetchHttpApi<any> => {
const fetchFn = jest.fn();
const emitter = new TypedEventEmitter<HttpApiEvent, HttpApiEventHandlerMap>();
return new FetchHttpApi(emitter, { baseUrl: localBaseUrl, prefix, fetchFn, extraParams });
};
const userId = "@rsb-tbg:localhost";
const encodedUserId = encodeURIComponent(userId);
it("should include extraParams in URL when no queryParams provided", () => {
const extraParams = { user_id: userId, version: "1.0" };
const api = makeApiWithExtraParams(extraParams);
const result = api.getUrl("/test");
expect(result.toString()).toBe(`${localBaseUrl}${prefix}/test?user_id=${encodedUserId}&version=1.0`);
});
it("should merge extraParams with queryParams", () => {
const extraParams = { user_id: userId, version: "1.0" };
const api = makeApiWithExtraParams(extraParams);
const queryParams = { userId: "123", filter: "active" };
const result = api.getUrl("/test", queryParams);
expect(result.searchParams.get("user_id")!).toBe(userId);
expect(result.searchParams.get("version")!).toBe("1.0");
expect(result.searchParams.get("userId")!).toBe("123");
expect(result.searchParams.get("filter")!).toBe("active");
});
it("should allow queryParams to override extraParams", () => {
const extraParams = { user_id: "@default:localhost", version: "1.0" };
const api = makeApiWithExtraParams(extraParams);
const queryParams = { user_id: "@override:localhost", userId: "123" };
const result = api.getUrl("/test", queryParams);
expect(result.searchParams.get("user_id")).toBe("@override:localhost");
expect(result.searchParams.get("version")!).toBe("1.0");
expect(result.searchParams.get("userId")!).toBe("123");
});
it("should handle empty extraParams", () => {
const extraParams = {};
const api = makeApiWithExtraParams(extraParams);
const queryParams = { userId: "123" };
const result = api.getUrl("/test", queryParams);
expect(result.searchParams.get("userId")!).toBe("123");
expect(result.searchParams.has("user_id")).toBe(false);
});
it("should work when extraParams is undefined", () => {
const fetchFn = jest.fn();
const emitter = new TypedEventEmitter<HttpApiEvent, HttpApiEventHandlerMap>();
const api = new FetchHttpApi(emitter, { baseUrl: localBaseUrl, prefix, fetchFn });
const queryParams = { userId: "123" };
const result = api.getUrl("/test", queryParams);
expect(result.searchParams.get("userId")!).toBe("123");
expect(result.toString()).toBe(`${localBaseUrl}${prefix}/test?userId=123`);
});
it("should work when queryParams is undefined", () => {
const extraParams = { user_id: userId, version: "1.0" };
const api = makeApiWithExtraParams(extraParams);
const result = api.getUrl("/test");
expect(result.searchParams.get("user_id")!).toBe(userId);
expect(result.toString()).toBe(`${localBaseUrl}${prefix}/test?user_id=${encodedUserId}&version=1.0`);
});
});
}); });
it("should not log query parameters", async () => { it("should not log query parameters", async () => {

View File

@ -360,7 +360,7 @@ export interface ICreateClientOpts {
* to all requests with this client. Useful for application services which require * to all requests with this client. Useful for application services which require
* `?user_id=`. * `?user_id=`.
*/ */
queryParams?: Record<string, string>; queryParams?: QueryDict;
/** /**
* Encryption key used for encrypting sensitive data (such as e2ee keys) in {@link ICreateClientOpts#cryptoStore}. * Encryption key used for encrypting sensitive data (such as e2ee keys) in {@link ICreateClientOpts#cryptoStore}.

View File

@ -379,9 +379,12 @@ export class FetchHttpApi<O extends IHttpOpts> {
? baseUrlWithFallback.slice(0, -1) ? baseUrlWithFallback.slice(0, -1)
: baseUrlWithFallback; : baseUrlWithFallback;
const url = new URL(baseUrlWithoutTrailingSlash + (prefix ?? this.opts.prefix) + path); const url = new URL(baseUrlWithoutTrailingSlash + (prefix ?? this.opts.prefix) + path);
if (queryParams) { // If there are any params, encode and append them to the URL.
encodeParams(queryParams, url.searchParams); if (this.opts.extraParams || queryParams) {
const mergedParams = { ...this.opts.extraParams, ...queryParams };
encodeParams(mergedParams, url.searchParams);
} }
return url; return url;
} }
} }

View File

@ -16,6 +16,7 @@ limitations under the License.
import { type MatrixError } from "./errors.ts"; import { type MatrixError } from "./errors.ts";
import { type Logger } from "../logger.ts"; import { type Logger } from "../logger.ts";
import { type QueryDict } from "../utils.ts";
export type Body = Record<string, any> | BodyInit; export type Body = Record<string, any> | BodyInit;
@ -52,7 +53,7 @@ export interface IHttpOpts {
baseUrl: string; baseUrl: string;
idBaseUrl?: string; idBaseUrl?: string;
prefix: string; prefix: string;
extraParams?: Record<string, string>; extraParams?: QueryDict;
accessToken?: string; accessToken?: string;
/** /**