1
0
mirror of https://github.com/matrix-org/matrix-js-sdk.git synced 2025-08-06 12:02:40 +03:00

Simplify MatrixClient::setPowerLevel API (#3570)

* Simplify `MatrixClient::setPowerLevel` API

While making it more resilient to causing issues like nuking room state

* Handle edge case

* Fix tests

* Add test coverage
This commit is contained in:
Michael Telatynski
2023-07-11 08:26:30 +01:00
committed by GitHub
parent 5df4ebaada
commit d2b782a2f5
2 changed files with 110 additions and 23 deletions

View File

@@ -1386,14 +1386,11 @@ describe("MatrixClient", function () {
expectation: {}, expectation: {},
}, },
])("should modify power levels of $userId correctly", async ({ userId, powerLevel, expectation }) => { ])("should modify power levels of $userId correctly", async ({ userId, powerLevel, expectation }) => {
const event = { httpBackend!.when("GET", "/state/m.room.power_levels/").respond(200, {
getType: () => "m.room.power_levels", users: {
getContent: () => ({ "alice@localhost": 50,
users: { },
"alice@localhost": 50, });
},
}),
} as MatrixEvent;
httpBackend! httpBackend!
.when("PUT", "/state/m.room.power_levels") .when("PUT", "/state/m.room.power_levels")
@@ -1402,7 +1399,76 @@ describe("MatrixClient", function () {
}) })
.respond(200, {}); .respond(200, {});
const prom = client!.setPowerLevel("!room_id:server", userId, powerLevel, event); const prom = client!.setPowerLevel("!room_id:server", userId, powerLevel);
await httpBackend!.flushAllExpected();
await prom;
});
it("should use power level from room state if available", async () => {
client!.clientRunning = true;
client!.isInitialSyncComplete = () => true;
const room = new Room("!room_id:server", client!, client!.getUserId()!);
room.currentState.events.set("m.room.power_levels", new Map());
room.currentState.events.get("m.room.power_levels")!.set(
"",
new MatrixEvent({
type: "m.room.power_levels",
state_key: "",
content: {
users: {
"@bob:localhost": 50,
},
},
}),
);
client!.getRoom = () => room;
httpBackend!
.when("PUT", "/state/m.room.power_levels")
.check((req) => {
expect(req.data).toStrictEqual({
users: {
"@bob:localhost": 50,
[userId]: 42,
},
});
})
.respond(200, {});
const prom = client!.setPowerLevel("!room_id:server", userId, 42);
await httpBackend!.flushAllExpected();
await prom;
});
it("should throw error if state API errors", async () => {
httpBackend!.when("GET", "/state/m.room.power_levels/").respond(500, {
errcode: "ERR_DERP",
});
const prom = client!.setPowerLevel("!room_id:server", userId, 42);
await Promise.all([
expect(prom).rejects.toMatchInlineSnapshot(`[ERR_DERP: MatrixError: [500] Unknown message]`),
httpBackend!.flushAllExpected(),
]);
});
it("should not throw error if /state/ API returns M_NOT_FOUND", async () => {
httpBackend!.when("GET", "/state/m.room.power_levels/").respond(404, {
errcode: "M_NOT_FOUND",
});
httpBackend!
.when("PUT", "/state/m.room.power_levels")
.check((req) => {
expect(req.data).toStrictEqual({
users: {
[userId]: 42,
},
});
})
.respond(200, {});
const prom = client!.setPowerLevel("!room_id:server", userId, 42);
await httpBackend!.flushAllExpected(); await httpBackend!.flushAllExpected();
await prom; await prom;
}); });

View File

@@ -111,7 +111,7 @@ import * as ContentHelpers from "./content-helpers";
import { CrossSigningInfo, DeviceTrustLevel, ICacheCallbacks, UserTrustLevel } from "./crypto/CrossSigning"; import { CrossSigningInfo, DeviceTrustLevel, ICacheCallbacks, UserTrustLevel } from "./crypto/CrossSigning";
import { Room, NotificationCountType, RoomEvent, RoomEventHandlerMap, RoomNameState } from "./models/room"; import { Room, NotificationCountType, RoomEvent, RoomEventHandlerMap, RoomNameState } from "./models/room";
import { RoomMemberEvent, RoomMemberEventHandlerMap } from "./models/room-member"; import { RoomMemberEvent, RoomMemberEventHandlerMap } from "./models/room-member";
import { RoomStateEvent, RoomStateEventHandlerMap } from "./models/room-state"; import { IPowerLevelsContent, RoomStateEvent, RoomStateEventHandlerMap } from "./models/room-state";
import { import {
IAddThreePidOnlyBody, IAddThreePidOnlyBody,
IBindThreePidBody, IBindThreePidBody,
@@ -4256,24 +4256,48 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
/** /**
* Set a power level to one or multiple users. * Set a power level to one or multiple users.
* Will apply changes atop of current power level event from local state if running & synced, falling back
* to fetching latest from the `/state/` API.
* @param roomId - the room to update power levels in
* @param userId - the ID of the user or users to update power levels of
* @param powerLevel - the numeric power level to update given users to
* @param event - deprecated and no longer used.
* @returns Promise which resolves: to an ISendEventResponse object * @returns Promise which resolves: to an ISendEventResponse object
* @returns Rejects: with an error response. * @returns Rejects: with an error response.
*/ */
public setPowerLevel( public async setPowerLevel(
roomId: string, roomId: string,
userId: string | string[], userId: string | string[],
powerLevel: number | undefined, powerLevel: number | undefined,
event: MatrixEvent | null, /**
* @deprecated no longer needed, unused.
*/
event?: MatrixEvent | null,
): Promise<ISendEventResponse> { ): Promise<ISendEventResponse> {
let content = { let content: IPowerLevelsContent | undefined;
users: {} as Record<string, number>, if (this.clientRunning && this.isInitialSyncComplete()) {
}; content = this.getRoom(roomId)?.currentState?.getStateEvents(EventType.RoomPowerLevels, "")?.getContent();
if (event?.getType() === EventType.RoomPowerLevels) { }
// take a copy of the content to ensure we don't corrupt if (!content) {
// existing client state with a failed power level change try {
content = utils.deepCopy(event.getContent()); content = await this.getStateEvent(roomId, EventType.RoomPowerLevels, "");
} catch (e) {
// It is possible for a Matrix room to not have a power levels event
if (e instanceof MatrixError && e.errcode === "M_NOT_FOUND") {
content = {};
} else {
throw e;
}
}
} }
// take a copy of the content to ensure we don't corrupt
// existing client state with a failed power level change
content = utils.deepCopy(content);
if (!content?.users) {
content.users = {};
}
const users = Array.isArray(userId) ? userId : [userId]; const users = Array.isArray(userId) ? userId : [userId];
for (const user of users) { for (const user of users) {
if (powerLevel == null) { if (powerLevel == null) {
@@ -4283,10 +4307,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
} }
} }
const path = utils.encodeUri("/rooms/$roomId/state/m.room.power_levels", { return this.sendStateEvent(roomId, EventType.RoomPowerLevels, content, "");
$roomId: roomId,
});
return this.http.authedRequest(Method.Put, path, undefined, content);
} }
/** /**