diff --git a/spec/integ/matrix-client-methods.spec.ts b/spec/integ/matrix-client-methods.spec.ts index 8bf57124d83..da417b8c22f 100644 --- a/spec/integ/matrix-client-methods.spec.ts +++ b/spec/integ/matrix-client-methods.spec.ts @@ -1386,23 +1386,89 @@ describe("MatrixClient", function () { expectation: {}, }, ])("should modify power levels of $userId correctly", async ({ userId, powerLevel, expectation }) => { - const event = { - getType: () => "m.room.power_levels", - getContent: () => ({ - users: { - "alice@localhost": 50, + httpBackend!.when("GET", "/state/m.room.power_levels/").respond(200, { + users: { + "alice@localhost": 50, + }, + }); + + httpBackend! + .when("PUT", "/state/m.room.power_levels") + .check((req) => { + expect(req.data.users).toStrictEqual(expectation); + }) + .respond(200, {}); + + 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, + }, }, }), - } as MatrixEvent; + ); + client!.getRoom = () => room; httpBackend! .when("PUT", "/state/m.room.power_levels") .check((req) => { - expect(req.data.users).toStrictEqual(expectation); + 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, powerLevel, event); + const prom = client!.setPowerLevel("!room_id:server", userId, 42); await httpBackend!.flushAllExpected(); await prom; }); diff --git a/src/client.ts b/src/client.ts index 87464cf640e..9d9e1d3aa19 100644 --- a/src/client.ts +++ b/src/client.ts @@ -111,7 +111,7 @@ import * as ContentHelpers from "./content-helpers"; import { CrossSigningInfo, DeviceTrustLevel, ICacheCallbacks, UserTrustLevel } from "./crypto/CrossSigning"; import { Room, NotificationCountType, RoomEvent, RoomEventHandlerMap, RoomNameState } from "./models/room"; import { RoomMemberEvent, RoomMemberEventHandlerMap } from "./models/room-member"; -import { RoomStateEvent, RoomStateEventHandlerMap } from "./models/room-state"; +import { IPowerLevelsContent, RoomStateEvent, RoomStateEventHandlerMap } from "./models/room-state"; import { IAddThreePidOnlyBody, IBindThreePidBody, @@ -4256,24 +4256,48 @@ export class MatrixClient extends TypedEventEmitter { - let content = { - users: {} as Record, - }; - if (event?.getType() === EventType.RoomPowerLevels) { - // take a copy of the content to ensure we don't corrupt - // existing client state with a failed power level change - content = utils.deepCopy(event.getContent()); + let content: IPowerLevelsContent | undefined; + if (this.clientRunning && this.isInitialSyncComplete()) { + content = this.getRoom(roomId)?.currentState?.getStateEvents(EventType.RoomPowerLevels, "")?.getContent(); + } + if (!content) { + try { + 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]; for (const user of users) { if (powerLevel == null) { @@ -4283,10 +4307,7 @@ export class MatrixClient extends TypedEventEmitter