Unverified Commit 798ac7b9 authored by David Baker's avatar David Baker Committed by GitHub
Browse files

Revert "Revert "Add the call object to Call events""

parent eb0c0f7b
develop SimonBrandner/feat/e2ee-hash SimonBrandner/feat/groupcall-opts SimonBrandner/simplify andybalaam/bump-matrix-sdk-crypto-wasm-to-4.4.0 andybalaam/dont-reset-unread-mid-sync andybalaam/figure-out-why-ci-and-local-disagree andybalaam/fix-types-in-getSessionBackupPrivateKey andybalaam/issue-26254 andybalaam/remove-dist-from-package andybalaam/rewrite-hasUserReadEvent andybalaam/rewrite-receipts dbkr/bettermembercomparison dbkr/change_groupcall_on_new_call dbkr/fetch_rr_event_hack dbkr/movebase64 dbkr/msc3981_matrixversion dbkr/thread_fix_root_event_fetch dbkr/types_normal_deps dbkr/use_thread_root_if_present dbkr/workflow_comments enricoschw/fix-enter-conf-without-device enricoschw/test-relay-connection gh-readonly-queue/develop/pr-3238-41d3ffdab91ff0a98a22f3c9c5f98f1292599a65 gh-readonly-queue/develop/pr-3264-53260ee25d3eb29522bf78d87fef6706980f7714 gh-readonly-queue/develop/pr-3291-90234402a71955d60ca75a068e5450bdafed0b41 gh-readonly-queue/develop/pr-3371-8d14d45272dc4b6c33269802d229e6d8feffbbad gh-readonly-queue/develop/pr-3518-5a68861418dffbfbd3fb3cdfb1b074b8435bc417 hughns/msc3906-v2 hughns/oidc-qr-prototyping justjanne/fix/3981-followup justjanne/fix/threads-unnecessary-requests kerry/25472/findClientConfig-protocol kerry/25710/oidc-revocation-endpoint kerry/types-client-login langleyd/fix_incorrect_ll_dialog livekit master matthew/fix-logspam matthew/streaming-file-transfer rav/connect_olmmachine_to_client rav/element-r/00_is_secret_storage_ready rav/element-r/36_rust_crypto_for_cypress_bot rav/element-r/43_securebackup_interface rav/element-r/backup_logging rav/element-r/sanity_check_uia rav/no_parallel_jest renovate/eslint-plugin-jsdoc-41.x renovate/lock-file-maintenance renovate/typescript-5.x revert-3236-revert-3229-dbkr/call_events_pass_call revert-3536-rav/element-r/36_rust_crypto_for_cypress_bot staging t3chguy/const-enum t3chguy/fix/24336 t3chguy/fix/25395.1 t3chguy/fix/26628 t3chguy/oidc-qr-prototyping t3chguy/openapi t3chguy/react18/context t3chguy/saner-releases/2024 t3chguy/saner-releases/wrap t3chguy/test-revert t3chguy/tslint toger5/currentState-refactor-dedepreaction toger5/matrix-rtx-expires-at-part2 toger5/public_constructor_MatrixRTCSession valere/backup/fix_import_keys_progress_report valere/backup_prepareKeyBackupVersion_api valere/element-r/backup/import_backup_after_gossip valere/element-r/backup/restore/pr5 valere/element-r/bump_bindings_2.1.0_wip valere/element-r/bump_wasm_bindings_4.2.0 valere/element-r/keybackup_reset valere/element-r/keybackup_restore valere/element-r/migration_support_clear_ssss_secret valere/element-r/send_perf_improvement valere/rust_backup_support_wp0 v32.1.0 v32.1.0-rc.0 v32.0.0 v32.0.0-rc.0 v31.6.1 v31.6.0 v31.6.0-rc.0 v31.5.0 v31.5.0-rc.0 v31.4.0 v31.4.0-rc.0 v31.3.0 v31.3.0-rc.4 v31.3.0-rc.3 v31.3.0-rc.2 v31.3.0-rc.1 v31.3.0-rc.0 v31.2.0 v31.2.0-rc.0 v31.1.0 v31.0.0 v31.0.0-rc.0 v30.3.0 v30.3.0-rc.0 v30.2.0 v30.2.0-rc.0 v30.1.0 v30.1.0-rc.1 v30.1.0-rc.0 v30.0.1 v30.0.0 v30.0.0-rc.1 v29.1.0 v29.1.0-rc.1 v29.0.0 v29.0.0-rc.1 v28.2.0 v28.2.0-rc.1 v28.1.0 v28.1.0-rc.1 v28.0.0 v28.0.0-rc.1 v27.3.0-rc.1 v27.2.0 v27.2.0-rc.1 v27.1.0 v27.1.0-rc.1 v27.0.0 v27.0.0-rc.2 v27.0.0-rc.1 v26.2.0 v26.2.0-rc.1 v26.2.0-no-media-devices-hotfix v26.1.0 v26.1.0-rc1 v26.1.0-patch.1 v26.0.1 v26.0.0 v26.0.0-rc.1 v25.2.0-rc.5 v25.2.0-rc.4 v25.2.0-rc.2 v25.2.0-rc.1 v25.1.1 v25.1.0 v25.1.0-rc.1 v25.0.0 v25.0.0-rc.1 v24.1.0 v24.1.0-rc.1 no-media-devices-release
No related merge requests found
......@@ -123,6 +123,7 @@ export class MockRTCPeerConnection {
public iceCandidateListener?: (e: RTCPeerConnectionIceEvent) => void;
public iceConnectionStateChangeListener?: () => void;
public onTrackListener?: (e: RTCTrackEvent) => void;
public onDataChannelListener?: (ev: RTCDataChannelEvent) => void;
public needsNegotiation = false;
public readyToNegotiate: Promise<void>;
private onReadyToNegotiate?: () => void;
......@@ -168,6 +169,8 @@ export class MockRTCPeerConnection {
this.iceConnectionStateChangeListener = listener;
} else if (type == "track") {
this.onTrackListener = listener;
} else if (type == "datachannel") {
this.onDataChannelListener = listener;
}
}
public createDataChannel(label: string, opts: RTCDataChannelInit) {
......@@ -232,6 +235,10 @@ export class MockRTCPeerConnection {
this.negotiationNeededListener();
}
}
public triggerIncomingDataChannel(): void {
this.onDataChannelListener?.({ channel: {} } as RTCDataChannelEvent);
}
}
export class MockRTCRtpSender {
......
......@@ -431,6 +431,33 @@ describe("Call", function () {
expect(transceivers.get("m.usermedia:video")!.sender.track!.id).toBe("usermedia_video_track");
});
it("should handle error on call upgrade", async () => {
const onError = jest.fn();
call.on(CallEvent.Error, onError);
await startVoiceCall(client, call);
await call.onAnswerReceived(
makeMockEvent("@test:foo", {
version: 1,
call_id: call.callId,
party_id: "party_id",
answer: {
sdp: DUMMY_SDP,
},
[SDPStreamMetadataKey]: {},
}),
);
const mockGetUserMediaStream = jest.fn().mockRejectedValue(new Error("Test error"));
client.client.getMediaHandler().getUserMediaStream = mockGetUserMediaStream;
// then unmute which should cause an upgrade
await call.setLocalVideoMuted(false);
expect(onError).toHaveBeenCalled();
});
it("should unmute video after upgrading to video call", async () => {
// Regression test for https://github.com/vector-im/element-call/issues/925
await startVoiceCall(client, call);
......@@ -737,11 +764,22 @@ describe("Call", function () {
const dataChannel = call.createDataChannel("data_channel_label", { id: 123 });
expect(dataChannelCallback).toHaveBeenCalledWith(dataChannel);
expect(dataChannelCallback).toHaveBeenCalledWith(dataChannel, call);
expect(dataChannel.label).toBe("data_channel_label");
expect(dataChannel.id).toBe(123);
});
it("should emit a data channel event when the other side adds a data channel", async () => {
await startVoiceCall(client, call);
const dataChannelCallback = jest.fn();
call.on(CallEvent.DataChannel, dataChannelCallback);
(call.peerConn as unknown as MockRTCPeerConnection).triggerIncomingDataChannel();
expect(dataChannelCallback).toHaveBeenCalled();
});
describe("supportsMatrixCall", () => {
it("should return true when the environment is right", () => {
expect(supportsMatrixCall()).toBe(true);
......@@ -1604,7 +1642,7 @@ describe("Call", function () {
hasAdvancedBy += advanceBy;
expect(lengthChangedListener).toHaveBeenCalledTimes(hasAdvancedBy);
expect(lengthChangedListener).toHaveBeenCalledWith(hasAdvancedBy);
expect(lengthChangedListener).toHaveBeenCalledWith(hasAdvancedBy, call);
}
});
......@@ -1634,4 +1672,24 @@ describe("Call", function () {
expect(call.hangup).not.toHaveBeenCalled();
});
});
describe("Call replace", () => {
it("Fires event when call replaced", async () => {
const onReplace = jest.fn();
call.on(CallEvent.Replaced, onReplace);
await call.placeVoiceCall();
const call2 = new MatrixCall({
client: client.client,
roomId: FAKE_ROOM_ID,
});
call2.on(CallEvent.Error, errorListener);
await fakeIncomingCall(client, call2);
call.replacedBy(call2);
expect(onReplace).toHaveBeenCalled();
});
});
});
......@@ -102,7 +102,7 @@ describe("CallFeed", () => {
[CallState.Connected, true],
[CallState.Connecting, false],
])("should react to call state, when !isLocal()", (state: CallState, expected: Boolean) => {
call.emit(CallEvent.State, state);
call.emit(CallEvent.State, state, CallState.InviteSent, call.typed());
expect(feed.connected).toBe(expected);
});
......
......@@ -794,7 +794,7 @@ describe("Group Call", function () {
call.isLocalVideoMuted = jest.fn().mockReturnValue(true);
call.setLocalVideoMuted = jest.fn();
call.emit(CallEvent.State, CallState.Connected);
call.emit(CallEvent.State, CallState.Connected, CallState.InviteSent, call);
expect(call.setMicrophoneMuted).toHaveBeenCalledWith(false);
expect(call.setLocalVideoMuted).toHaveBeenCalledWith(false);
......@@ -1154,7 +1154,7 @@ describe("Group Call", function () {
});
it("handles regular case", () => {
oldMockCall.emit(CallEvent.Replaced, newMockCall.typed());
oldMockCall.emit(CallEvent.Replaced, newMockCall.typed(), oldMockCall.typed());
expect(oldMockCall.hangup).toHaveBeenCalled();
expect(callChangedListener).toHaveBeenCalledWith(newCallsMap);
......@@ -1165,7 +1165,7 @@ describe("Group Call", function () {
it("handles case where call is missing from the calls map", () => {
// @ts-ignore
groupCall.calls = new Map();
oldMockCall.emit(CallEvent.Replaced, newMockCall.typed());
oldMockCall.emit(CallEvent.Replaced, newMockCall.typed(), oldMockCall.typed());
expect(oldMockCall.hangup).toHaveBeenCalled();
expect(callChangedListener).toHaveBeenCalledWith(newCallsMap);
......
......@@ -296,20 +296,34 @@ export interface VoipEvent {
content: Record<string, unknown>;
}
/**
* These now all have the call object as an argument. Why? Well, to know which call a given event is
* about you have three options:
* 1. Use a closure as the callback that remembers what call it's listening to. This can be
* a pain because you need to pass the listener function again when you remove the listener,
* which might be somewhere else.
* 2. Use not-very-well-known fact that EventEmitter sets 'this' to the emitter object in the
* callback. This doesn't really play well with modern Typescript and eslint and doesn't work
* with our pattern of re-emitting events.
* 3. Pass the object in question as an argument to the callback.
*
* Now that we have group calls which have to deal with multiple call objects, this will
* become more important, and I think methods 1 and 2 are just going to cause issues.
*/
export type CallEventHandlerMap = {
[CallEvent.DataChannel]: (channel: RTCDataChannel) => void;
[CallEvent.FeedsChanged]: (feeds: CallFeed[]) => void;
[CallEvent.Replaced]: (newCall: MatrixCall) => void;
[CallEvent.Error]: (error: CallError) => void;
[CallEvent.RemoteHoldUnhold]: (onHold: boolean) => void;
[CallEvent.LocalHoldUnhold]: (onHold: boolean) => void;
[CallEvent.LengthChanged]: (length: number) => void;
[CallEvent.State]: (state: CallState, oldState?: CallState) => void;
[CallEvent.DataChannel]: (channel: RTCDataChannel, call: MatrixCall) => void;
[CallEvent.FeedsChanged]: (feeds: CallFeed[], call: MatrixCall) => void;
[CallEvent.Replaced]: (newCall: MatrixCall, oldCall: MatrixCall) => void;
[CallEvent.Error]: (error: CallError, call: MatrixCall) => void;
[CallEvent.RemoteHoldUnhold]: (onHold: boolean, call: MatrixCall) => void;
[CallEvent.LocalHoldUnhold]: (onHold: boolean, call: MatrixCall) => void;
[CallEvent.LengthChanged]: (length: number, call: MatrixCall) => void;
[CallEvent.State]: (state: CallState, oldState: CallState, call: MatrixCall) => void;
[CallEvent.Hangup]: (call: MatrixCall) => void;
[CallEvent.AssertedIdentityChanged]: () => void;
[CallEvent.AssertedIdentityChanged]: (call: MatrixCall) => void;
/* @deprecated */
[CallEvent.HoldUnhold]: (onHold: boolean) => void;
[CallEvent.SendVoipEvent]: (event: VoipEvent) => void;
[CallEvent.SendVoipEvent]: (event: VoipEvent, call: MatrixCall) => void;
};
// The key of the transceiver map (purpose + media type, separated by ':')
......@@ -459,7 +473,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
*/
public createDataChannel(label: string, options: RTCDataChannelInit | undefined): RTCDataChannel {
const dataChannel = this.peerConn!.createDataChannel(label, options);
this.emit(CallEvent.DataChannel, dataChannel);
this.emit(CallEvent.DataChannel, dataChannel, this);
return dataChannel;
}
......@@ -494,7 +508,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
private set state(state: CallState) {
const oldState = this._state;
this._state = state;
this.emit(CallEvent.State, state, oldState);
this.emit(CallEvent.State, state, oldState, this);
}
public get type(): CallType {
......@@ -684,7 +698,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
}),
);
this.emit(CallEvent.FeedsChanged, this.feeds);
this.emit(CallEvent.FeedsChanged, this.feeds, this);
logger.info(
`Call ${this.callId} pushRemoteFeed() pushed stream (streamId=${stream.id}, active=${stream.active}, purpose=${purpose})`,
......@@ -732,7 +746,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
}),
);
this.emit(CallEvent.FeedsChanged, this.feeds);
this.emit(CallEvent.FeedsChanged, this.feeds, this);
logger.info(
`Call ${this.callId} pushRemoteFeedWithoutMetadata() pushed stream (streamId=${stream.id}, active=${stream.active})`,
......@@ -832,7 +846,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
`Call ${this.callId} pushLocalFeed() pushed stream (id=${callFeed.stream.id}, active=${callFeed.stream.active}, purpose=${callFeed.purpose})`,
);
this.emit(CallEvent.FeedsChanged, this.feeds);
this.emit(CallEvent.FeedsChanged, this.feeds, this);
}
/**
......@@ -869,7 +883,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
}
this.feeds = [];
this.emit(CallEvent.FeedsChanged, this.feeds);
this.emit(CallEvent.FeedsChanged, this.feeds, this);
}
private deleteFeedByStream(stream: MediaStream): void {
......@@ -886,7 +900,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
private deleteFeed(feed: CallFeed): void {
feed.dispose();
this.feeds.splice(this.feeds.indexOf(feed), 1);
this.emit(CallEvent.FeedsChanged, this.feeds);
this.emit(CallEvent.FeedsChanged, this.feeds, this);
}
// The typescript definitions have this type as 'any' :(
......@@ -1117,7 +1131,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
}
}
this.successor = newCall;
this.emit(CallEvent.Replaced, newCall);
this.emit(CallEvent.Replaced, newCall, this);
this.hangup(CallErrorCode.Replaced, true);
}
......@@ -1188,6 +1202,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
this.emit(
CallEvent.Error,
new CallError(CallErrorCode.NoUserMedia, "Failed to get camera access: ", <Error>error),
this,
);
}
}
......@@ -1513,7 +1528,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
this.updateMuteStatus();
this.sendMetadataUpdate();
this.emit(CallEvent.RemoteHoldUnhold, this.remoteOnHold);
this.emit(CallEvent.RemoteHoldUnhold, this.remoteOnHold, this);
}
/**
......@@ -1638,7 +1653,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
code = CallErrorCode.UnknownDevices;
message = "Unknown devices present in the room";
}
this.emit(CallEvent.Error, new CallError(code, message, <Error>error));
this.emit(CallEvent.Error, new CallError(code, message, <Error>error), this);
throw error;
}
......@@ -1987,7 +2002,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
const newLocalOnHold = this.isLocalOnHold();
if (prevLocalOnHold !== newLocalOnHold) {
this.emit(CallEvent.LocalHoldUnhold, newLocalOnHold);
this.emit(CallEvent.LocalHoldUnhold, newLocalOnHold, this);
// also this one for backwards compat
this.emit(CallEvent.HoldUnhold, newLocalOnHold);
}
......@@ -2018,7 +2033,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
id: content.asserted_identity.id,
displayName: content.asserted_identity.display_name,
};
this.emit(CallEvent.AssertedIdentityChanged);
this.emit(CallEvent.AssertedIdentityChanged, this);
}
public callHasEnded(): boolean {
......@@ -2040,6 +2055,12 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
private async wrappedGotLocalOffer(): Promise<void> {
this.makingOffer = true;
try {
// XXX: in what situations do we believe gotLocalOffer actually throws? It appears
// to handle most of its exceptions itself and terminate the call. I'm not entirely
// sure it would ever throw, so I can't add a test for these lines.
// Also the tense is different between "gotLocalOffer" and "getLocalOfferFailed" so
// it's not entirely clear whether getLocalOfferFailed is just misnamed or whether
// they've been cross-polinated somehow at some point.
await this.gotLocalOffer();
} catch (e) {
this.getLocalOfferFailed(e as Error);
......@@ -2134,7 +2155,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
message = "Unknown devices present in the room";
}
this.emit(CallEvent.Error, new CallError(code, message, <Error>error));
this.emit(CallEvent.Error, new CallError(code, message, <Error>error), this);
this.terminate(CallParty.Local, code, false);
// no need to carry on & send the candidate queue, but we also
......@@ -2158,7 +2179,11 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
private getLocalOfferFailed = (err: Error): void => {
logger.error(`Call ${this.callId} getLocalOfferFailed() running`, err);
this.emit(CallEvent.Error, new CallError(CallErrorCode.LocalOfferFailed, "Failed to get local offer!", err));
this.emit(
CallEvent.Error,
new CallError(CallErrorCode.LocalOfferFailed, "Failed to get local offer!", err),
this,
);
this.terminate(CallParty.Local, CallErrorCode.LocalOfferFailed, false);
};
......@@ -2177,6 +2202,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
"Couldn't start capturing media! Is your microphone set up and " + "does this app have permission?",
err,
),
this,
);
this.terminate(CallParty.Local, CallErrorCode.NoUserMedia, false);
};
......@@ -2200,7 +2226,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
this.callStartTime = Date.now();
this.callLengthInterval = setInterval(() => {
this.emit(CallEvent.LengthChanged, Math.round((Date.now() - this.callStartTime!) / 1000));
this.emit(CallEvent.LengthChanged, Math.round((Date.now() - this.callStartTime!) / 1000), this);
}, CALL_LENGTH_INTERVAL);
}
} else if (this.peerConn?.iceConnectionState == "failed") {
......@@ -2267,7 +2293,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
};
private onDataChannel = (ev: RTCDataChannelEvent): void => {
this.emit(CallEvent.DataChannel, ev.channel);
this.emit(CallEvent.DataChannel, ev.channel, this);
};
/**
......@@ -2380,13 +2406,17 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
[ToDeviceMessageId]: uuidv4(),
};
this.emit(CallEvent.SendVoipEvent, {
type: "toDevice",
eventType,
userId: this.invitee || this.getOpponentMember()?.userId,
opponentDeviceId: this.opponentDeviceId,
content,
});
this.emit(
CallEvent.SendVoipEvent,
{
type: "toDevice",
eventType,
userId: this.invitee || this.getOpponentMember()?.userId,
opponentDeviceId: this.opponentDeviceId,
content,
},
this,
);
const userId = this.invitee || this.getOpponentMember()!.userId;
if (this.client.getUseE2eForGroupCall()) {
......@@ -2414,13 +2444,17 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
);
}
} else {
this.emit(CallEvent.SendVoipEvent, {
type: "sendEvent",
eventType,
roomId: this.roomId,
content: realContent,
userId: this.invitee || this.getOpponentMember()?.userId,
});
this.emit(
CallEvent.SendVoipEvent,
{
type: "sendEvent",
eventType,
roomId: this.roomId,
content: realContent,
userId: this.invitee || this.getOpponentMember()?.userId,
},
this,
);
await this.client.sendEvent(this.roomId!, eventType, realContent);
}
......@@ -2669,7 +2703,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
const code = CallErrorCode.SignallingFailed;
const message = "Signalling failed";
this.emit(CallEvent.Error, new CallError(code, message, <Error>error));
this.emit(CallEvent.Error, new CallError(code, message, <Error>error), this);
this.hangup(code, false);
return;
......
......@@ -40,6 +40,11 @@ export enum GroupCallTerminationReason {
CallEnded = "call_ended",
}
/**
* Because event names are just strings, they do need
* to be unique over all event types of event emitter.
* Some objects could emit more then one set of events.
*/
export enum GroupCallEvent {
GroupCallStateChanged = "group_call_state_changed",
ActiveSpeakerChanged = "active_speaker_changed",
......@@ -49,7 +54,7 @@ export enum GroupCallEvent {
LocalScreenshareStateChanged = "local_screenshare_state_changed",
LocalMuteStateChanged = "local_mute_state_changed",
ParticipantsChanged = "participants_changed",
Error = "error",
Error = "group_call_error",
}
export type GroupCallEventHandlerMap = {
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment