Fix empty string to room compatibility trick to only apply to m.call (#5172)
* Fix empty string to room compatibility trick to only apply to m.call * add logging * fix linter * Add tests * limit logging.
This commit is contained in:
@@ -158,10 +158,35 @@ describe("CallMembership", () => {
|
||||
expect(membership.eventId).toBe("$eventid");
|
||||
});
|
||||
it("returns correct slot_id", () => {
|
||||
// slot_id is application and call_id dependent. So we create
|
||||
// a membership for each possible combination
|
||||
|
||||
// non call application (should not alter call_id even with empty string)
|
||||
const nonCallMembership = createCallMembership(makeMockEvent(), {
|
||||
...membershipTemplate,
|
||||
application: "m.not.a.call",
|
||||
call_id: "",
|
||||
});
|
||||
// non "" call id should not be altered
|
||||
const callMembershipCustomId = createCallMembership(makeMockEvent(), {
|
||||
...membershipTemplate,
|
||||
call_id: "customCallId",
|
||||
});
|
||||
|
||||
// for membership (application = m.call and call_id = "") we expect "" -> ROOM
|
||||
// for legacy events we expect the room to be added automagically
|
||||
// See INFO_SLOT_ID_LEGACY_CASE comments
|
||||
expect(membership.slotId).toBe("m.call#ROOM");
|
||||
expect(membership.slotDescription).toStrictEqual({ id: "ROOM", application: "m.call" });
|
||||
|
||||
expect(nonCallMembership.slotId).toBe("m.not.a.call#");
|
||||
expect(nonCallMembership.slotDescription).toStrictEqual({ id: "", application: "m.not.a.call" });
|
||||
|
||||
expect(callMembershipCustomId.slotId).toBe("m.call#customCallId");
|
||||
expect(callMembershipCustomId.slotDescription).toStrictEqual({
|
||||
id: "customCallId",
|
||||
application: "m.call",
|
||||
});
|
||||
});
|
||||
it("returns correct deviceId", () => {
|
||||
expect(membership.deviceId).toBe("AAAAAAA");
|
||||
|
||||
@@ -139,6 +139,7 @@ describe("MembershipManager", () => {
|
||||
"org.matrix.msc3401.call.member",
|
||||
{
|
||||
application: "m.call",
|
||||
// This tests INFO_SLOT_ID_LEGACY_CASE because it is using callSession = { id: "ROOM", application: "m.call" }
|
||||
call_id: "",
|
||||
device_id: "AAAAAAA",
|
||||
expires: 14400000,
|
||||
@@ -147,6 +148,7 @@ describe("MembershipManager", () => {
|
||||
focus_active: focusActive,
|
||||
scope: "m.room",
|
||||
},
|
||||
// This tests INFO_SLOT_ID_LEGACY_CASE because it is using callSession = { id: "ROOM", application: "m.call" }
|
||||
"_@alice:example.org_AAAAAAA_m.call",
|
||||
);
|
||||
restartScheduledDelayedEventHandle.resolve?.();
|
||||
@@ -160,6 +162,45 @@ describe("MembershipManager", () => {
|
||||
expect(client._unstable_sendDelayedStateEvent).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("sends correct call_id and state key when using non empty string. Not using empty string -> ROOM hack. See: INFO_SLOT_ID_LEGACY_CASE", async () => {
|
||||
// Spys/Mocks
|
||||
|
||||
const customCallSession = { id: "custom", application: "m.call" };
|
||||
const restartScheduledDelayedEventHandle = createAsyncHandle<void>(
|
||||
client._unstable_restartScheduledDelayedEvent,
|
||||
);
|
||||
|
||||
// Test
|
||||
const memberManager = new MembershipManager(undefined, room, client, customCallSession);
|
||||
memberManager.join([focus], undefined);
|
||||
// expects
|
||||
await waitForMockCall(client.sendStateEvent, Promise.resolve({ event_id: "id" }));
|
||||
expect(client.sendStateEvent).toHaveBeenCalledWith(
|
||||
room.roomId,
|
||||
"org.matrix.msc3401.call.member",
|
||||
{
|
||||
application: "m.call",
|
||||
call_id: "custom",
|
||||
device_id: "AAAAAAA",
|
||||
expires: 14400000,
|
||||
foci_preferred: [focus],
|
||||
membershipID: "@alice:example.org:AAAAAAA",
|
||||
focus_active: focusActive,
|
||||
scope: "m.room",
|
||||
},
|
||||
"_@alice:example.org_AAAAAAA_m.callcustom",
|
||||
);
|
||||
restartScheduledDelayedEventHandle.resolve?.();
|
||||
expect(client._unstable_sendDelayedStateEvent).toHaveBeenCalledWith(
|
||||
room.roomId,
|
||||
{ delay: 8000 },
|
||||
"org.matrix.msc3401.call.member",
|
||||
{},
|
||||
"_@alice:example.org_AAAAAAA_m.callcustom",
|
||||
);
|
||||
expect(client._unstable_sendDelayedStateEvent).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("reschedules delayed leave event if sending state cancels it", async () => {
|
||||
const memberManager = new MembershipManager(undefined, room, client, callSession);
|
||||
const waitForSendState = waitForMockCall(client.sendStateEvent);
|
||||
|
||||
@@ -372,28 +372,53 @@ export class CallMembership {
|
||||
*/
|
||||
public get slotId(): string {
|
||||
const { kind, data } = this.membershipData;
|
||||
if (data.application === "m.call") {
|
||||
switch (kind) {
|
||||
case "rtc":
|
||||
return data.slot_id;
|
||||
case "session":
|
||||
default: {
|
||||
const [application, id] = [this.application, data.call_id];
|
||||
|
||||
// INFO_SLOT_ID_LEGACY_CASE (search for all occurances of this INFO to get the full picture)
|
||||
// The spec got changed to use `"ROOM"` instead of `""` empyt string for the implicit default call.
|
||||
// State events still are sent with `""` however. To find other events that should end up in the same call,
|
||||
// we use the slotId.
|
||||
// Since the CallMembership is the public representation of a rtc.member event, we just pretend it is a
|
||||
// "ROOM" slotId/call_id.
|
||||
// This makes all the remote members work with just this simple trick.
|
||||
//
|
||||
// We of course now need to be careful when sending legacy events (state events)
|
||||
// They get a slotDescription containing "ROOM" since this is what we use starting at the time this comment
|
||||
// is commited.
|
||||
//
|
||||
// See the Other INFO_SLOT_ID_LEGACY_CASE comments to see where we revert back to "" just before sending the event.
|
||||
let compatibilityAdaptedId: string;
|
||||
if (id === "") {
|
||||
compatibilityAdaptedId = "ROOM";
|
||||
this.logger?.info("use slotId compat hack emptyString -> ROOM");
|
||||
} else {
|
||||
compatibilityAdaptedId = id;
|
||||
}
|
||||
return slotDescriptionToId({
|
||||
application,
|
||||
id: compatibilityAdaptedId,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.logger?.info("NOT using slotId compat hack emptyString -> ROOM");
|
||||
// This is what the function should look like for any other application that did not
|
||||
// go through a `""`=> `"ROOM"` rename
|
||||
switch (kind) {
|
||||
case "rtc":
|
||||
return data.slot_id;
|
||||
case "session":
|
||||
default:
|
||||
// INFO_SLOT_ID_LEGACY_CASE (search for all occurances of this INFO to get the full picture)
|
||||
// The spec got changed to use `"ROOM"` instead of `""` empyt string for the implicit default call.
|
||||
// State events still are sent with `""` however. To find other events that should end up in the same call,
|
||||
// we use the slotId.
|
||||
// Since the CallMembership is the public representation of a rtc.member event, we just pretend it is a
|
||||
// "ROOM" slotId/call_id.
|
||||
// This makes all the remote members work with just this simple trick.
|
||||
//
|
||||
// We of course now need to be careful when sending legacy events (state events)
|
||||
// They get a slotDescription containing "ROOM" since this is what we use starting at the time this comment
|
||||
// is commited.
|
||||
//
|
||||
// See the Other INFO_SLOT_ID_LEGACY_CASE comments to see where we revert back to "" just before sending the event.
|
||||
return slotDescriptionToId({
|
||||
application: this.application,
|
||||
id: data.call_id === "" ? "ROOM" : data.call_id,
|
||||
});
|
||||
default: {
|
||||
const [application, id] = [this.application, data.call_id];
|
||||
return slotDescriptionToId({ application, id });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -777,7 +777,8 @@ export class MembershipManager
|
||||
// INFO_SLOT_ID_LEGACY_CASE (search for all occurances of this INFO to get the full picture)
|
||||
// Revert back to "" just for the state key (state keys are always legacy. we use sticky events for non legacy events)
|
||||
const application = this.slotDescription.application;
|
||||
const slotId = this.slotDescription.id === "ROOM" ? "" : this.slotDescription.id;
|
||||
const needsEmptyStringRoomFix = application === "m.call" && this.slotDescription.id === "ROOM";
|
||||
const slotId = needsEmptyStringRoomFix ? "" : this.slotDescription.id;
|
||||
const stateKey = `${localUserId}_${localDeviceId}_${application}${slotId}`;
|
||||
if (/^org\.matrix\.msc(3757|3779)\b/.exec(this.room.getVersion())) {
|
||||
return stateKey;
|
||||
@@ -791,6 +792,8 @@ export class MembershipManager
|
||||
*/
|
||||
protected makeMyMembership(expires: number): SessionMembershipData | RtcMembershipData {
|
||||
const ownMembership = this.ownMembership;
|
||||
const needsEmptyStringRoomFix =
|
||||
this.slotDescription.application === "m.call" && this.slotDescription.id === "ROOM";
|
||||
|
||||
const focusObjects =
|
||||
this.rtcTransport === undefined
|
||||
@@ -806,7 +809,7 @@ export class MembershipManager
|
||||
"application": this.slotDescription.application,
|
||||
// INFO_SLOT_ID_LEGACY_CASE (search for all occurances of this INFO to get the full picture)
|
||||
// Revert back to "" just for the sending the event.
|
||||
"call_id": this.slotDescription.id === "ROOM" ? "" : this.slotDescription.id,
|
||||
"call_id": needsEmptyStringRoomFix ? "" : this.slotDescription.id,
|
||||
"scope": "m.room",
|
||||
"device_id": this.deviceId,
|
||||
// DO NOT use this.memberId here since that is the state key (using application...)
|
||||
|
||||
Reference in New Issue
Block a user