You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-08-07 23:02:56 +03:00
Prepare delayed call leave events more reliably (#4447)
* Prepare delayed call leave events more reliably - Try sending call join after preparing delayed leave - On leave, send delayed leave instead of a new event * Don't rely on errcodes for retry logic because they are unavailable in widget mode * Make arrow method readonly SonarCloud rule typescript:S2933 * Test coverage for restarting delayed call leave * Remove unneeded unstable_features mock It's unneeded because all affected methods are mocked
This commit is contained in:
committed by
GitHub
parent
66c80949e8
commit
13a967ae8f
@@ -46,9 +46,6 @@ describe("MatrixRTCSession", () => {
|
|||||||
client = new MatrixClient({ baseUrl: "base_url" });
|
client = new MatrixClient({ baseUrl: "base_url" });
|
||||||
client.getUserId = jest.fn().mockReturnValue("@alice:example.org");
|
client.getUserId = jest.fn().mockReturnValue("@alice:example.org");
|
||||||
client.getDeviceId = jest.fn().mockReturnValue("AAAAAAA");
|
client.getDeviceId = jest.fn().mockReturnValue("AAAAAAA");
|
||||||
client.doesServerSupportUnstableFeature = jest.fn((feature) =>
|
|
||||||
Promise.resolve(feature === "org.matrix.msc4140"),
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@@ -414,6 +411,8 @@ describe("MatrixRTCSession", () => {
|
|||||||
client._unstable_sendDelayedStateEvent = sendDelayedStateMock;
|
client._unstable_sendDelayedStateEvent = sendDelayedStateMock;
|
||||||
client.sendEvent = sendEventMock;
|
client.sendEvent = sendEventMock;
|
||||||
|
|
||||||
|
client._unstable_updateDelayedEvent = jest.fn();
|
||||||
|
|
||||||
mockRoom = makeMockRoom([]);
|
mockRoom = makeMockRoom([]);
|
||||||
sess = MatrixRTCSession.roomSessionForRoom(client, mockRoom);
|
sess = MatrixRTCSession.roomSessionForRoom(client, mockRoom);
|
||||||
});
|
});
|
||||||
@@ -490,6 +489,13 @@ describe("MatrixRTCSession", () => {
|
|||||||
);
|
);
|
||||||
await Promise.race([sentDelayedState, new Promise((resolve) => realSetTimeout(resolve, 500))]);
|
await Promise.race([sentDelayedState, new Promise((resolve) => realSetTimeout(resolve, 500))]);
|
||||||
expect(client._unstable_sendDelayedStateEvent).toHaveBeenCalledTimes(1);
|
expect(client._unstable_sendDelayedStateEvent).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
|
// should have tried updating the delayed leave to test that it wasn't replaced by own state
|
||||||
|
expect(client._unstable_updateDelayedEvent).toHaveBeenCalledTimes(1);
|
||||||
|
// should update delayed disconnect
|
||||||
|
jest.advanceTimersByTime(5000);
|
||||||
|
expect(client._unstable_updateDelayedEvent).toHaveBeenCalledTimes(2);
|
||||||
|
|
||||||
jest.useRealTimers();
|
jest.useRealTimers();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -140,6 +140,8 @@ export class MatrixRTCSession extends TypedEventEmitter<MatrixRTCSessionEvent, M
|
|||||||
private encryptionKeys = new Map<string, Array<{ key: Uint8Array; timestamp: number }>>();
|
private encryptionKeys = new Map<string, Array<{ key: Uint8Array; timestamp: number }>>();
|
||||||
private lastEncryptionKeyUpdateRequest?: number;
|
private lastEncryptionKeyUpdateRequest?: number;
|
||||||
|
|
||||||
|
private disconnectDelayId: string | undefined;
|
||||||
|
|
||||||
// We use this to store the last membership fingerprints we saw, so we can proactively re-send encryption keys
|
// We use this to store the last membership fingerprints we saw, so we can proactively re-send encryption keys
|
||||||
// if it looks like a membership has been updated.
|
// if it looks like a membership has been updated.
|
||||||
private lastMembershipFingerprints: Set<string> | undefined;
|
private lastMembershipFingerprints: Set<string> | undefined;
|
||||||
@@ -1011,19 +1013,24 @@ export class MatrixRTCSession extends TypedEventEmitter<MatrixRTCSessionEvent, M
|
|||||||
newContent = this.makeNewMembership(localDeviceId);
|
newContent = this.makeNewMembership(localDeviceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
const stateKey = legacy ? localUserId : this.makeMembershipStateKey(localUserId, localDeviceId);
|
|
||||||
try {
|
try {
|
||||||
await this.client.sendStateEvent(this.room.roomId, EventType.GroupCallMemberPrefix, newContent, stateKey);
|
if (legacy) {
|
||||||
logger.info(`Sent updated call member event.`);
|
await this.client.sendStateEvent(
|
||||||
|
this.room.roomId,
|
||||||
// check periodically to see if we need to refresh our member event
|
EventType.GroupCallMemberPrefix,
|
||||||
if (this.isJoined()) {
|
newContent,
|
||||||
if (legacy) {
|
localUserId,
|
||||||
|
);
|
||||||
|
if (this.isJoined()) {
|
||||||
|
// check periodically to see if we need to refresh our member event
|
||||||
this.memberEventTimeout = setTimeout(
|
this.memberEventTimeout = setTimeout(
|
||||||
this.triggerCallMembershipEventUpdate,
|
this.triggerCallMembershipEventUpdate,
|
||||||
MEMBER_EVENT_CHECK_PERIOD,
|
MEMBER_EVENT_CHECK_PERIOD,
|
||||||
);
|
);
|
||||||
} else {
|
}
|
||||||
|
} else if (this.isJoined()) {
|
||||||
|
const stateKey = this.makeMembershipStateKey(localUserId, localDeviceId);
|
||||||
|
const prepareDelayedDisconnection = async (): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
// TODO: If delayed event times out, re-join!
|
// TODO: If delayed event times out, re-join!
|
||||||
const res = await this.client._unstable_sendDelayedStateEvent(
|
const res = await this.client._unstable_sendDelayedStateEvent(
|
||||||
@@ -1035,12 +1042,63 @@ export class MatrixRTCSession extends TypedEventEmitter<MatrixRTCSessionEvent, M
|
|||||||
{}, // leave event
|
{}, // leave event
|
||||||
stateKey,
|
stateKey,
|
||||||
);
|
);
|
||||||
this.scheduleDelayDisconnection(res.delay_id);
|
this.disconnectDelayId = res.delay_id;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error("Failed to send delayed event:", e);
|
// TODO: Retry if rate-limited
|
||||||
|
logger.error("Failed to prepare delayed disconnection event:", e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
await prepareDelayedDisconnection();
|
||||||
|
// Send join event _after_ preparing the delayed disconnection event
|
||||||
|
await this.client.sendStateEvent(
|
||||||
|
this.room.roomId,
|
||||||
|
EventType.GroupCallMemberPrefix,
|
||||||
|
newContent,
|
||||||
|
stateKey,
|
||||||
|
);
|
||||||
|
// If sending state cancels your own delayed state, prepare another delayed state
|
||||||
|
// TODO: Remove this once MSC4140 is stable & doesn't cancel own delayed state
|
||||||
|
if (this.disconnectDelayId !== undefined) {
|
||||||
|
try {
|
||||||
|
await this.client._unstable_updateDelayedEvent(
|
||||||
|
this.disconnectDelayId,
|
||||||
|
UpdateDelayedEventAction.Restart,
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
// TODO: Make embedded client include errcode, and retry only if not M_NOT_FOUND (or rate-limited)
|
||||||
|
logger.warn("Failed to update delayed disconnection event, prepare it again:", e);
|
||||||
|
this.disconnectDelayId = undefined;
|
||||||
|
await prepareDelayedDisconnection();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (this.disconnectDelayId !== undefined) {
|
||||||
|
this.scheduleDelayDisconnection();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let sentDelayedDisconnect = false;
|
||||||
|
if (this.disconnectDelayId !== undefined) {
|
||||||
|
try {
|
||||||
|
await this.client._unstable_updateDelayedEvent(
|
||||||
|
this.disconnectDelayId,
|
||||||
|
UpdateDelayedEventAction.Send,
|
||||||
|
);
|
||||||
|
sentDelayedDisconnect = true;
|
||||||
|
} catch (e) {
|
||||||
|
// TODO: Retry if rate-limited
|
||||||
|
logger.error("Failed to send our delayed disconnection event:", e);
|
||||||
|
}
|
||||||
|
this.disconnectDelayId = undefined;
|
||||||
|
}
|
||||||
|
if (!sentDelayedDisconnect) {
|
||||||
|
await this.client.sendStateEvent(
|
||||||
|
this.room.roomId,
|
||||||
|
EventType.GroupCallMemberPrefix,
|
||||||
|
{},
|
||||||
|
this.makeMembershipStateKey(localUserId, localDeviceId),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
logger.info("Sent updated call member event.");
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const resendDelay = CALL_MEMBER_EVENT_RETRY_DELAY_MIN + Math.random() * 2000;
|
const resendDelay = CALL_MEMBER_EVENT_RETRY_DELAY_MIN + Math.random() * 2000;
|
||||||
logger.warn(`Failed to send call member event (retrying in ${resendDelay}): ${e}`);
|
logger.warn(`Failed to send call member event (retrying in ${resendDelay}): ${e}`);
|
||||||
@@ -1049,18 +1107,19 @@ export class MatrixRTCSession extends TypedEventEmitter<MatrixRTCSessionEvent, M
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private scheduleDelayDisconnection(delayId: string): void {
|
private scheduleDelayDisconnection(): void {
|
||||||
this.memberEventTimeout = setTimeout(() => this.delayDisconnection(delayId), 5000);
|
this.memberEventTimeout = setTimeout(this.delayDisconnection, 5000);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async delayDisconnection(delayId: string): Promise<void> {
|
private readonly delayDisconnection = async (): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
await this.client._unstable_updateDelayedEvent(delayId, UpdateDelayedEventAction.Restart);
|
await this.client._unstable_updateDelayedEvent(this.disconnectDelayId!, UpdateDelayedEventAction.Restart);
|
||||||
this.scheduleDelayDisconnection(delayId);
|
this.scheduleDelayDisconnection();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error("Failed to delay our disconnection event", e);
|
// TODO: Retry if rate-limited
|
||||||
|
logger.error("Failed to delay our disconnection event:", e);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
private stateEventsContainOngoingLegacySession(callMemberEvents: Map<string, MatrixEvent> | undefined): boolean {
|
private stateEventsContainOngoingLegacySession(callMemberEvents: Map<string, MatrixEvent> | undefined): boolean {
|
||||||
if (!callMemberEvents?.size) {
|
if (!callMemberEvents?.size) {
|
||||||
|
Reference in New Issue
Block a user