Skip to content

Commit d769e5c

Browse files
author
Charly Nguyen
committed
Allow knocking rooms
Signed-off-by: Charly Nguyen <charly.nguyen@nordeck.net>
1 parent 61c0a49 commit d769e5c

File tree

3 files changed

+120
-1
lines changed

3 files changed

+120
-1
lines changed

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

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import { Mocked } from "jest-mock";
1919
import * as utils from "../test-utils/test-utils";
2020
import { CRYPTO_ENABLED, IStoredClientOpts, MatrixClient } from "../../src/client";
2121
import { MatrixEvent } from "../../src/models/event";
22-
import { Filter, MemoryStore, Method, Room, SERVICE_TYPES } from "../../src/matrix";
22+
import { Filter, KnockRoomOpts, MemoryStore, Method, Room, SERVICE_TYPES } from "../../src/matrix";
2323
import { TestClient } from "../TestClient";
2424
import { THREAD_RELATION_TYPE } from "../../src/models/thread";
2525
import { IFilterDefinition } from "../../src/filter";
@@ -205,6 +205,84 @@ describe("MatrixClient", function () {
205205
});
206206
});
207207

208+
describe("knockRoom", function () {
209+
const roomId = "!some-room-id:example.org";
210+
const reason = "some reason";
211+
const viaServers = "example.com";
212+
213+
type TestCase = [string, KnockRoomOpts];
214+
const testCases: TestCase[] = [
215+
["should knock a room", {}],
216+
["should knock a room for a reason", { reason }],
217+
["should knock a room via given servers", { viaServers }],
218+
["should knock a room for a reason via given servers", { reason, viaServers }],
219+
];
220+
221+
it.each(testCases)("%s", async (_, opts) => {
222+
httpBackend
223+
.when("POST", "/knock/" + encodeURIComponent(roomId))
224+
.check((request) => {
225+
expect(request.data).toEqual({ reason: opts.reason });
226+
expect(request.queryParams).toEqual({ server_name: opts.viaServers });
227+
})
228+
.respond(200, { room_id: roomId });
229+
230+
const prom = client.knockRoom(roomId, opts);
231+
await httpBackend.flushAllExpected();
232+
expect((await prom).room_id).toBe(roomId);
233+
});
234+
235+
it("should no-op if you've already knocked a room", function () {
236+
const room = new Room(roomId, client, userId);
237+
238+
client.fetchRoomEvent = () =>
239+
Promise.resolve({
240+
type: "test",
241+
content: {},
242+
});
243+
244+
room.addLiveEvents([
245+
utils.mkMembership({
246+
user: userId,
247+
room: roomId,
248+
mship: "knock",
249+
event: true,
250+
}),
251+
]);
252+
253+
httpBackend.verifyNoOutstandingRequests();
254+
store.storeRoom(room);
255+
client.knockRoom(roomId);
256+
httpBackend.verifyNoOutstandingRequests();
257+
});
258+
259+
describe("errors", function () {
260+
type TestCase = [number, { errcode: string; error?: string }, string];
261+
const testCases: TestCase[] = [
262+
[
263+
403,
264+
{ errcode: "M_FORBIDDEN", error: "You don't have permission to knock" },
265+
"[M_FORBIDDEN: MatrixError: [403] You don't have permission to knock]",
266+
],
267+
[
268+
500,
269+
{ errcode: "INTERNAL_SERVER_ERROR" },
270+
"[INTERNAL_SERVER_ERROR: MatrixError: [500] Unknown message]",
271+
],
272+
];
273+
274+
it.each(testCases)("should handle %s error", async (code, { errcode, error }, snapshot) => {
275+
httpBackend.when("POST", "/knock/" + encodeURIComponent(roomId)).respond(code, { errcode, error });
276+
277+
const prom = client.knockRoom(roomId);
278+
await Promise.all([
279+
httpBackend.flushAllExpected(),
280+
expect(prom).rejects.toMatchInlineSnapshot(snapshot),
281+
]);
282+
});
283+
});
284+
});
285+
208286
describe("getFilter", function () {
209287
const filterId = "f1lt3r1d";
210288

src/@types/requests.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,18 @@ export interface IJoinRoomOpts {
4545
viaServers?: string[];
4646
}
4747

48+
export interface KnockRoomOpts {
49+
/**
50+
* The reason for the knock.
51+
*/
52+
reason?: string;
53+
54+
/**
55+
* The server names to try and knock through in addition to those that are automatically chosen.
56+
*/
57+
viaServers?: string | string[];
58+
}
59+
4860
export interface IRedactOpts {
4961
reason?: string;
5062
/**

src/client.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ import {
134134
ITagsResponse,
135135
IStatusResponse,
136136
IAddThreePidBody,
137+
KnockRoomOpts,
137138
} from "./@types/requests";
138139
import {
139140
EventType,
@@ -4158,6 +4159,34 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
41584159
return syncRoom;
41594160
}
41604161

4162+
/**
4163+
* Knock a room. If you have already knocked the room, this will no-op.
4164+
* @param roomIdOrAlias - The room ID or room alias to knock.
4165+
* @param opts - Options when knocking the room.
4166+
* @returns Promise which resolves: `{room_id: {string}}`
4167+
* @returns Rejects: with an error response.
4168+
*/
4169+
public knockRoom(roomIdOrAlias: string, opts: KnockRoomOpts = {}): Promise<{ room_id: string }> {
4170+
const room = this.getRoom(roomIdOrAlias);
4171+
if (room?.hasMembershipState(this.credentials.userId!, "knock")) {
4172+
return Promise.resolve({ room_id: room.roomId });
4173+
}
4174+
4175+
const path = utils.encodeUri("/knock/$roomIdOrAlias", { $roomIdOrAlias: roomIdOrAlias });
4176+
4177+
const queryParams: Record<string, string | string[]> = {};
4178+
if (opts.viaServers) {
4179+
queryParams.server_name = opts.viaServers;
4180+
}
4181+
4182+
const body: Record<string, string> = {};
4183+
if (opts.reason) {
4184+
body.reason = opts.reason;
4185+
}
4186+
4187+
return this.http.authedRequest(Method.Post, path, queryParams, body);
4188+
}
4189+
41614190
/**
41624191
* Resend an event. Will also retry any to-device messages waiting to be sent.
41634192
* @param event - The event to resend.

0 commit comments

Comments
 (0)