From a4e853e1d47428a53dc16b30a598bf4680e74852 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Thu, 6 May 2021 17:15:04 +0200 Subject: [PATCH 001/118] Add types for MSC3077 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/webrtc/callEventTypes.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/webrtc/callEventTypes.ts b/src/webrtc/callEventTypes.ts index dce146485..f5b81ab5d 100644 --- a/src/webrtc/callEventTypes.ts +++ b/src/webrtc/callEventTypes.ts @@ -1,11 +1,22 @@ // allow camelcase as these are events type that go onto the wire /* eslint-disable camelcase */ +// TODO: Change to "sdp_stream_metadata" when MSC3077 is merged +export const SDPStreamMetadataKey = "org.matrix.msc3077.sdp_stream_metadata"; + export enum SDPStreamMetadataPurpose { Usermedia = "m.usermedia", Screenshare = "m.screenshare", } +export interface SDPStreamMetadataObject { + purpose: SDPStreamMetadataPurpose, +} + +export interface SDPStreamMetadata { + [key: string]: SDPStreamMetadataObject, +} + interface CallOfferAnswer { type: string; sdp: string; @@ -18,6 +29,7 @@ export interface CallCapabilities { export interface MCallAnswer { answer: CallOfferAnswer; capabilities: CallCapabilities; + [SDPStreamMetadataKey]: SDPStreamMetadata; } export interface MCallOfferNegotiate { @@ -25,6 +37,7 @@ export interface MCallOfferNegotiate { description: CallOfferAnswer; lifetime: number; capabilities: CallCapabilities; + [SDPStreamMetadataKey]: SDPStreamMetadata; } export interface MCallReplacesTarget { From 631faa2046704ee6c742e6db9876a47ecf39f74a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Thu, 6 May 2021 17:18:30 +0200 Subject: [PATCH 002/118] Send SDPStreamMetadata MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/webrtc/call.ts | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/webrtc/call.ts b/src/webrtc/call.ts index d84be56b7..1c7cb2bda 100644 --- a/src/webrtc/call.ts +++ b/src/webrtc/call.ts @@ -34,6 +34,8 @@ import { MCallOfferNegotiate, CallCapabilities, SDPStreamMetadataPurpose, + SDPStreamMetadata, + SDPStreamMetadataKey, } from './callEventTypes'; import { CallFeed } from './callFeed'; @@ -448,6 +450,20 @@ export class MatrixCall extends EventEmitter { return this.feeds.filter((feed) => {return !feed.isLocal()}); } + /** + * Generates and returns localSDPStreamMetadata + * @returns {SDPStreamMetadata} localSDPStreamMetadata + */ + private getLocalSDPStreamMetadata(): SDPStreamMetadata { + const metadata = {}; + this.getLocalFeeds().map((localFeed) => { + metadata[localFeed.stream.id] = { + purpose: localFeed.purpose, + }; + }); + return metadata; + } + /** * Returns true if there are no incoming feeds, * otherwise returns false @@ -812,7 +828,8 @@ export class MatrixCall extends EventEmitter { logger.debug( "Setting screen sharing stream to the local video element", ); - this.pushNewFeed(this.screenSharingStream, this.client.getUserId(), SDPStreamMetadataPurpose.Screenshare); + // XXX: Because this is to support backwards compatibility we pretend like this stream is usermedia + this.pushNewFeed(this.screenSharingStream, this.client.getUserId(), SDPStreamMetadataPurpose.Usermedia); } else { this.pushNewFeed(stream, this.client.getUserId(), SDPStreamMetadataPurpose.Usermedia); } @@ -1196,6 +1213,8 @@ export class MatrixCall extends EventEmitter { } } + content[SDPStreamMetadataKey] = this.getLocalSDPStreamMetadata(); + // Get rid of any candidates waiting to be sent: they'll be included in the local // description we just got and will send in the offer. logger.info(`Discarding ${this.candidateSendQueue.length} candidates that will be sent in offer`); From 6a920fe6232dc7566aa88c4d265989e367d7e172 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 7 May 2021 07:56:27 +0200 Subject: [PATCH 003/118] Get sdpStreamMetadata from invite MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/webrtc/call.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/webrtc/call.ts b/src/webrtc/call.ts index 1c7cb2bda..d5e4891fa 100644 --- a/src/webrtc/call.ts +++ b/src/webrtc/call.ts @@ -310,6 +310,8 @@ export class MatrixCall extends EventEmitter { private remoteAssertedIdentity: AssertedIdentity; + private remoteSDPStreamMetadata: SDPStreamMetadata; + constructor(opts: CallOpts) { super(); this.roomId = opts.roomId; @@ -528,6 +530,13 @@ export class MatrixCall extends EventEmitter { logger.warn("Failed to get TURN credentials! Proceeding with call anyway..."); } + const sdpStreamMetadata = invite[SDPStreamMetadataKey]; + if (sdpStreamMetadata) { + this.remoteSDPStreamMetadata = sdpStreamMetadata; + } else { + logger.debug("Did not get any SDPStreamMetadata! Can not send/receive multiple streams"); + } + this.peerConn = this.createPeerConnection(); // we must set the party ID before await-ing on anything: the call event // handler will start giving us more call events (eg. candidates) so if From 4d4a6ede21bd122b821f68ff27bc344161fa8a17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 7 May 2021 10:41:59 +0200 Subject: [PATCH 004/118] Use somicolons instead MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/webrtc/callEventTypes.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/webrtc/callEventTypes.ts b/src/webrtc/callEventTypes.ts index f5b81ab5d..532567843 100644 --- a/src/webrtc/callEventTypes.ts +++ b/src/webrtc/callEventTypes.ts @@ -10,11 +10,11 @@ export enum SDPStreamMetadataPurpose { } export interface SDPStreamMetadataObject { - purpose: SDPStreamMetadataPurpose, + purpose: SDPStreamMetadataPurpose; } export interface SDPStreamMetadata { - [key: string]: SDPStreamMetadataObject, + [key: string]: SDPStreamMetadataObject; } interface CallOfferAnswer { From 23f5c2e03f0bbbed35aa39d0b2e3071b2d65bde0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 7 May 2021 10:43:36 +0200 Subject: [PATCH 005/118] Add a separate method to push local feed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/webrtc/call.ts | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/webrtc/call.ts b/src/webrtc/call.ts index d5e4891fa..dfda7aadd 100644 --- a/src/webrtc/call.ts +++ b/src/webrtc/call.ts @@ -485,6 +485,18 @@ export class MatrixCall extends EventEmitter { this.feeds.push(new CallFeed(stream, userId, purpose, this.client, this.roomId)); this.emit(CallEvent.FeedsChanged, this.feeds); } + + private pushLocalFeed(stream: MediaStream, purpose: SDPStreamMetadataPurpose) { + const userId = this.client.getUserId(); + + // We try to replace an existing feed if there already is one with the same purpose + const existingFeed = this.getLocalFeeds().find((feed) => feed.purpose === purpose); + if (existingFeed) { + existingFeed.setNewStream(stream); + } else { + this.feeds.push(new CallFeed(stream, userId, purpose, this.client, this.roomId)); + this.emit(CallEvent.FeedsChanged, this.feeds); + } } private deleteAllFeeds() { @@ -838,9 +850,9 @@ export class MatrixCall extends EventEmitter { "Setting screen sharing stream to the local video element", ); // XXX: Because this is to support backwards compatibility we pretend like this stream is usermedia - this.pushNewFeed(this.screenSharingStream, this.client.getUserId(), SDPStreamMetadataPurpose.Usermedia); + this.pushLocalFeed(this.screenSharingStream, SDPStreamMetadataPurpose.Usermedia); } else { - this.pushNewFeed(stream, this.client.getUserId(), SDPStreamMetadataPurpose.Usermedia); + this.pushLocalFeed(stream, SDPStreamMetadataPurpose.Usermedia); } // why do we enable audio (and only audio) tracks here? -- matthew @@ -910,7 +922,7 @@ export class MatrixCall extends EventEmitter { return; } - this.pushNewFeed(stream, this.client.getUserId(), SDPStreamMetadataPurpose.Usermedia); + this.pushLocalFeed(stream, SDPStreamMetadataPurpose.Usermedia); this.localAVStream = stream; logger.info("Got local AV stream with id " + this.localAVStream.id); From cebdc44689da34fd67dc9798013e98c2a0d39db5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 7 May 2021 11:11:31 +0200 Subject: [PATCH 006/118] Set remoteSDPStreamMetadata from answer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/webrtc/call.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/webrtc/call.ts b/src/webrtc/call.ts index dfda7aadd..c5150a5d4 100644 --- a/src/webrtc/call.ts +++ b/src/webrtc/call.ts @@ -1060,6 +1060,13 @@ export class MatrixCall extends EventEmitter { this.setState(CallState.Connecting); + const sdpStreamMetadata = event.getContent()[SDPStreamMetadataKey]; + if (sdpStreamMetadata) { + this.remoteSDPStreamMetadata = sdpStreamMetadata; + } else { + logger.debug("Did not get any SDPStreamMetadata! Can not send/receive multiple streams"); + } + try { await this.peerConn.setRemoteDescription(event.getContent().answer); } catch (e) { From 25eb6de22039942f9fae61f64f68752e22ec988a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 7 May 2021 11:17:08 +0200 Subject: [PATCH 007/118] Send SDPStreamMetadata in answer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/webrtc/call.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/webrtc/call.ts b/src/webrtc/call.ts index c5150a5d4..30f19d892 100644 --- a/src/webrtc/call.ts +++ b/src/webrtc/call.ts @@ -878,6 +878,7 @@ export class MatrixCall extends EventEmitter { // required to still be sent for backwards compat type: this.peerConn.localDescription.type, }, + [SDPStreamMetadataKey]: this.getLocalSDPStreamMetadata(), } as MCallAnswer; if (this.client._supportsCallTransfer) { From 30f22634439a8f6cb3ec98932a75bc73b70331be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 7 May 2021 11:18:25 +0200 Subject: [PATCH 008/118] Rework pushing of remote feeds for MSC3077 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/webrtc/call.ts | 75 ++++++++++++++++++++++++++++++---------------- 1 file changed, 50 insertions(+), 25 deletions(-) diff --git a/src/webrtc/call.ts b/src/webrtc/call.ts index 30f19d892..7c1961cb3 100644 --- a/src/webrtc/call.ts +++ b/src/webrtc/call.ts @@ -475,7 +475,52 @@ export class MatrixCall extends EventEmitter { return !this.feeds.some((feed) => !feed.isLocal()); } - private pushNewFeed(stream: MediaStream, userId: string, purpose: SDPStreamMetadataPurpose) { + private pushRemoteFeed(stream: MediaStream) { + // Fallback to old behavior if the other side doesn't support SDPStreamMetadata + if (!this.remoteSDPStreamMetadata) { + this.pushRemoteFeedWithoutMetadata(stream); + return; + } + + const userId = this.getOpponentMember().userId; + const streamMetadata = this.remoteSDPStreamMetadata[stream.id]; + + if (!streamMetadata) { + logger.warn(`Ignoring stream with id ${stream.id} because we didn't get any metadata about it`); + return; + } + + // Try to find a feed with the same purpose as the new stream, + // if we find it replace the old stream with the new one + const existingFeed = this.getRemoteFeeds().find((feed) => feed.purpose === streamMetadata.purpose); + if (existingFeed) { + existingFeed.setNewStream(stream); + } else { + this.feeds.push(new CallFeed(stream, userId, streamMetadata.purpose, this.client, this.roomId)); + this.emit(CallEvent.FeedsChanged, this.feeds); + } + + logger.info(`Pushed remote stream with id ${stream.id}. Stream active? ${stream.active}`); + } + + /** + * This method is used ONLY if the other client doesn't support sending SDPStreamMetadata + */ + private pushRemoteFeedWithoutMetadata(stream: MediaStream) { + const userId = this.getOpponentMember().userId; + // We can guess the purpose here since the other client can only send one stream + const purpose = SDPStreamMetadataPurpose.Usermedia; + const oldRemoteStream = this.feeds.find((feed) => {return !feed.isLocal()})?.stream; + + // Note that we check by ID and always set the remote stream: Chrome appears + // to make new stream objects when transceiver directionality is changed and the 'active' + // status of streams change - Dave + // If we already have a stream, check this stream has the same id + if (oldRemoteStream && stream.id !== oldRemoteStream.id) { + logger.warn(`Ignoring new stream ID ${stream.id}: we already have stream ID ${oldRemoteStream.id}`); + return; + } + // Try to find a feed with the same stream id as the new stream, // if we find it replace the old stream with the new one const feed = this.feeds.find((feed) => feed.stream.id === stream.id); @@ -486,6 +531,9 @@ export class MatrixCall extends EventEmitter { this.emit(CallEvent.FeedsChanged, this.feeds); } + logger.info(`Pushed remote stream with id ${stream.id}. Stream active? ${stream.active}`); + } + private pushLocalFeed(stream: MediaStream, purpose: SDPStreamMetadataPurpose) { const userId = this.client.getUserId(); @@ -1348,30 +1396,7 @@ export class MatrixCall extends EventEmitter { return; } - const oldRemoteStream = this.feeds.find((feed) => {return !feed.isLocal()})?.stream; - - // If we already have a stream, check this track is from the same one - // Note that we check by ID and always set the remote stream: Chrome appears - // to make new stream objects when tranciever directionality is changed and the 'active' - // status of streams change - Dave - if (oldRemoteStream && ev.streams[0].id !== oldRemoteStream.id) { - logger.warn( - `Ignoring new stream ID ${ev.streams[0].id}: we already have stream ID ${oldRemoteStream.id}`, - ); - return; - } - - if (!oldRemoteStream) { - logger.info("Got remote stream with id " + ev.streams[0].id); - } - - const newRemoteStream = ev.streams[0]; - - logger.debug(`Track id ${ev.track.id} of kind ${ev.track.kind} added`); - - this.pushNewFeed(newRemoteStream, this.getOpponentMember().userId, SDPStreamMetadataPurpose.Usermedia) - - logger.info("playing remote. stream active? " + newRemoteStream.active); + this.pushRemoteFeed(ev.streams[0]); }; onNegotiationNeeded = async () => { From 50e0f6353a3329060edf79d3e1eec881916e8374 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 7 May 2021 13:15:09 +0200 Subject: [PATCH 009/118] Move adding tracks into pushLocalFeed() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/webrtc/call.ts | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/src/webrtc/call.ts b/src/webrtc/call.ts index 7c1961cb3..5e9335816 100644 --- a/src/webrtc/call.ts +++ b/src/webrtc/call.ts @@ -545,6 +545,14 @@ export class MatrixCall extends EventEmitter { this.feeds.push(new CallFeed(stream, userId, purpose, this.client, this.roomId)); this.emit(CallEvent.FeedsChanged, this.feeds); } + + // why do we enable audio (and only audio) tracks here? -- matthew + setTracksEnabled(stream.getAudioTracks(), true); + + for (const track of stream.getTracks()) { + logger.info(`Adding track with id ${track.id} and with kind ${track.kind} to peer connection`) + this.peerConn.addTrack(track, stream); + } } private deleteAllFeeds() { @@ -903,18 +911,6 @@ export class MatrixCall extends EventEmitter { this.pushLocalFeed(stream, SDPStreamMetadataPurpose.Usermedia); } - // why do we enable audio (and only audio) tracks here? -- matthew - setTracksEnabled(stream.getAudioTracks(), true); - - for (const audioTrack of stream.getAudioTracks()) { - logger.info("Adding audio track with id " + audioTrack.id); - this.peerConn.addTrack(audioTrack, stream); - } - for (const videoTrack of (this.screenSharingStream || stream).getVideoTracks()) { - logger.info("Adding video track with id " + videoTrack.id); - this.peerConn.addTrack(videoTrack, stream); - } - // Now we wait for the negotiationneeded event }; @@ -975,10 +971,6 @@ export class MatrixCall extends EventEmitter { this.localAVStream = stream; logger.info("Got local AV stream with id " + this.localAVStream.id); - setTracksEnabled(stream.getAudioTracks(), true); - for (const track of stream.getTracks()) { - this.peerConn.addTrack(track, stream); - } this.setState(CallState.CreateAnswer); From 640d13af999190b736f6019779e0c4fad4c18c6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 7 May 2021 13:20:36 +0200 Subject: [PATCH 010/118] Set remoteSDPStreamMetadata in onNegotiateReceived() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/webrtc/call.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/webrtc/call.ts b/src/webrtc/call.ts index 5e9335816..90901b48a 100644 --- a/src/webrtc/call.ts +++ b/src/webrtc/call.ts @@ -1184,6 +1184,13 @@ export class MatrixCall extends EventEmitter { this.unholdingRemote = false; } + const metadata = event.getContent()[SDPStreamMetadataKey]; + if (metadata) { + this.remoteSDPStreamMetadata = metadata; + } else { + logger.warn("Received negotiation event without SDPStreamMetadata!") + } + try { await this.peerConn.setRemoteDescription(description); From b1ace49f9a2a855da3426ddb77e5133b8ce9320e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 7 May 2021 20:53:19 +0200 Subject: [PATCH 011/118] Add methods useful for screensharing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/webrtc/call.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/webrtc/call.ts b/src/webrtc/call.ts index 90901b48a..06db92be4 100644 --- a/src/webrtc/call.ts +++ b/src/webrtc/call.ts @@ -762,6 +762,23 @@ export class MatrixCall extends EventEmitter { this.sendVoipEvent(EventType.CallReject, {}); } + /** + * Returns true if this.remoteSDPStreamMetadata is defined, otherwise returns false + * @returns {boolean} can screenshare + */ + public opponentSupportsSDPStreamMetadata(): boolean { + return Boolean(this.remoteSDPStreamMetadata); + } + + /** + * If there is a screensharing stream returns true, otherwise returns false + * @returns {boolean} is screensharing + */ + public isScreensharing(): boolean { + console.log("LOG stream", this.screenSharingStream); + return Boolean(this.screenSharingStream); + } + /** * Set whether our outbound video should be muted or not. * @param {boolean} muted True to mute the outbound video. From 4d74b5cdad228c40e0d5108a4445a8c52a228be1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 7 May 2021 20:57:45 +0200 Subject: [PATCH 012/118] Send SDPStreamMetadata in negotiation response MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/webrtc/call.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/webrtc/call.ts b/src/webrtc/call.ts index 06db92be4..836ff2948 100644 --- a/src/webrtc/call.ts +++ b/src/webrtc/call.ts @@ -1230,6 +1230,7 @@ export class MatrixCall extends EventEmitter { this.sendVoipEvent(EventType.CallNegotiate, { description: this.peerConn.localDescription, + [SDPStreamMetadataKey]: this.getLocalSDPStreamMetadata(), }); } } catch (err) { From 8c8a68d3ae7ba5e1e59f9cb9ef0336604ddfa7a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 7 May 2021 21:01:56 +0200 Subject: [PATCH 013/118] Add methods to delete feeds MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/webrtc/call.ts | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/src/webrtc/call.ts b/src/webrtc/call.ts index 836ff2948..1daab7f76 100644 --- a/src/webrtc/call.ts +++ b/src/webrtc/call.ts @@ -560,6 +560,42 @@ export class MatrixCall extends EventEmitter { this.emit(CallEvent.FeedsChanged, this.feeds); } + private deleteLocalFeedByStream(stream: MediaStream) { + logger.debug(`Removing feed with stream id ${stream.id}`); + + const feed = this.getLocalFeeds().find((feed) => feed.stream.id === stream.id); + if (!feed) { + logger.warn(`Didn't find the feed with stream id ${stream.id} to delete`); + return; + } + + this.feeds.splice(this.feeds.indexOf(feed), 1); + this.emit(CallEvent.FeedsChanged, this.feeds); + + for (const track of stream.getTracks()) { + // XXX: This is ugly and there has to be a way to do this more nicely + for (const sender of this.peerConn.getSenders()) { + if (sender.track?.id === track.id) { + this.peerConn.removeTrack(sender); + } + } + track.stop(); + } + } + + private deleteRemoteFeedByStream(stream: MediaStream) { + logger.debug(`Removing feed with stream id ${stream.id}`); + + const feed = this.getRemoteFeeds().find((feed) => feed.stream.id === stream.id); + if (!feed) { + logger.warn(`Didn't find the feed with stream id ${stream.id} to delete`); + return; + } + + this.feeds.splice(this.feeds.indexOf(feed), 1); + this.emit(CallEvent.FeedsChanged, this.feeds); + } + // The typescript definitions have this type as 'any' :( public async getCurrentCallStats(): Promise { if (this.callHasEnded()) { From 0e6b43a7692dc8dc8de75510bef12765ae9e23af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 7 May 2021 21:02:56 +0200 Subject: [PATCH 014/118] Hook up methods to delete feeds MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/webrtc/call.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/webrtc/call.ts b/src/webrtc/call.ts index 1daab7f76..a1dec5762 100644 --- a/src/webrtc/call.ts +++ b/src/webrtc/call.ts @@ -1449,7 +1449,9 @@ export class MatrixCall extends EventEmitter { return; } - this.pushRemoteFeed(ev.streams[0]); + const stream = ev.streams[0]; + this.pushRemoteFeed(stream); + stream.addEventListener("removetrack", () => this.deleteRemoteFeedByStream(stream)); }; onNegotiationNeeded = async () => { From a35559be65c0852225bab1c18d331dd4d1edd5da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 7 May 2021 21:33:05 +0200 Subject: [PATCH 015/118] Add a method to start screensharing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/webrtc/call.ts | 59 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/src/webrtc/call.ts b/src/webrtc/call.ts index a1dec5762..d8dd8aabf 100644 --- a/src/webrtc/call.ts +++ b/src/webrtc/call.ts @@ -815,6 +815,65 @@ export class MatrixCall extends EventEmitter { return Boolean(this.screenSharingStream); } + /** + * Starts/stops screensharing + * @param enabled the desired screensharing state + * @param selectDesktopCapturerSource callBack to select a screensharing stream on desktop + * @returns {boolean} new screensharing state + */ + public async setScreensharingEnabled( + enabled: boolean, + selectDesktopCapturerSource?: () => Promise, + ) { + if (!this.opponentSupportsSDPStreamMetadata) { + logger.warn("The other side doesn't support multiple stream, therefore we can't screenshare"); + // TODO: Use replaceTrack() + return false; + } + + logger.debug(`Set screensharing enabled? ${enabled}`); + if (enabled) { + if (this.isScreensharing()) { + logger.warn(`There is already a screensharing stream - there is nothing to do!`); + return false; + } + + try { + const screenshareConstraints = await getScreenshareContraints(selectDesktopCapturerSource); + if (!screenshareConstraints) { + logger.error("Failed to get screensharing constraints!"); + return; + } + + if (window.electron?.getDesktopCapturerSources) { + // We are using Electron + logger.debug("Getting screen stream using getUserMedia()..."); + this.screenSharingStream = await navigator.mediaDevices.getUserMedia(screenshareConstraints); + } else { + // We are not using Electron + logger.debug("Getting screen stream using getDisplayMedia()..."); + this.screenSharingStream = await navigator.mediaDevices.getDisplayMedia(screenshareConstraints); + } + + this.pushLocalFeed(this.screenSharingStream, SDPStreamMetadataPurpose.Screenshare); + return true; + } catch (err) { + this.emit(CallEvent.Error, + new CallError(CallErrorCode.NoUserMedia, "Failed to get screen-sharing stream: ", err), + ); + return false; + } + } else { + if (!this.isScreensharing()) { + logger.warn(`There already isn't a screensharing stream - there is nothing to do!`); + return false; + } + this.deleteLocalFeedByStream(this.screenSharingStream); + this.screenSharingStream = null; + return false; + } + } + /** * Set whether our outbound video should be muted or not. * @param {boolean} muted True to mute the outbound video. From 972aef7a9df60c9bb3cd0ffa2e125ea414b8badf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 8 May 2021 07:40:46 +0200 Subject: [PATCH 016/118] Merge feed delete methods and add sender arrays MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/webrtc/call.ts | 52 +++++++++++++++++++++------------------------- 1 file changed, 24 insertions(+), 28 deletions(-) diff --git a/src/webrtc/call.ts b/src/webrtc/call.ts index d8dd8aabf..59670933c 100644 --- a/src/webrtc/call.ts +++ b/src/webrtc/call.ts @@ -269,10 +269,14 @@ export class MatrixCall extends EventEmitter { private peerConn: RTCPeerConnection; private feeds: Array; private screenSharingStream: MediaStream; + // TODO: Rename to usermedia rather than AV for consistency private localAVStream: MediaStream; + private usermediaSenders: Array; + private screensharingSenders: Array; private inviteOrAnswerSent: boolean; private waitForLocalAVStream: boolean; // XXX: I don't know why this is called 'config'. + // XXX: Do we even needs this? Seems to be unused private config: MediaStreamConstraints; private successor: MatrixCall; private opponentMember: RoomMember; @@ -349,6 +353,9 @@ export class MatrixCall extends EventEmitter { this.vidMuted = false; this.feeds = []; + + this.usermediaSenders = []; + this.screensharingSenders = []; } /** @@ -549,9 +556,14 @@ export class MatrixCall extends EventEmitter { // why do we enable audio (and only audio) tracks here? -- matthew setTracksEnabled(stream.getAudioTracks(), true); + const senderArray = purpose === SDPStreamMetadataPurpose.Usermedia ? + this.usermediaSenders : this.screensharingSenders; + // Empty the array + senderArray.splice(0, senderArray.length); + for (const track of stream.getTracks()) { logger.info(`Adding track with id ${track.id} and with kind ${track.kind} to peer connection`) - this.peerConn.addTrack(track, stream); + senderArray.push(this.peerConn.addTrack(track, stream)); } } @@ -560,33 +572,10 @@ export class MatrixCall extends EventEmitter { this.emit(CallEvent.FeedsChanged, this.feeds); } - private deleteLocalFeedByStream(stream: MediaStream) { + private deleteFeedByStream(stream: MediaStream) { logger.debug(`Removing feed with stream id ${stream.id}`); - const feed = this.getLocalFeeds().find((feed) => feed.stream.id === stream.id); - if (!feed) { - logger.warn(`Didn't find the feed with stream id ${stream.id} to delete`); - return; - } - - this.feeds.splice(this.feeds.indexOf(feed), 1); - this.emit(CallEvent.FeedsChanged, this.feeds); - - for (const track of stream.getTracks()) { - // XXX: This is ugly and there has to be a way to do this more nicely - for (const sender of this.peerConn.getSenders()) { - if (sender.track?.id === track.id) { - this.peerConn.removeTrack(sender); - } - } - track.stop(); - } - } - - private deleteRemoteFeedByStream(stream: MediaStream) { - logger.debug(`Removing feed with stream id ${stream.id}`); - - const feed = this.getRemoteFeeds().find((feed) => feed.stream.id === stream.id); + const feed = this.feeds.find((feed) => feed.stream.id === stream.id); if (!feed) { logger.warn(`Didn't find the feed with stream id ${stream.id} to delete`); return; @@ -868,7 +857,14 @@ export class MatrixCall extends EventEmitter { logger.warn(`There already isn't a screensharing stream - there is nothing to do!`); return false; } - this.deleteLocalFeedByStream(this.screenSharingStream); + + for (const sender of this.screensharingSenders) { + this.peerConn.removeTrack(sender); + } + for (const track of this.screenSharingStream.getTracks()) { + track.stop(); + } + this.deleteFeedByStream(this.screenSharingStream); this.screenSharingStream = null; return false; } @@ -1510,7 +1506,7 @@ export class MatrixCall extends EventEmitter { const stream = ev.streams[0]; this.pushRemoteFeed(stream); - stream.addEventListener("removetrack", () => this.deleteRemoteFeedByStream(stream)); + stream.addEventListener("removetrack", () => this.deleteFeedByStream(stream)); }; onNegotiationNeeded = async () => { From 377ca0c6786ebffeb7b541d3605801b9c6f2d5c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 8 May 2021 08:01:56 +0200 Subject: [PATCH 017/118] Add getScreensharingStream() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/webrtc/call.ts | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/webrtc/call.ts b/src/webrtc/call.ts index 59670933c..42fe34297 100644 --- a/src/webrtc/call.ts +++ b/src/webrtc/call.ts @@ -1875,6 +1875,26 @@ export class MatrixCall extends EventEmitter { } } +async function getScreensharingStream( + selectDesktopCapturerSource?: () => Promise, +): Promise { + const screenshareConstraints = await getScreenshareContraints(selectDesktopCapturerSource); + if (!screenshareConstraints) { + logger.error("Failed to get screensharing constraints!"); + return; + } + + if (window.electron?.getDesktopCapturerSources) { + // We are using Electron + logger.debug("Getting screen stream using getUserMedia()..."); + return await navigator.mediaDevices.getUserMedia(screenshareConstraints); + } else { + // We are not using Electron + logger.debug("Getting screen stream using getDisplayMedia()..."); + return await navigator.mediaDevices.getDisplayMedia(screenshareConstraints); + } +} + function setTracksEnabled(tracks: Array, enabled: boolean) { for (let i = 0; i < tracks.length; i++) { tracks[i].enabled = enabled; From e9b802deb3e5c9d9dcdba0c1e82b8a8268f782d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 8 May 2021 08:03:45 +0200 Subject: [PATCH 018/118] Use getScreensharingStream() in setScreensharingEnabled() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/webrtc/call.ts | 34 ++++++++++++++++++++++++++++------ 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/src/webrtc/call.ts b/src/webrtc/call.ts index 42fe34297..b09ed8aae 100644 --- a/src/webrtc/call.ts +++ b/src/webrtc/call.ts @@ -824,15 +824,37 @@ export class MatrixCall extends EventEmitter { if (enabled) { if (this.isScreensharing()) { logger.warn(`There is already a screensharing stream - there is nothing to do!`); - return false; + return true; } try { - const screenshareConstraints = await getScreenshareContraints(selectDesktopCapturerSource); - if (!screenshareConstraints) { - logger.error("Failed to get screensharing constraints!"); - return; - } + this.screenSharingStream = await getScreensharingStream(selectDesktopCapturerSource); + if (!this.screenSharingStream) return false; + this.pushLocalFeed(this.screenSharingStream, SDPStreamMetadataPurpose.Screenshare); + return true; + } catch (err) { + this.emit(CallEvent.Error, + new CallError(CallErrorCode.NoUserMedia, "Failed to get screen-sharing stream: ", err), + ); + return false; + } + } else { + if (!this.isScreensharing()) { + logger.warn(`There already isn't a screensharing stream - there is nothing to do!`); + return false; + } + + for (const sender of this.screensharingSenders) { + this.peerConn.removeTrack(sender); + } + for (const track of this.screenSharingStream.getTracks()) { + track.stop(); + } + this.deleteFeedByStream(this.screenSharingStream); + this.screenSharingStream = null; + return false; + } + } if (window.electron?.getDesktopCapturerSources) { // We are using Electron From cbc74815d89c610d88e9215131702299a6d35aef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 8 May 2021 08:46:29 +0200 Subject: [PATCH 019/118] Use getScreensharingStream() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/webrtc/call.ts | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/src/webrtc/call.ts b/src/webrtc/call.ts index b09ed8aae..729089896 100644 --- a/src/webrtc/call.ts +++ b/src/webrtc/call.ts @@ -392,22 +392,12 @@ export class MatrixCall extends EventEmitter { logger.debug("placeScreenSharingCall"); this.checkForErrorListener(); try { - const screenshareConstraints = await getScreenshareContraints(selectDesktopCapturerSource); - if (!screenshareConstraints) { + this.screenSharingStream = await getScreensharingStream(selectDesktopCapturerSource); + if (!this.screenSharingStream) { this.terminate(CallParty.Local, CallErrorCode.NoUserMedia, false); return; } - if (window.electron?.getDesktopCapturerSources) { - // We are using Electron - logger.debug("Getting screen stream using getUserMedia()..."); - this.screenSharingStream = await navigator.mediaDevices.getUserMedia(screenshareConstraints); - } else { - // We are not using Electron - logger.debug("Getting screen stream using getDisplayMedia()..."); - this.screenSharingStream = await navigator.mediaDevices.getDisplayMedia(screenshareConstraints); - } - logger.debug("Got screen stream, requesting audio stream..."); const audioConstraints = getUserMediaContraints(ConstraintsType.Audio); this.placeCallWithConstraints(audioConstraints); From d250e7387cdf1559749a61f7aca2a06f71b38235 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 8 May 2021 09:02:01 +0200 Subject: [PATCH 020/118] Merge screenshare track into usermedia stream MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/webrtc/call.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/webrtc/call.ts b/src/webrtc/call.ts index 729089896..9677f6e61 100644 --- a/src/webrtc/call.ts +++ b/src/webrtc/call.ts @@ -1025,8 +1025,11 @@ export class MatrixCall extends EventEmitter { logger.debug( "Setting screen sharing stream to the local video element", ); + const videoTrack = this.screenSharingStream.getTracks().find((track) => track.kind === "video"); + this.localAVStream.addTrack(videoTrack); + // XXX: Because this is to support backwards compatibility we pretend like this stream is usermedia - this.pushLocalFeed(this.screenSharingStream, SDPStreamMetadataPurpose.Usermedia); + this.pushLocalFeed(this.localAVStream, SDPStreamMetadataPurpose.Usermedia); } else { this.pushLocalFeed(stream, SDPStreamMetadataPurpose.Usermedia); } From 82c530da95d7ecf28f6e9015425905dec0db6e67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 8 May 2021 09:08:06 +0200 Subject: [PATCH 021/118] Add setScreensharingEnabledWithoutMetadataSupport as a fallback() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/webrtc/call.ts | 56 ++++++++++++++++++++++++++++++++-------------- 1 file changed, 39 insertions(+), 17 deletions(-) diff --git a/src/webrtc/call.ts b/src/webrtc/call.ts index 9677f6e61..cc4d7a95e 100644 --- a/src/webrtc/call.ts +++ b/src/webrtc/call.ts @@ -805,9 +805,7 @@ export class MatrixCall extends EventEmitter { selectDesktopCapturerSource?: () => Promise, ) { if (!this.opponentSupportsSDPStreamMetadata) { - logger.warn("The other side doesn't support multiple stream, therefore we can't screenshare"); - // TODO: Use replaceTrack() - return false; + return await this.setScreensharingEnabledWithoutMetadataSupport(enabled, selectDesktopCapturerSource); } logger.debug(`Set screensharing enabled? ${enabled}`); @@ -846,17 +844,36 @@ export class MatrixCall extends EventEmitter { } } - if (window.electron?.getDesktopCapturerSources) { - // We are using Electron - logger.debug("Getting screen stream using getUserMedia()..."); - this.screenSharingStream = await navigator.mediaDevices.getUserMedia(screenshareConstraints); - } else { - // We are not using Electron - logger.debug("Getting screen stream using getDisplayMedia()..."); - this.screenSharingStream = await navigator.mediaDevices.getDisplayMedia(screenshareConstraints); - } + /** + * Starts/stops screensharing + * Should be used ONLY if the opponent doesn't support SDPStreamMetadata + * @param enabled the desired screensharing state + * @param selectDesktopCapturerSource callBack to select a screensharing stream on desktop + * @returns {boolean} new screensharing state + */ + private async setScreensharingEnabledWithoutMetadataSupport( + enabled: boolean, + selectDesktopCapturerSource?: () => Promise, + ) { + logger.debug(`Set screensharing enabled? ${enabled} using replaceTrack()`); + if (enabled) { + if (this.isScreensharing()) { + logger.warn(`There is already a screensharing stream - there is nothing to do!`); + return true; + } + + try { + this.screenSharingStream = await getScreensharingStream(selectDesktopCapturerSource); + if (!this.screenSharingStream) return false; + + const videoScreensharingTrack = this.screenSharingStream.getTracks().find((track) => { + return track.kind === "video"; + }); + const userMediaVideoSender = this.usermediaSenders.find((sender) => { + return sender.track?.kind === "video"; + }); + userMediaVideoSender.replaceTrack(videoScreensharingTrack); - this.pushLocalFeed(this.screenSharingStream, SDPStreamMetadataPurpose.Screenshare); return true; } catch (err) { this.emit(CallEvent.Error, @@ -870,14 +887,19 @@ export class MatrixCall extends EventEmitter { return false; } - for (const sender of this.screensharingSenders) { - this.peerConn.removeTrack(sender); - } + const videoScreensharingTrack = this.localAVStream.getTracks().find((track) => { + return track.kind === "video"; + }); + const userMediaVideoSender = this.usermediaSenders.find((sender) => { + return sender.track?.kind === "video"; + }); + userMediaVideoSender.replaceTrack(videoScreensharingTrack); + for (const track of this.screenSharingStream.getTracks()) { track.stop(); } - this.deleteFeedByStream(this.screenSharingStream); this.screenSharingStream = null; + return false; } } From fc68bb3ae0256a5f37d606ed200dc21f58b5bde8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 8 May 2021 09:29:31 +0200 Subject: [PATCH 022/118] Add ()!!! MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/webrtc/call.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/webrtc/call.ts b/src/webrtc/call.ts index cc4d7a95e..bb302b0ee 100644 --- a/src/webrtc/call.ts +++ b/src/webrtc/call.ts @@ -804,7 +804,7 @@ export class MatrixCall extends EventEmitter { enabled: boolean, selectDesktopCapturerSource?: () => Promise, ) { - if (!this.opponentSupportsSDPStreamMetadata) { + if (!this.opponentSupportsSDPStreamMetadata()) { return await this.setScreensharingEnabledWithoutMetadataSupport(enabled, selectDesktopCapturerSource); } From df28d87d253c11664c7e0acc3072240d703b4a5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 8 May 2021 09:29:41 +0200 Subject: [PATCH 023/118] Remove log line MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/webrtc/call.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/webrtc/call.ts b/src/webrtc/call.ts index bb302b0ee..f8b5796c3 100644 --- a/src/webrtc/call.ts +++ b/src/webrtc/call.ts @@ -790,7 +790,6 @@ export class MatrixCall extends EventEmitter { * @returns {boolean} is screensharing */ public isScreensharing(): boolean { - console.log("LOG stream", this.screenSharingStream); return Boolean(this.screenSharingStream); } @@ -893,6 +892,9 @@ export class MatrixCall extends EventEmitter { const userMediaVideoSender = this.usermediaSenders.find((sender) => { return sender.track?.kind === "video"; }); + if (!userMediaVideoSender) { + this.peerConn.removeTrack(userMediaVideoSender); + } userMediaVideoSender.replaceTrack(videoScreensharingTrack); for (const track of this.screenSharingStream.getTracks()) { From fa3b246de53f1e505b82bbf7723775590e9f2f5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 8 May 2021 10:00:59 +0200 Subject: [PATCH 024/118] Add addToPeerConnection param MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/webrtc/call.ts | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/webrtc/call.ts b/src/webrtc/call.ts index f8b5796c3..d7b5adab1 100644 --- a/src/webrtc/call.ts +++ b/src/webrtc/call.ts @@ -531,7 +531,7 @@ export class MatrixCall extends EventEmitter { logger.info(`Pushed remote stream with id ${stream.id}. Stream active? ${stream.active}`); } - private pushLocalFeed(stream: MediaStream, purpose: SDPStreamMetadataPurpose) { + private pushLocalFeed(stream: MediaStream, purpose: SDPStreamMetadataPurpose, addToPeerConnection = true) { const userId = this.client.getUserId(); // We try to replace an existing feed if there already is one with the same purpose @@ -546,14 +546,16 @@ export class MatrixCall extends EventEmitter { // why do we enable audio (and only audio) tracks here? -- matthew setTracksEnabled(stream.getAudioTracks(), true); - const senderArray = purpose === SDPStreamMetadataPurpose.Usermedia ? - this.usermediaSenders : this.screensharingSenders; - // Empty the array - senderArray.splice(0, senderArray.length); + if (addToPeerConnection) { + const senderArray = purpose === SDPStreamMetadataPurpose.Usermedia ? + this.usermediaSenders : this.screensharingSenders; + // Empty the array + senderArray.splice(0, senderArray.length); - for (const track of stream.getTracks()) { - logger.info(`Adding track with id ${track.id} and with kind ${track.kind} to peer connection`) - senderArray.push(this.peerConn.addTrack(track, stream)); + for (const track of stream.getTracks()) { + logger.info(`Adding track with id ${track.id} and with kind ${track.kind} to peer connection`) + senderArray.push(this.peerConn.addTrack(track, stream)); + } } } From d0707e183d1dfa61449e4696b41c0cb69b3a8ba9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 8 May 2021 10:02:27 +0200 Subject: [PATCH 025/118] Make shift-click work again MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is VERY ugly but it works Signed-off-by: Šimon Brandner --- src/webrtc/call.ts | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/src/webrtc/call.ts b/src/webrtc/call.ts index d7b5adab1..7950e14df 100644 --- a/src/webrtc/call.ts +++ b/src/webrtc/call.ts @@ -399,7 +399,7 @@ export class MatrixCall extends EventEmitter { } logger.debug("Got screen stream, requesting audio stream..."); - const audioConstraints = getUserMediaContraints(ConstraintsType.Audio); + const audioConstraints = getUserMediaContraints(ConstraintsType.Video); this.placeCallWithConstraints(audioConstraints); } catch (err) { this.emit(CallEvent.Error, @@ -875,6 +875,8 @@ export class MatrixCall extends EventEmitter { }); userMediaVideoSender.replaceTrack(videoScreensharingTrack); + this.pushLocalFeed(this.screenSharingStream, SDPStreamMetadataPurpose.Screenshare, false); + return true; } catch (err) { this.emit(CallEvent.Error, @@ -888,20 +890,18 @@ export class MatrixCall extends EventEmitter { return false; } - const videoScreensharingTrack = this.localAVStream.getTracks().find((track) => { + const track = this.localAVStream.getTracks().find((track) => { return track.kind === "video"; }); - const userMediaVideoSender = this.usermediaSenders.find((sender) => { + const sender = this.usermediaSenders.find((sender) => { return sender.track?.kind === "video"; }); - if (!userMediaVideoSender) { - this.peerConn.removeTrack(userMediaVideoSender); - } - userMediaVideoSender.replaceTrack(videoScreensharingTrack); + sender.replaceTrack(track); for (const track of this.screenSharingStream.getTracks()) { track.stop(); } + this.deleteFeedByStream(this.screenSharingStream); this.screenSharingStream = null; return false; @@ -1051,11 +1051,17 @@ export class MatrixCall extends EventEmitter { logger.debug( "Setting screen sharing stream to the local video element", ); - const videoTrack = this.screenSharingStream.getTracks().find((track) => track.kind === "video"); - this.localAVStream.addTrack(videoTrack); - // XXX: Because this is to support backwards compatibility we pretend like this stream is usermedia + this.pushLocalFeed(this.screenSharingStream, SDPStreamMetadataPurpose.Screenshare, false); this.pushLocalFeed(this.localAVStream, SDPStreamMetadataPurpose.Usermedia); + + const videoScreensharingTrack = this.screenSharingStream.getTracks().find((track) => { + return track.kind === "video"; + }); + const userMediaVideoSender = this.usermediaSenders.find((sender) => { + return sender.track?.kind === "video"; + }); + userMediaVideoSender.replaceTrack(videoScreensharingTrack); } else { this.pushLocalFeed(stream, SDPStreamMetadataPurpose.Usermedia); } From 7b333a34b56e99b124427d13226c990a3162eeee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 8 May 2021 14:16:30 +0200 Subject: [PATCH 026/118] Warn level MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/webrtc/call.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/webrtc/call.ts b/src/webrtc/call.ts index 7950e14df..8022df6e6 100644 --- a/src/webrtc/call.ts +++ b/src/webrtc/call.ts @@ -1260,7 +1260,7 @@ export class MatrixCall extends EventEmitter { if (sdpStreamMetadata) { this.remoteSDPStreamMetadata = sdpStreamMetadata; } else { - logger.debug("Did not get any SDPStreamMetadata! Can not send/receive multiple streams"); + logger.warn("Did not get any SDPStreamMetadata! Can not send/receive multiple streams"); } try { From 18580624e6b9c9711d964e770adb0a66b3878200 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 8 May 2021 14:16:43 +0200 Subject: [PATCH 027/118] Use opponentSupportsSDPStreamMetadata() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/webrtc/call.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/webrtc/call.ts b/src/webrtc/call.ts index 8022df6e6..b3210d87a 100644 --- a/src/webrtc/call.ts +++ b/src/webrtc/call.ts @@ -474,7 +474,7 @@ export class MatrixCall extends EventEmitter { private pushRemoteFeed(stream: MediaStream) { // Fallback to old behavior if the other side doesn't support SDPStreamMetadata - if (!this.remoteSDPStreamMetadata) { + if (!this.opponentSupportsSDPStreamMetadata()) { this.pushRemoteFeedWithoutMetadata(stream); return; } From c6764490c618e9a0916ed497e8b66e48cd1c2188 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 8 May 2021 14:17:10 +0200 Subject: [PATCH 028/118] Use for loop MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/webrtc/call.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/webrtc/call.ts b/src/webrtc/call.ts index b3210d87a..d2b33eb53 100644 --- a/src/webrtc/call.ts +++ b/src/webrtc/call.ts @@ -455,11 +455,11 @@ export class MatrixCall extends EventEmitter { */ private getLocalSDPStreamMetadata(): SDPStreamMetadata { const metadata = {}; - this.getLocalFeeds().map((localFeed) => { + for (const localFeed of this.getLocalFeeds()) { metadata[localFeed.stream.id] = { purpose: localFeed.purpose, }; - }); + } return metadata; } From b67cd94ee2f4ac495885adc5c5ccffe339904bcf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 8 May 2021 15:39:11 +0200 Subject: [PATCH 029/118] Jest: should map SDPStreamMetadata to feeds MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- spec/unit/webrtc/call.spec.ts | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/spec/unit/webrtc/call.spec.ts b/spec/unit/webrtc/call.spec.ts index e5ef6de87..65f4e7a02 100644 --- a/spec/unit/webrtc/call.spec.ts +++ b/spec/unit/webrtc/call.spec.ts @@ -16,6 +16,7 @@ limitations under the License. import {TestClient} from '../../TestClient'; import {MatrixCall, CallErrorCode, CallEvent} from '../../../src/webrtc/call'; +import { SDPStreamMetadataKey, SDPStreamMetadataPurpose } from '../../../src/webrtc/callEventTypes'; const DUMMY_SDP = ( "v=0\r\n" + @@ -298,4 +299,33 @@ describe('Call', function() { // Hangup to stop timers call.hangup(CallErrorCode.UserHangup, true); }); + + it("should map SDPStreamMetadata to feeds", async () => { + const callPromise = call.placeVoiceCall(); + await client.httpBackend.flush(); + await callPromise; + + call.getOpponentMember = () => {return {userId: "@bob:bar.uk"}}; + + await call.onAnswerReceived({ + getContent: () => { + return { + version: 1, + call_id: call.callId, + party_id: 'party_id', + answer: { + sdp: DUMMY_SDP, + }, + [SDPStreamMetadataKey]: { + "stream_id": { + purpose: SDPStreamMetadataPurpose.Usermedia, + } + } + }; + }, + }); + + call.pushRemoteFeed({id: "stream_id"}); + expect(call.getFeeds().find((feed) => feed.stream.id === "stream_id")?.purpose).toBe(SDPStreamMetadataPurpose.Usermedia); + }); }); From 1e0d6b9d4aef1327b62d5761cf0088609cab7761 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 8 May 2021 15:50:20 +0200 Subject: [PATCH 030/118] Jest: should fallback to replaceTrack() if the other side doesn't support SPDStreamMetadata MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- spec/unit/webrtc/call.spec.ts | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/spec/unit/webrtc/call.spec.ts b/spec/unit/webrtc/call.spec.ts index 65f4e7a02..6a5955f1a 100644 --- a/spec/unit/webrtc/call.spec.ts +++ b/spec/unit/webrtc/call.spec.ts @@ -17,6 +17,7 @@ limitations under the License. import {TestClient} from '../../TestClient'; import {MatrixCall, CallErrorCode, CallEvent} from '../../../src/webrtc/call'; import { SDPStreamMetadataKey, SDPStreamMetadataPurpose } from '../../../src/webrtc/callEventTypes'; +import { isMainThread } from 'worker_threads'; const DUMMY_SDP = ( "v=0\r\n" + @@ -328,4 +329,30 @@ describe('Call', function() { call.pushRemoteFeed({id: "stream_id"}); expect(call.getFeeds().find((feed) => feed.stream.id === "stream_id")?.purpose).toBe(SDPStreamMetadataPurpose.Usermedia); }); + + it("should fallback to replaceTrack() if the other side doesn't support SPDStreamMetadata", async () => { + const callPromise = call.placeVoiceCall(); + await client.httpBackend.flush(); + await callPromise; + + call.getOpponentMember = () => {return {userId: "@bob:bar.uk"}}; + + await call.onAnswerReceived({ + getContent: () => { + return { + version: 1, + call_id: call.callId, + party_id: 'party_id', + answer: { + sdp: DUMMY_SDP, + }, + }; + }, + }); + + call.setScreensharingEnabledWithoutMetadataSupport = jest.fn(); + + call.setScreensharingEnabled(true); + expect(call.setScreensharingEnabledWithoutMetadataSupport).toHaveBeenCalled(); + }); }); From 27c172361f059d90620d1d54215b7b19f855c0a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 8 May 2021 20:03:47 +0200 Subject: [PATCH 031/118] Add a log line to pushLocalFeed() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/webrtc/call.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/webrtc/call.ts b/src/webrtc/call.ts index d2b33eb53..0b583caff 100644 --- a/src/webrtc/call.ts +++ b/src/webrtc/call.ts @@ -557,6 +557,8 @@ export class MatrixCall extends EventEmitter { senderArray.push(this.peerConn.addTrack(track, stream)); } } + + logger.info(`Pushed local stream with id ${stream.id}. Stream active? ${stream.active}`); } private deleteAllFeeds() { From 16f569136b87d50389243dd1d978402c5b6ac146 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sun, 9 May 2021 18:48:08 +0200 Subject: [PATCH 032/118] Simplifie and avoid repetation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/webrtc/call.ts | 30 ++++++++++-------------------- 1 file changed, 10 insertions(+), 20 deletions(-) diff --git a/src/webrtc/call.ts b/src/webrtc/call.ts index 0b583caff..4ed6058ab 100644 --- a/src/webrtc/call.ts +++ b/src/webrtc/call.ts @@ -807,17 +807,22 @@ export class MatrixCall extends EventEmitter { enabled: boolean, selectDesktopCapturerSource?: () => Promise, ) { + // Skip if there is nothing to do + if (enabled && this.isScreensharing()) { + logger.warn(`There is already a screensharing stream - there is nothing to do!`); + return true; + } else if (!enabled && !this.isScreensharing()) { + logger.warn(`There already isn't a screensharing stream - there is nothing to do!`); + return false; + } + + // Fallback to replaceTrack() if (!this.opponentSupportsSDPStreamMetadata()) { return await this.setScreensharingEnabledWithoutMetadataSupport(enabled, selectDesktopCapturerSource); } logger.debug(`Set screensharing enabled? ${enabled}`); if (enabled) { - if (this.isScreensharing()) { - logger.warn(`There is already a screensharing stream - there is nothing to do!`); - return true; - } - try { this.screenSharingStream = await getScreensharingStream(selectDesktopCapturerSource); if (!this.screenSharingStream) return false; @@ -830,11 +835,6 @@ export class MatrixCall extends EventEmitter { return false; } } else { - if (!this.isScreensharing()) { - logger.warn(`There already isn't a screensharing stream - there is nothing to do!`); - return false; - } - for (const sender of this.screensharingSenders) { this.peerConn.removeTrack(sender); } @@ -860,11 +860,6 @@ export class MatrixCall extends EventEmitter { ) { logger.debug(`Set screensharing enabled? ${enabled} using replaceTrack()`); if (enabled) { - if (this.isScreensharing()) { - logger.warn(`There is already a screensharing stream - there is nothing to do!`); - return true; - } - try { this.screenSharingStream = await getScreensharingStream(selectDesktopCapturerSource); if (!this.screenSharingStream) return false; @@ -887,11 +882,6 @@ export class MatrixCall extends EventEmitter { return false; } } else { - if (!this.isScreensharing()) { - logger.warn(`There already isn't a screensharing stream - there is nothing to do!`); - return false; - } - const track = this.localAVStream.getTracks().find((track) => { return track.kind === "video"; }); From ff60bbac9d54883598302bc584dce270f5285537 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Mon, 10 May 2021 07:35:25 +0200 Subject: [PATCH 033/118] Remove import that was a mistake MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- spec/unit/webrtc/call.spec.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/spec/unit/webrtc/call.spec.ts b/spec/unit/webrtc/call.spec.ts index 6a5955f1a..d1af1a770 100644 --- a/spec/unit/webrtc/call.spec.ts +++ b/spec/unit/webrtc/call.spec.ts @@ -17,7 +17,6 @@ limitations under the License. import {TestClient} from '../../TestClient'; import {MatrixCall, CallErrorCode, CallEvent} from '../../../src/webrtc/call'; import { SDPStreamMetadataKey, SDPStreamMetadataPurpose } from '../../../src/webrtc/callEventTypes'; -import { isMainThread } from 'worker_threads'; const DUMMY_SDP = ( "v=0\r\n" + From a13cf0e1e05db9793f64a6cff75d846e3fd61969 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Mon, 10 May 2021 12:27:21 +0200 Subject: [PATCH 034/118] Remove placeScreenSharingCall() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This method is quite problematic and doesn't have any benefits Signed-off-by: Šimon Brandner --- src/webrtc/call.ts | 31 ------------------------------- 1 file changed, 31 deletions(-) diff --git a/src/webrtc/call.ts b/src/webrtc/call.ts index 4ed6058ab..b22fbdff2 100644 --- a/src/webrtc/call.ts +++ b/src/webrtc/call.ts @@ -382,37 +382,6 @@ export class MatrixCall extends EventEmitter { await this.placeCallWithConstraints(constraints); } - /** - * Place a screen-sharing call to this room. This includes audio. - * This method is EXPERIMENTAL and subject to change without warning. It - * only works in Google Chrome and Firefox >= 44. - * @throws If you have not specified a listener for 'error' events. - */ - async placeScreenSharingCall(selectDesktopCapturerSource?: () => Promise) { - logger.debug("placeScreenSharingCall"); - this.checkForErrorListener(); - try { - this.screenSharingStream = await getScreensharingStream(selectDesktopCapturerSource); - if (!this.screenSharingStream) { - this.terminate(CallParty.Local, CallErrorCode.NoUserMedia, false); - return; - } - - logger.debug("Got screen stream, requesting audio stream..."); - const audioConstraints = getUserMediaContraints(ConstraintsType.Video); - this.placeCallWithConstraints(audioConstraints); - } catch (err) { - this.emit(CallEvent.Error, - new CallError( - CallErrorCode.NoUserMedia, - "Failed to get screen-sharing stream: ", err, - ), - ); - this.terminate(CallParty.Local, CallErrorCode.NoUserMedia, false); - } - this.type = CallType.Video; - } - public getOpponentMember() { return this.opponentMember; } From f2c215311f130163d3ce2091aa161b4f8e6a6bb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Mon, 10 May 2021 13:01:06 +0200 Subject: [PATCH 035/118] If we can't get constraints don't error MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We do this because it could mean the user just hasn't selected a window/screen Signed-off-by: Šimon Brandner --- src/webrtc/call.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/webrtc/call.ts b/src/webrtc/call.ts index b22fbdff2..07cf80ff5 100644 --- a/src/webrtc/call.ts +++ b/src/webrtc/call.ts @@ -1887,10 +1887,7 @@ async function getScreensharingStream( selectDesktopCapturerSource?: () => Promise, ): Promise { const screenshareConstraints = await getScreenshareContraints(selectDesktopCapturerSource); - if (!screenshareConstraints) { - logger.error("Failed to get screensharing constraints!"); - return; - } + if (!screenshareConstraints) return null; if (window.electron?.getDesktopCapturerSources) { // We are using Electron From 61e7d4f807e791ad147cb6879fb0eb947425db41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Mon, 10 May 2021 18:22:06 +0200 Subject: [PATCH 036/118] Remove some leftovers from placeScreensharingCall() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/webrtc/call.ts | 25 +++---------------------- 1 file changed, 3 insertions(+), 22 deletions(-) diff --git a/src/webrtc/call.ts b/src/webrtc/call.ts index 07cf80ff5..ecd4e167e 100644 --- a/src/webrtc/call.ts +++ b/src/webrtc/call.ts @@ -1001,32 +1001,13 @@ export class MatrixCall extends EventEmitter { this.stopAllMedia(); return; } - this.localAVStream = stream; - logger.info("Got local AV stream with id " + this.localAVStream.id); + this.localAVStream = stream; + this.pushLocalFeed(stream, SDPStreamMetadataPurpose.Usermedia); this.setState(CallState.CreateOffer); + logger.info("Got local AV stream with id " + this.localAVStream.id); logger.debug("gotUserMediaForInvite -> " + this.type); - - if (this.screenSharingStream) { - logger.debug( - "Setting screen sharing stream to the local video element", - ); - - this.pushLocalFeed(this.screenSharingStream, SDPStreamMetadataPurpose.Screenshare, false); - this.pushLocalFeed(this.localAVStream, SDPStreamMetadataPurpose.Usermedia); - - const videoScreensharingTrack = this.screenSharingStream.getTracks().find((track) => { - return track.kind === "video"; - }); - const userMediaVideoSender = this.usermediaSenders.find((sender) => { - return sender.track?.kind === "video"; - }); - userMediaVideoSender.replaceTrack(videoScreensharingTrack); - } else { - this.pushLocalFeed(stream, SDPStreamMetadataPurpose.Usermedia); - } - // Now we wait for the negotiationneeded event }; From 582aafa552183b225009a38667f8c0bbfb61b6a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Mon, 10 May 2021 19:12:33 +0200 Subject: [PATCH 037/118] Simpler naming MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/webrtc/call.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/webrtc/call.ts b/src/webrtc/call.ts index ecd4e167e..d2d4b912b 100644 --- a/src/webrtc/call.ts +++ b/src/webrtc/call.ts @@ -833,13 +833,13 @@ export class MatrixCall extends EventEmitter { this.screenSharingStream = await getScreensharingStream(selectDesktopCapturerSource); if (!this.screenSharingStream) return false; - const videoScreensharingTrack = this.screenSharingStream.getTracks().find((track) => { + const track = this.screenSharingStream.getTracks().find((track) => { return track.kind === "video"; }); - const userMediaVideoSender = this.usermediaSenders.find((sender) => { + const sender = this.usermediaSenders.find((sender) => { return sender.track?.kind === "video"; }); - userMediaVideoSender.replaceTrack(videoScreensharingTrack); + sender.replaceTrack(track); this.pushLocalFeed(this.screenSharingStream, SDPStreamMetadataPurpose.Screenshare, false); From 2596c25ccc7dc8bd477c40b0461643c3e8fbf401 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Mon, 10 May 2021 19:17:07 +0200 Subject: [PATCH 038/118] Add missing semicolon MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/webrtc/call.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/webrtc/call.ts b/src/webrtc/call.ts index d2d4b912b..f9e1924de 100644 --- a/src/webrtc/call.ts +++ b/src/webrtc/call.ts @@ -1356,7 +1356,7 @@ export class MatrixCall extends EventEmitter { } catch (err) { logger.debug("Error setting local description!", err); this.terminate(CallParty.Local, CallErrorCode.SetLocalDescription, true); - return + return; } if (this.peerConn.iceGatheringState === 'gathering') { From 00a28e743d64383ad63f2abdf4121362452371a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Mon, 10 May 2021 19:18:39 +0200 Subject: [PATCH 039/118] Move track.stop() to deleteFeedByStream() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/webrtc/call.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/webrtc/call.ts b/src/webrtc/call.ts index f9e1924de..a03572375 100644 --- a/src/webrtc/call.ts +++ b/src/webrtc/call.ts @@ -546,6 +546,10 @@ export class MatrixCall extends EventEmitter { this.feeds.splice(this.feeds.indexOf(feed), 1); this.emit(CallEvent.FeedsChanged, this.feeds); + + for (const track of stream.getTracks()) { + track.stop(); + } } // The typescript definitions have this type as 'any' :( @@ -807,9 +811,6 @@ export class MatrixCall extends EventEmitter { for (const sender of this.screensharingSenders) { this.peerConn.removeTrack(sender); } - for (const track of this.screenSharingStream.getTracks()) { - track.stop(); - } this.deleteFeedByStream(this.screenSharingStream); this.screenSharingStream = null; return false; @@ -859,9 +860,6 @@ export class MatrixCall extends EventEmitter { }); sender.replaceTrack(track); - for (const track of this.screenSharingStream.getTracks()) { - track.stop(); - } this.deleteFeedByStream(this.screenSharingStream); this.screenSharingStream = null; From 5a8e5a9785a3f21b5684f91ba6c6308ae2b22bb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Thu, 13 May 2021 13:43:44 +0200 Subject: [PATCH 040/118] Log local SDPStreamMetadata MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/webrtc/call.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/webrtc/call.ts b/src/webrtc/call.ts index a03572375..9f8b035c0 100644 --- a/src/webrtc/call.ts +++ b/src/webrtc/call.ts @@ -429,6 +429,7 @@ export class MatrixCall extends EventEmitter { purpose: localFeed.purpose, }; } + logger.debug("Got local SDPStreamMetadata", metadata); return metadata; } From 44fc820f99044a0d1f3527fd7466bb4847226945 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Thu, 13 May 2021 18:12:48 +0200 Subject: [PATCH 041/118] Make feed pushing methods more verbose MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/webrtc/call.ts | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/webrtc/call.ts b/src/webrtc/call.ts index 9f8b035c0..10f1d7c35 100644 --- a/src/webrtc/call.ts +++ b/src/webrtc/call.ts @@ -467,7 +467,7 @@ export class MatrixCall extends EventEmitter { this.emit(CallEvent.FeedsChanged, this.feeds); } - logger.info(`Pushed remote stream with id ${stream.id}. Stream active? ${stream.active}`); + logger.info(`Pushed remote stream (id="${stream.id}", active="${stream.active}")`); } /** @@ -498,7 +498,7 @@ export class MatrixCall extends EventEmitter { this.emit(CallEvent.FeedsChanged, this.feeds); } - logger.info(`Pushed remote stream with id ${stream.id}. Stream active? ${stream.active}`); + logger.info(`Pushed remote stream (id="${stream.id}", active="${stream.active}")`); } private pushLocalFeed(stream: MediaStream, purpose: SDPStreamMetadataPurpose, addToPeerConnection = true) { @@ -522,13 +522,21 @@ export class MatrixCall extends EventEmitter { // Empty the array senderArray.splice(0, senderArray.length); + this.emit(CallEvent.FeedsChanged, this.feeds); for (const track of stream.getTracks()) { - logger.info(`Adding track with id ${track.id} and with kind ${track.kind} to peer connection`) + logger.info( + `Adding track (` + + `id="${track.id}", ` + + `kind="${track.kind}", ` + + `streamId="${stream.id}", ` + + `streamPurpose="${purpose}"` + + `) to peer connection`, + ) senderArray.push(this.peerConn.addTrack(track, stream)); } } - logger.info(`Pushed local stream with id ${stream.id}. Stream active? ${stream.active}`); + logger.info(`Pushed local stream (id="${stream.id}", active="${stream.active}", purpose="${purpose}")`); } private deleteAllFeeds() { From 5de189bfa3c97a0e66d107d51312f6bbac4e5773 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 15 May 2021 11:06:04 +0200 Subject: [PATCH 042/118] Improve logging in pushRemoteFeed() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/webrtc/call.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/webrtc/call.ts b/src/webrtc/call.ts index 10f1d7c35..fee073eb2 100644 --- a/src/webrtc/call.ts +++ b/src/webrtc/call.ts @@ -450,24 +450,24 @@ export class MatrixCall extends EventEmitter { } const userId = this.getOpponentMember().userId; - const streamMetadata = this.remoteSDPStreamMetadata[stream.id]; + const purpose = this.remoteSDPStreamMetadata[stream.id].purpose; - if (!streamMetadata) { + if (!purpose) { logger.warn(`Ignoring stream with id ${stream.id} because we didn't get any metadata about it`); return; } // Try to find a feed with the same purpose as the new stream, // if we find it replace the old stream with the new one - const existingFeed = this.getRemoteFeeds().find((feed) => feed.purpose === streamMetadata.purpose); + const existingFeed = this.getRemoteFeeds().find((feed) => feed.purpose === purpose); if (existingFeed) { existingFeed.setNewStream(stream); } else { - this.feeds.push(new CallFeed(stream, userId, streamMetadata.purpose, this.client, this.roomId)); + this.feeds.push(new CallFeed(stream, userId, purpose, this.client, this.roomId)); this.emit(CallEvent.FeedsChanged, this.feeds); } - logger.info(`Pushed remote stream (id="${stream.id}", active="${stream.active}")`); + logger.info(`Pushed remote stream (id="${stream.id}", active="${stream.active}", purpose=${purpose})`); } /** From 92e89ffbcf59992a62e963c2af0621f3c1b863f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Wed, 19 May 2021 21:06:35 +0200 Subject: [PATCH 043/118] Add getRidOfRTXCodecs() method MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/webrtc/call.ts | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/src/webrtc/call.ts b/src/webrtc/call.ts index fef2869bb..4b16972ce 100644 --- a/src/webrtc/call.ts +++ b/src/webrtc/call.ts @@ -1505,6 +1505,44 @@ export class MatrixCall extends EventEmitter { stream.addEventListener("removetrack", () => this.deleteFeedByStream(stream)); }; + /** + * This method removes all video/rtx codecs from video transceivers. + * This is necessary since they can cause problems. Without this the + * following steps should produce an error: + * Chromium calls Firefox + * Firefox answers + * Firefox starts screen-sharing + * Chromium starts screen-sharing + * Call crashes for Chromium with: + * [96685:23:0518/162603.933321:ERROR:webrtc_video_engine.cc(3296)] RTX codec (PT=97) mapped to PT=96 which is not in the codec list. + * [96685:23:0518/162603.933377:ERROR:webrtc_video_engine.cc(1171)] GetChangedRecvParameters called without any video codecs. + * [96685:23:0518/162603.933430:ERROR:sdp_offer_answer.cc(4302)] Failed to set local video description recv parameters for m-section with mid='2'. (INVALID_PARAMETER) + */ + private getRidOfRTXCodecs() { + // RTCRtpReceiver.getCapabilities and RTCRtpSender.getCapabilities don't seem to be supported on FF + if (RTCRtpReceiver.getCapabilities && RTCRtpSender.getCapabilities) { + const recvCodecs = RTCRtpReceiver.getCapabilities("video").codecs; + const sendCodecs = RTCRtpSender.getCapabilities("video").codecs; + const codecs = [...sendCodecs, ...recvCodecs]; + + for (const codec of codecs) { + if (codec.mimeType === "video/rtx") { + const rtxCodecIndex = codecs.indexOf(codec); + codecs.splice(rtxCodecIndex, 1); + } + } + + for (const trans of this.peerConn.getTransceivers()) { + if ( + trans.sender.track?.kind === "video" || + trans.receiver.track?.kind === "video" + ) { + trans.setCodecPreferences(codecs); + } + } + } + } + onNegotiationNeeded = async () => { logger.info("Negotation is needed!"); @@ -1515,6 +1553,7 @@ export class MatrixCall extends EventEmitter { this.makingOffer = true; try { + this.getRidOfRTXCodecs(); const myOffer = await this.peerConn.createOffer(); await this.gotLocalOffer(myOffer); } catch (e) { From b1459a43ef7d74cbffbd92db4a0ce360bde0730f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Thu, 20 May 2021 15:11:23 +0200 Subject: [PATCH 044/118] Also put getRidOfRTXCodecs() before createAnswer() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Just to be sure Signed-off-by: Šimon Brandner --- src/webrtc/call.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/webrtc/call.ts b/src/webrtc/call.ts index 4b16972ce..5be011e47 100644 --- a/src/webrtc/call.ts +++ b/src/webrtc/call.ts @@ -1080,6 +1080,7 @@ export class MatrixCall extends EventEmitter { let myAnswer; try { + this.getRidOfRTXCodecs(); myAnswer = await this.peerConn.createAnswer(); } catch (err) { logger.debug("Failed to create answer: ", err); @@ -1306,6 +1307,7 @@ export class MatrixCall extends EventEmitter { for (const tranceiver of this.peerConn.getTransceivers()) { tranceiver.direction = this.isRemoteOnHold() ? 'inactive' : 'sendrecv'; } + this.getRidOfRTXCodecs(); const localDescription = await this.peerConn.createAnswer(); await this.peerConn.setLocalDescription(localDescription); // Now we've got our answer, set the direction to the outcome of the negotiation. From a1a5d85979bd46ee5656555703e9fed57f4c248a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Thu, 20 May 2021 15:49:56 +0200 Subject: [PATCH 045/118] Stop tracks only if disabling screen-sharing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The other thing lead to usermedia tracks being stopped when on hold Signed-off-by: Šimon Brandner --- src/webrtc/call.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/webrtc/call.ts b/src/webrtc/call.ts index 5be011e47..af1a46c6c 100644 --- a/src/webrtc/call.ts +++ b/src/webrtc/call.ts @@ -555,10 +555,6 @@ export class MatrixCall extends EventEmitter { this.feeds.splice(this.feeds.indexOf(feed), 1); this.emit(CallEvent.FeedsChanged, this.feeds); - - for (const track of stream.getTracks()) { - track.stop(); - } } // The typescript definitions have this type as 'any' :( @@ -821,6 +817,9 @@ export class MatrixCall extends EventEmitter { this.peerConn.removeTrack(sender); } this.deleteFeedByStream(this.screenSharingStream); + for (const track of this.screensharingSenders.getTracks()) { + track.stop(); + } this.screenSharingStream = null; return false; } @@ -870,6 +869,9 @@ export class MatrixCall extends EventEmitter { sender.replaceTrack(track); this.deleteFeedByStream(this.screenSharingStream); + for (const track of this.screenSharingStream.getTracks()) { + track.stop(); + } this.screenSharingStream = null; return false; From db4c6af472437b2d469d4d119e8f91129ba30fcf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Thu, 20 May 2021 15:54:08 +0200 Subject: [PATCH 046/118] Fix an odd mistake MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/webrtc/call.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/webrtc/call.ts b/src/webrtc/call.ts index 3be346ff0..a288142c8 100644 --- a/src/webrtc/call.ts +++ b/src/webrtc/call.ts @@ -817,7 +817,7 @@ export class MatrixCall extends EventEmitter { this.peerConn.removeTrack(sender); } this.deleteFeedByStream(this.screenSharingStream); - for (const track of this.screensharingSenders.getTracks()) { + for (const track of this.screenSharingStream.getTracks()) { track.stop(); } this.screenSharingStream = null; From 43198b0425bfedc1eb14774214670013143d23f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Thu, 27 May 2021 18:41:48 +0200 Subject: [PATCH 047/118] Disable RTX only for screen-sharing transceivers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/webrtc/call.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/webrtc/call.ts b/src/webrtc/call.ts index aca130096..f25af4242 100644 --- a/src/webrtc/call.ts +++ b/src/webrtc/call.ts @@ -1509,9 +1509,9 @@ export class MatrixCall extends EventEmitter { }; /** - * This method removes all video/rtx codecs from video transceivers. - * This is necessary since they can cause problems. Without this the - * following steps should produce an error: + * This method removes all video/rtx codecs from screensharing video + * transceivers. This is necessary since they can cause problems. Without + * this the following steps should produce an error: * Chromium calls Firefox * Firefox answers * Firefox starts screen-sharing @@ -1537,8 +1537,11 @@ export class MatrixCall extends EventEmitter { for (const trans of this.peerConn.getTransceivers()) { if ( - trans.sender.track?.kind === "video" || - trans.receiver.track?.kind === "video" + this.screensharingSenders.includes(trans.sender) && + ( + trans.sender.track?.kind === "video" || + trans.receiver.track?.kind === "video" + ) ) { trans.setCodecPreferences(codecs); } From 75321220fd8b7a1cdd39d54db213b3ca4b6cb9f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Thu, 27 May 2021 18:49:02 +0200 Subject: [PATCH 048/118] Simplifie code - don't be an idiot MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/webrtc/call.ts | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/webrtc/call.ts b/src/webrtc/call.ts index f25af4242..821345cd4 100644 --- a/src/webrtc/call.ts +++ b/src/webrtc/call.ts @@ -1523,28 +1523,28 @@ export class MatrixCall extends EventEmitter { */ private getRidOfRTXCodecs() { // RTCRtpReceiver.getCapabilities and RTCRtpSender.getCapabilities don't seem to be supported on FF - if (RTCRtpReceiver.getCapabilities && RTCRtpSender.getCapabilities) { - const recvCodecs = RTCRtpReceiver.getCapabilities("video").codecs; - const sendCodecs = RTCRtpSender.getCapabilities("video").codecs; - const codecs = [...sendCodecs, ...recvCodecs]; + if (!RTCRtpReceiver.getCapabilities || !RTCRtpSender.getCapabilities) return; - for (const codec of codecs) { - if (codec.mimeType === "video/rtx") { - const rtxCodecIndex = codecs.indexOf(codec); - codecs.splice(rtxCodecIndex, 1); - } + const recvCodecs = RTCRtpReceiver.getCapabilities("video").codecs; + const sendCodecs = RTCRtpSender.getCapabilities("video").codecs; + const codecs = [...sendCodecs, ...recvCodecs]; + + for (const codec of codecs) { + if (codec.mimeType === "video/rtx") { + const rtxCodecIndex = codecs.indexOf(codec); + codecs.splice(rtxCodecIndex, 1); } + } - for (const trans of this.peerConn.getTransceivers()) { - if ( - this.screensharingSenders.includes(trans.sender) && + for (const trans of this.peerConn.getTransceivers()) { + if ( + this.screensharingSenders.includes(trans.sender) && ( trans.sender.track?.kind === "video" || trans.receiver.track?.kind === "video" ) - ) { - trans.setCodecPreferences(codecs); - } + ) { + trans.setCodecPreferences(codecs); } } } From 0a0489750c488bbdaad0552f8fac7920304adacb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 28 May 2021 17:37:55 +0200 Subject: [PATCH 049/118] Add missing space MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- spec/unit/webrtc/call.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/unit/webrtc/call.spec.ts b/spec/unit/webrtc/call.spec.ts index 885df977f..8b2a27a27 100644 --- a/spec/unit/webrtc/call.spec.ts +++ b/spec/unit/webrtc/call.spec.ts @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { TestClient} from '../../TestClient'; +import { TestClient } from '../../TestClient'; import { MatrixCall, CallErrorCode, CallEvent } from '../../../src/webrtc/call'; import { SDPStreamMetadataKey, SDPStreamMetadataPurpose } from '../../../src/webrtc/callEventTypes'; From 5724462c2cd82b1da9225cd5aa0a62992ee8f090 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 28 May 2021 17:45:52 +0200 Subject: [PATCH 050/118] Delint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- spec/unit/webrtc/call.spec.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/spec/unit/webrtc/call.spec.ts b/spec/unit/webrtc/call.spec.ts index 8b2a27a27..cf46053d2 100644 --- a/spec/unit/webrtc/call.spec.ts +++ b/spec/unit/webrtc/call.spec.ts @@ -305,7 +305,7 @@ describe('Call', function() { await client.httpBackend.flush(); await callPromise; - call.getOpponentMember = () => {return {userId: "@bob:bar.uk"}}; + call.getOpponentMember = () => {return { userId: "@bob:bar.uk" }}; await call.onAnswerReceived({ getContent: () => { @@ -319,14 +319,16 @@ describe('Call', function() { [SDPStreamMetadataKey]: { "stream_id": { purpose: SDPStreamMetadataPurpose.Usermedia, - } - } + }, + }, }; }, }); - call.pushRemoteFeed({id: "stream_id"}); - expect(call.getFeeds().find((feed) => feed.stream.id === "stream_id")?.purpose).toBe(SDPStreamMetadataPurpose.Usermedia); + call.pushRemoteFeed({ id: "stream_id" }); + expect(call.getFeeds().find((feed) => { + return feed.stream.id === "stream_id"; + })?.purpose).toBe(SDPStreamMetadataPurpose.Usermedia); }); it("should fallback to replaceTrack() if the other side doesn't support SPDStreamMetadata", async () => { @@ -334,7 +336,7 @@ describe('Call', function() { await client.httpBackend.flush(); await callPromise; - call.getOpponentMember = () => {return {userId: "@bob:bar.uk"}}; + call.getOpponentMember = () => {return { userId: "@bob:bar.uk" }}; await call.onAnswerReceived({ getContent: () => { From 0e05f9fd734bb63627cd186fd5dde029938d036d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 4 Jun 2021 11:41:35 +0200 Subject: [PATCH 051/118] Styling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- spec/unit/webrtc/call.spec.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/spec/unit/webrtc/call.spec.ts b/spec/unit/webrtc/call.spec.ts index cf46053d2..69a3553b0 100644 --- a/spec/unit/webrtc/call.spec.ts +++ b/spec/unit/webrtc/call.spec.ts @@ -305,7 +305,9 @@ describe('Call', function() { await client.httpBackend.flush(); await callPromise; - call.getOpponentMember = () => {return { userId: "@bob:bar.uk" }}; + call.getOpponentMember = () => { + return { userId: "@bob:bar.uk" }; + }; await call.onAnswerReceived({ getContent: () => { @@ -336,7 +338,9 @@ describe('Call', function() { await client.httpBackend.flush(); await callPromise; - call.getOpponentMember = () => {return { userId: "@bob:bar.uk" }}; + call.getOpponentMember = () => { + return { userId: "@bob:bar.uk" }; + }; await call.onAnswerReceived({ getContent: () => { From c1625e5c27d78de9fd15760273e008615bf60042 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 4 Jun 2021 11:44:31 +0200 Subject: [PATCH 052/118] More styling :( MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/webrtc/call.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/webrtc/call.ts b/src/webrtc/call.ts index 72e6fde37..0e37dac46 100644 --- a/src/webrtc/call.ts +++ b/src/webrtc/call.ts @@ -481,7 +481,7 @@ export class MatrixCall extends EventEmitter { const userId = this.getOpponentMember().userId; // We can guess the purpose here since the other client can only send one stream const purpose = SDPStreamMetadataPurpose.Usermedia; - const oldRemoteStream = this.feeds.find((feed) => {return !feed.isLocal()})?.stream; + const oldRemoteStream = this.feeds.find((feed) => !feed.isLocal())?.stream; // Note that we check by ID and always set the remote stream: Chrome appears // to make new stream objects when transceiver directionality is changed and the 'active' @@ -535,7 +535,7 @@ export class MatrixCall extends EventEmitter { `streamId="${stream.id}", ` + `streamPurpose="${purpose}"` + `) to peer connection`, - ) + ); senderArray.push(this.peerConn.addTrack(track, stream)); } } @@ -1299,7 +1299,7 @@ export class MatrixCall extends EventEmitter { if (metadata) { this.remoteSDPStreamMetadata = metadata; } else { - logger.warn("Received negotiation event without SDPStreamMetadata!") + logger.warn("Received negotiation event without SDPStreamMetadata!"); } try { From fcbbcc0398e0942b5d0816fcd6a88bfdc3e4ac25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Thu, 22 Jul 2021 09:24:59 +0200 Subject: [PATCH 053/118] Always return true for voice calls MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/webrtc/call.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/webrtc/call.ts b/src/webrtc/call.ts index de81d47c9..6a9a8be76 100644 --- a/src/webrtc/call.ts +++ b/src/webrtc/call.ts @@ -901,6 +901,7 @@ export class MatrixCall extends EventEmitter { * (including if the call is not set up yet). */ isLocalVideoMuted(): boolean { + if (this.type === CallType.Voice) return true; return this.vidMuted; } From 5aa60b8056936b0f09f413855abcd99a44db03ec Mon Sep 17 00:00:00 2001 From: Brad Murray Date: Fri, 23 Jul 2021 15:56:24 -0400 Subject: [PATCH 054/118] Clean up Event.clearEvent handling --- src/models/event.ts | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/src/models/event.ts b/src/models/event.ts index 9050f4440..cc68c4458 100644 --- a/src/models/event.ts +++ b/src/models/event.ts @@ -152,7 +152,7 @@ export class MatrixEvent extends EventEmitter { private _replacingEvent: MatrixEvent = null; private _localRedactionEvent: MatrixEvent = null; private _isCancelled = false; - private clearEvent: Partial = {}; + private clearEvent?: IClearEvent; /* curve25519 key which we believe belongs to the sender of the event. See * getSenderKey() @@ -285,7 +285,7 @@ export class MatrixEvent extends EventEmitter { * @return {string} The event type, e.g. m.room.message */ public getType(): EventType | string { - return this.clearEvent.type || this.event.type; + return this.clearEvent?.type || this.event.type; } /** @@ -334,7 +334,7 @@ export class MatrixEvent extends EventEmitter { if (this._localRedactionEvent) { return {} as T; } - return (this.clearEvent.content || this.event.content || {}) as T; + return (this.clearEvent?.content || this.event.content || {}) as T; } /** @@ -486,7 +486,7 @@ export class MatrixEvent extends EventEmitter { } public shouldAttemptDecryption() { - return this.isEncrypted() && !this.isBeingDecrypted() && this.getClearContent() === null; + return this.isEncrypted() && !this.isBeingDecrypted() && !this.clearEvent; } /** @@ -518,10 +518,7 @@ export class MatrixEvent extends EventEmitter { throw new Error("Attempt to decrypt event which isn't encrypted"); } - if ( - this.clearEvent && this.clearEvent.content && - this.clearEvent.content.msgtype !== "m.bad.encrypted" - ) { + if (this.clearEvent?.content?.msgtype !== "m.bad.encrypted") { // we may want to just ignore this? let's start with rejecting it. throw new Error( "Attempt to decrypt event which has already been decrypted", @@ -729,8 +726,7 @@ export class MatrixEvent extends EventEmitter { * @returns {Object} The cleartext (decrypted) content for the event */ public getClearContent(): IContent | null { - const ev = this.clearEvent; - return ev && ev.content ? ev.content : null; + return this.clearEvent?.content || null; } /** @@ -921,8 +917,8 @@ export class MatrixEvent extends EventEmitter { public getRedactionEvent(): object | null { if (!this.isRedacted()) return null; - if (this.clearEvent.unsigned) { - return this.clearEvent.unsigned.redacted_because; + if (this.clearEvent?.unsigned) { + return this.clearEvent?.unsigned.redacted_because; } else if (this.event.unsigned.redacted_because) { return this.event.unsigned.redacted_because; } else { From 980d6fc2ae3f77f98891904488897935c49b4d3d Mon Sep 17 00:00:00 2001 From: Brad Murray Date: Fri, 23 Jul 2021 16:13:46 -0400 Subject: [PATCH 055/118] Differentiate between an undefined clearEvent and a falsey property of the clearEvent --- src/models/event.ts | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/models/event.ts b/src/models/event.ts index cc68c4458..6a8751960 100644 --- a/src/models/event.ts +++ b/src/models/event.ts @@ -285,7 +285,10 @@ export class MatrixEvent extends EventEmitter { * @return {string} The event type, e.g. m.room.message */ public getType(): EventType | string { - return this.clearEvent?.type || this.event.type; + if (this.clearEvent) { + return this.clearEvent.type; + } + return this.event.type; } /** @@ -334,7 +337,10 @@ export class MatrixEvent extends EventEmitter { if (this._localRedactionEvent) { return {} as T; } - return (this.clearEvent?.content || this.event.content || {}) as T; + if (this.clearEvent) { + return this.clearEvent.content as T; + } + return (this.event.content || {}) as T; } /** @@ -518,7 +524,7 @@ export class MatrixEvent extends EventEmitter { throw new Error("Attempt to decrypt event which isn't encrypted"); } - if (this.clearEvent?.content?.msgtype !== "m.bad.encrypted") { + if (this.isDecryptionFailure()) { // we may want to just ignore this? let's start with rejecting it. throw new Error( "Attempt to decrypt event which has already been decrypted", @@ -726,7 +732,10 @@ export class MatrixEvent extends EventEmitter { * @returns {Object} The cleartext (decrypted) content for the event */ public getClearContent(): IContent | null { - return this.clearEvent?.content || null; + if (this.clearEvent) { + return this.clearEvent.content; + } + return null; } /** From 91f5df1e48c76c6e0818222be36d51df9671f17e Mon Sep 17 00:00:00 2001 From: Brad Murray Date: Fri, 23 Jul 2021 16:16:39 -0400 Subject: [PATCH 056/118] Fix incorrect indentation --- src/models/event.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/models/event.ts b/src/models/event.ts index 6a8751960..ca960d42a 100644 --- a/src/models/event.ts +++ b/src/models/event.ts @@ -338,7 +338,7 @@ export class MatrixEvent extends EventEmitter { return {} as T; } if (this.clearEvent) { - return this.clearEvent.content as T; + return this.clearEvent.content as T; } return (this.event.content || {}) as T; } From ad79a64f1cd5ba14a73dee3333e2645ff7182c73 Mon Sep 17 00:00:00 2001 From: Brad Murray Date: Fri, 23 Jul 2021 16:25:39 -0400 Subject: [PATCH 057/118] Address review feedback --- src/models/event.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/models/event.ts b/src/models/event.ts index ca960d42a..506dd5d48 100644 --- a/src/models/event.ts +++ b/src/models/event.ts @@ -338,7 +338,7 @@ export class MatrixEvent extends EventEmitter { return {} as T; } if (this.clearEvent) { - return this.clearEvent.content as T; + return (this.clearEvent.content || {}) as T; } return (this.event.content || {}) as T; } @@ -732,10 +732,7 @@ export class MatrixEvent extends EventEmitter { * @returns {Object} The cleartext (decrypted) content for the event */ public getClearContent(): IContent | null { - if (this.clearEvent) { - return this.clearEvent.content; - } - return null; + return this.clearEvent?.content || null; } /** From 011a2acfd2fe3ad65265fdeba354ff526aecec48 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Sat, 24 Jul 2021 11:35:30 +0100 Subject: [PATCH 058/118] Convert crypto/store/* to Typescript --- src/@types/partials.ts | 8 + src/client.ts | 2 +- src/crypto/CrossSigning.ts | 13 +- src/crypto/DeviceList.ts | 33 +- src/crypto/OlmDevice.js | 2 +- src/crypto/OutgoingRoomKeyRequestManager.ts | 55 ++- src/crypto/RoomList.ts | 2 +- src/crypto/algorithms/megolm.ts | 2 +- src/crypto/index.ts | 7 +- src/crypto/store/base.ts | 166 ++++++++- ...d.js => indexeddb-crypto-store-backend.ts} | 288 +++++++++------ ...pto-store.js => indexeddb-crypto-store.ts} | 339 ++++++++++-------- ...-store.js => localStorage-crypto-store.ts} | 181 +++++----- ...crypto-store.js => memory-crypto-store.ts} | 281 +++++++++------ ...exeddb-helpers.js => indexeddb-helpers.ts} | 8 +- src/logger.ts | 2 +- src/store/indexeddb-local-backend.ts | 2 +- src/store/indexeddb.ts | 2 +- 18 files changed, 873 insertions(+), 520 deletions(-) rename src/crypto/store/{indexeddb-crypto-store-backend.js => indexeddb-crypto-store-backend.ts} (74%) rename src/crypto/store/{indexeddb-crypto-store.js => indexeddb-crypto-store.ts} (62%) rename src/crypto/store/{localStorage-crypto-store.js => localStorage-crypto-store.ts} (62%) rename src/crypto/store/{memory-crypto-store.js => memory-crypto-store.ts} (51%) rename src/{indexeddb-helpers.js => indexeddb-helpers.ts} (88%) diff --git a/src/@types/partials.ts b/src/@types/partials.ts index 19c11d153..f66dbe971 100644 --- a/src/@types/partials.ts +++ b/src/@types/partials.ts @@ -74,3 +74,11 @@ export enum HistoryVisibility { Shared = "shared", WorldReadable = "world_readable", } + +// XXX move to OlmDevice when converted +export interface InboundGroupSessionData { + room_id: string; // eslint-disable-line camelcase + session: string; + keysClaimed: Record; + forwardingCurve25519KeyChain: string[]; +} diff --git a/src/client.ts b/src/client.ts index e7d7bfb48..72cabc6ce 100644 --- a/src/client.ts +++ b/src/client.ts @@ -146,10 +146,10 @@ import { ISynapseAdminDeactivateResponse, ISynapseAdminWhoisResponse } from "./@ import { ISpaceSummaryEvent, ISpaceSummaryRoom } from "./@types/spaces"; import { IPusher, IPusherRequest, IPushRules, PushRuleAction, PushRuleKind, RuleId } from "./@types/PushRules"; import { IThreepid } from "./@types/threepids"; +import { CryptoStore } from "./crypto/store/base"; export type Store = IStore; export type SessionStore = WebStorageSessionStore; -export type CryptoStore = MemoryCryptoStore | LocalStorageCryptoStore | IndexedDBCryptoStore; export type Callback = (err: Error | any | null, data?: any) => void; export type ResetTimelineCallback = (roomId: string) => boolean; diff --git a/src/crypto/CrossSigning.ts b/src/crypto/CrossSigning.ts index aacb47148..76f651209 100644 --- a/src/crypto/CrossSigning.ts +++ b/src/crypto/CrossSigning.ts @@ -28,10 +28,11 @@ import { decryptAES, encryptAES } from './aes'; import { PkSigning } from "@matrix-org/olm"; import { DeviceInfo } from "./deviceinfo"; import { SecretStorage } from "./SecretStorage"; -import { CryptoStore, ICrossSigningKey, ISignedKey, MatrixClient } from "../client"; +import { ICrossSigningKey, ISignedKey, MatrixClient } from "../client"; import { OlmDevice } from "./OlmDevice"; import { ICryptoCallbacks } from "../matrix"; import { ISignatures } from "../@types/signed"; +import { CryptoStore } from "./store/base"; const KEY_REQUEST_TIMEOUT_MS = 1000 * 60; @@ -47,6 +48,12 @@ export interface ICacheCallbacks { storeCrossSigningKeyCache?(type: string, key: Uint8Array): Promise; } +export interface ICrossSigningInfo { + keys: Record; + firstUse: boolean; + crossSigningVerifiedBefore: boolean; +} + export class CrossSigningInfo extends EventEmitter { public keys: Record = {}; public firstUse = true; @@ -75,7 +82,7 @@ export class CrossSigningInfo extends EventEmitter { super(); } - public static fromStorage(obj: object, userId: string): CrossSigningInfo { + public static fromStorage(obj: ICrossSigningInfo, userId: string): CrossSigningInfo { const res = new CrossSigningInfo(userId); for (const prop in obj) { if (obj.hasOwnProperty(prop)) { @@ -85,7 +92,7 @@ export class CrossSigningInfo extends EventEmitter { return res; } - public toStorage(): object { + public toStorage(): ICrossSigningInfo { return { keys: this.keys, firstUse: this.firstUse, diff --git a/src/crypto/DeviceList.ts b/src/crypto/DeviceList.ts index 890f3bfa3..d27af3c54 100644 --- a/src/crypto/DeviceList.ts +++ b/src/crypto/DeviceList.ts @@ -24,12 +24,13 @@ import { EventEmitter } from 'events'; import { logger } from '../logger'; import { DeviceInfo, IDevice } from './deviceinfo'; -import { CrossSigningInfo } from './CrossSigning'; +import { CrossSigningInfo, ICrossSigningInfo } from './CrossSigning'; import * as olmlib from './olmlib'; import { IndexedDBCryptoStore } from './store/indexeddb-crypto-store'; import { chunkPromises, defer, IDeferred, sleep } from '../utils'; -import { MatrixClient, CryptoStore } from "../client"; +import { MatrixClient } from "../client"; import { OlmDevice } from "./OlmDevice"; +import { CryptoStore } from "./store/base"; /* State transition diagram for DeviceList.deviceTrackingStatus * @@ -52,7 +53,7 @@ import { OlmDevice } from "./OlmDevice"; */ // constants for DeviceList.deviceTrackingStatus -enum TrackingStatus { +export enum TrackingStatus { NotTracked, PendingDownload, DownloadInProgress, @@ -65,32 +66,22 @@ export type DeviceInfoMap = Record>; * @alias module:crypto/DeviceList */ export class DeviceList extends EventEmitter { - // userId -> { - // deviceId -> { - // [device info] - // } - // } - private devices: Record> = {}; + private devices: { [userId: string]: { [deviceId: string]: IDevice } } = {}; - // userId -> { - // [key info] - // } - public crossSigningInfo: Record = {}; + public crossSigningInfo: { [userId: string]: ICrossSigningInfo } = {}; // map of identity keys to the user who owns it private userByIdentityKey: Record = {}; // which users we are tracking device status for. - // userId -> TRACKING_STATUS_* - private deviceTrackingStatus: Record = {}; // loaded from storage in load() + private deviceTrackingStatus: { [userId: string]: TrackingStatus } = {}; // loaded from storage in load() // The 'next_batch' sync token at the point the data was written, // ie. a token representing the point immediately after the // moment represented by the snapshot in the db. private syncToken: string = null; - // userId -> promise - private keyDownloadsInProgressByUser: Record> = {}; + private keyDownloadsInProgressByUser: { [userId: string]: Promise } = {}; // Set whenever changes are made other than setting the sync token private dirty = false; @@ -375,7 +366,7 @@ export class DeviceList extends EventEmitter { return CrossSigningInfo.fromStorage(this.crossSigningInfo[userId], userId); } - public storeCrossSigningForUser(userId: string, info: CrossSigningInfo): void { + public storeCrossSigningForUser(userId: string, info: ICrossSigningInfo): void { this.crossSigningInfo[userId] = info; this.dirty = true; } @@ -603,7 +594,7 @@ export class DeviceList extends EventEmitter { } } - public setRawStoredCrossSigningForUser(userId: string, info: object): void { + public setRawStoredCrossSigningForUser(userId: string, info: ICrossSigningInfo): void { this.crossSigningInfo[userId] = info; } @@ -864,9 +855,7 @@ class DeviceListUpdateSerialiser { crossSigning.setKeys(crossSigningResponse); - this.deviceList.setRawStoredCrossSigningForUser( - userId, crossSigning.toStorage(), - ); + this.deviceList.setRawStoredCrossSigningForUser(userId, crossSigning.toStorage()); // NB. Unlike most events in the js-sdk, this one is internal to the // js-sdk and is not re-emitted diff --git a/src/crypto/OlmDevice.js b/src/crypto/OlmDevice.js index b0410df72..4d8d2618a 100644 --- a/src/crypto/OlmDevice.js +++ b/src/crypto/OlmDevice.js @@ -949,7 +949,7 @@ OlmDevice.prototype.getOutboundGroupSessionKey = function(sessionId) { * data stored in the session store about an inbound group session * * @typedef {Object} InboundGroupSessionData - * @property {string} room_Id + * @property {string} room_id * @property {string} session pickled Olm.InboundGroupSession * @property {Object} keysClaimed * @property {Array} forwardingCurve25519KeyChain Devices involved in forwarding diff --git a/src/crypto/OutgoingRoomKeyRequestManager.ts b/src/crypto/OutgoingRoomKeyRequestManager.ts index d01245722..b152f248d 100644 --- a/src/crypto/OutgoingRoomKeyRequestManager.ts +++ b/src/crypto/OutgoingRoomKeyRequestManager.ts @@ -15,9 +15,9 @@ limitations under the License. */ import { logger } from '../logger'; -import { CryptoStore, MatrixClient } from "../client"; +import { MatrixClient } from "../client"; import { IRoomKeyRequestBody, IRoomKeyRequestRecipient } from "./index"; -import { OutgoingRoomKeyRequest } from './store/base'; +import { CryptoStore, OutgoingRoomKeyRequest } from './store/base'; import { EventType } from "../@types/event"; /** @@ -263,9 +263,8 @@ export class OutgoingRoomKeyRequestManager { 'deleting unnecessary room key request for ' + stringifyRequestBody(requestBody), ); - return this.cryptoStore.deleteOutgoingRoomKeyRequest( - req.requestId, RoomKeyRequestState.Unsent, - ); + return this.cryptoStore.deleteOutgoingRoomKeyRequest(req.requestId, RoomKeyRequestState.Unsent) + .then(); // match Promise signature case RoomKeyRequestState.Sent: { // send a cancellation. @@ -325,7 +324,7 @@ export class OutgoingRoomKeyRequestManager { * @return {Promise} resolves to a list of all the * {@link module:crypto/store/base~OutgoingRoomKeyRequest} */ - public getOutgoingSentRoomKeyRequest(userId: string, deviceId: string): OutgoingRoomKeyRequest[] { + public getOutgoingSentRoomKeyRequest(userId: string, deviceId: string): Promise { return this.cryptoStore.getOutgoingRoomKeyRequestsByTarget(userId, deviceId, [RoomKeyRequestState.Sent]); } @@ -415,7 +414,7 @@ export class OutgoingRoomKeyRequestManager { } // given a RoomKeyRequest, send it and update the request record - private sendOutgoingRoomKeyRequest(req: OutgoingRoomKeyRequest): Promise { + private async sendOutgoingRoomKeyRequest(req: OutgoingRoomKeyRequest): Promise { logger.log( `Requesting keys for ${stringifyRequestBody(req.requestBody)}` + ` from ${stringifyRecipientList(req.recipients)}` + @@ -429,19 +428,19 @@ export class OutgoingRoomKeyRequestManager { body: req.requestBody, }; - return this.sendMessageToDevices( - requestMessage, req.recipients, req.requestTxnId || req.requestId, - ).then(() => { - return this.cryptoStore.updateOutgoingRoomKeyRequest( - req.requestId, RoomKeyRequestState.Unsent, - { state: RoomKeyRequestState.Sent }, - ); - }); + await this.sendMessageToDevices(requestMessage, req.recipients, req.requestTxnId || req.requestId); + await this.cryptoStore.updateOutgoingRoomKeyRequest( + req.requestId, RoomKeyRequestState.Unsent, + { state: RoomKeyRequestState.Sent }, + ); } // Given a RoomKeyRequest, cancel it and delete the request record unless // andResend is set, in which case transition to UNSENT. - private sendOutgoingRoomKeyRequestCancellation(req: OutgoingRoomKeyRequest, andResend = false): Promise { + private async sendOutgoingRoomKeyRequestCancellation( + req: OutgoingRoomKeyRequest, + andResend = false, + ): Promise { logger.log( `Sending cancellation for key request for ` + `${stringifyRequestBody(req.requestBody)} to ` + @@ -455,21 +454,19 @@ export class OutgoingRoomKeyRequestManager { request_id: req.requestId, }; - return this.sendMessageToDevices( - requestMessage, req.recipients, req.cancellationTxnId, - ).then(() => { - if (andResend) { - // We want to resend, so transition to UNSENT - return this.cryptoStore.updateOutgoingRoomKeyRequest( - req.requestId, - RoomKeyRequestState.CancellationPendingAndWillResend, - { state: RoomKeyRequestState.Unsent }, - ); - } - return this.cryptoStore.deleteOutgoingRoomKeyRequest( + await this.sendMessageToDevices(requestMessage, req.recipients, req.cancellationTxnId); + if (andResend) { + // We want to resend, so transition to UNSENT + await this.cryptoStore.updateOutgoingRoomKeyRequest( + req.requestId, + RoomKeyRequestState.CancellationPendingAndWillResend, + { state: RoomKeyRequestState.Unsent }, + ); + } else { + await this.cryptoStore.deleteOutgoingRoomKeyRequest( req.requestId, RoomKeyRequestState.CancellationPending, ); - }); + } } // send a RoomKeyRequest to a list of recipients diff --git a/src/crypto/RoomList.ts b/src/crypto/RoomList.ts index b2835362f..24315d4ec 100644 --- a/src/crypto/RoomList.ts +++ b/src/crypto/RoomList.ts @@ -20,8 +20,8 @@ limitations under the License. * Manages the list of encrypted rooms */ +import { CryptoStore } from './store/base'; import { IndexedDBCryptoStore } from './store/indexeddb-crypto-store'; -import { CryptoStore } from "../client"; /* eslint-disable camelcase */ export interface IRoomEncryption { diff --git a/src/crypto/algorithms/megolm.ts b/src/crypto/algorithms/megolm.ts index 2a1a2d8db..381f85188 100644 --- a/src/crypto/algorithms/megolm.ts +++ b/src/crypto/algorithms/megolm.ts @@ -61,7 +61,7 @@ interface IBlockedMap { }; } -interface IOlmDevice { +export interface IOlmDevice { userId: string; deviceInfo: T; } diff --git a/src/crypto/index.ts b/src/crypto/index.ts index 761333f65..ea627dddf 100644 --- a/src/crypto/index.ts +++ b/src/crypto/index.ts @@ -59,12 +59,13 @@ import { IStore } from "../store"; import { Room } from "../models/room"; import { RoomMember } from "../models/room-member"; import { MatrixEvent } from "../models/event"; -import { MatrixClient, IKeysUploadResponse, SessionStore, CryptoStore, ISignedKey } from "../client"; +import { MatrixClient, IKeysUploadResponse, SessionStore, ISignedKey } from "../client"; import type { EncryptionAlgorithm, DecryptionAlgorithm } from "./algorithms/base"; import type { IRoomEncryption, RoomList } from "./RoomList"; import { IRecoveryKey, IEncryptedEventInfo } from "./api"; import { IKeyBackupInfo } from "./keybackup"; import { ISyncStateData } from "../sync"; +import { CryptoStore } from "./store/base"; const DeviceVerification = DeviceInfo.DeviceVerification; @@ -1410,9 +1411,7 @@ export class Crypto extends EventEmitter { crossSigning.updateCrossSigningVerifiedBefore( this.checkUserTrust(userId).isCrossSigningVerified(), ); - this.deviceList.setRawStoredCrossSigningForUser( - userId, crossSigning.toStorage(), - ); + this.deviceList.setRawStoredCrossSigningForUser(userId, crossSigning.toStorage()); } this.emit("userTrustStatusChanged", userId, this.checkUserTrust(userId)); diff --git a/src/crypto/store/base.ts b/src/crypto/store/base.ts index d76fb9ead..03ba6ef8e 100644 --- a/src/crypto/store/base.ts +++ b/src/crypto/store/base.ts @@ -1,5 +1,33 @@ +/* +Copyright 2021 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { IRoomKeyRequestBody, IRoomKeyRequestRecipient } from "../index"; +import { RoomKeyRequestState } from "../OutgoingRoomKeyRequestManager"; +import { ICrossSigningKey } from "../../client"; +import { IOlmDevice } from "../algorithms/megolm"; +import { TrackingStatus } from "../DeviceList"; +import { IRoomEncryption } from "../RoomList"; +import { IDevice } from "../deviceinfo"; +import { ICrossSigningInfo } from "../CrossSigning"; +import { PrefixedLogger } from "../../logger"; +import { InboundGroupSessionData } from "../../@types/partials"; +import { IEncryptedPayload } from "../aes"; + /** - * Internal module. Defintions for storage for the crypto module + * Internal module. Definitions for storage for the crypto module * * @module */ @@ -9,9 +37,141 @@ * * @interface CryptoStore */ +export interface CryptoStore { + startup(): Promise; + deleteAllData(): Promise; + getOrAddOutgoingRoomKeyRequest(request: OutgoingRoomKeyRequest): Promise; + getOutgoingRoomKeyRequest(requestBody: IRoomKeyRequestBody): Promise; + getOutgoingRoomKeyRequestByState(wantedStates: number[]): Promise; + getAllOutgoingRoomKeyRequestsByState(wantedState: number): Promise; + getOutgoingRoomKeyRequestsByTarget( + userId: string, + deviceId: string, + wantedStates: number[], + ): Promise; + updateOutgoingRoomKeyRequest( + requestId: string, + expectedState: number, + updates: Partial, + ): Promise; + deleteOutgoingRoomKeyRequest(requestId: string, expectedState: number): Promise; -import { IRoomKeyRequestBody, IRoomKeyRequestRecipient } from "../index"; -import { RoomKeyRequestState } from "../OutgoingRoomKeyRequestManager"; + // Olm Account + getAccount(txn: unknown, func: (accountPickle: string) => void); + storeAccount(txn: unknown, accountPickle: string): void; + getCrossSigningKeys(txn: unknown, func: (keys: Record) => void): void; + getSecretStorePrivateKey(txn: unknown, func: (key: IEncryptedPayload | null) => T, type: string): void; + storeCrossSigningKeys(txn: unknown, keys: Record): void; + storeSecretStorePrivateKey(txn: unknown, type: string, key: IEncryptedPayload): void; + + // Olm Sessions + countEndToEndSessions(txn: unknown, func: (count: number) => void): void; + getEndToEndSession( + deviceKey: string, + sessionId: string, + txn: unknown, + func: (session: ISessionInfo) => void, + ): void; + getEndToEndSessions( + deviceKey: string, + txn: unknown, + func: (sessions: { [sessionId: string]: ISessionInfo }) => void, + ): void; + getAllEndToEndSessions(txn: unknown, func: (session: ISessionInfo) => void): void; + storeEndToEndSession(deviceKey: string, sessionId: string, sessionInfo: ISessionInfo, txn: unknown): void; + storeEndToEndSessionProblem(deviceKey: string, type: string, fixed: boolean): Promise; + getEndToEndSessionProblem(deviceKey: string, timestamp: number): Promise; + filterOutNotifiedErrorDevices(devices: IOlmDevice[]): Promise; + + // Inbound Group Sessions + getEndToEndInboundGroupSession( + senderCurve25519Key: string, + sessionId: string, + txn: unknown, + func: (groupSession: InboundGroupSessionData | null, groupSessionWithheld: IWithheld | null) => void, + ): void; + getAllEndToEndInboundGroupSessions( + txn: unknown, + func: (session: ISession | null) => void, + ): void; + addEndToEndInboundGroupSession( + senderCurve25519Key: string, + sessionId: string, + sessionData: InboundGroupSessionData, + txn: unknown, + ): void; + storeEndToEndInboundGroupSession( + senderCurve25519Key: string, + sessionId: string, + sessionData: InboundGroupSessionData, + txn: unknown, + ): void; + storeEndToEndInboundGroupSessionWithheld( + senderCurve25519Key: string, + sessionId: string, + sessionData: IWithheld, + txn: unknown, + ): void; + + // Device Data + getEndToEndDeviceData(txn: unknown, func: (deviceData: IDeviceData | null) => void): void; + storeEndToEndDeviceData(deviceData: IDeviceData, txn: unknown): void; + storeEndToEndRoom(roomId: string, roomInfo: IRoomEncryption, txn: unknown): void; + getEndToEndRooms(txn: unknown, func: (rooms: Record) => void): void; + getSessionsNeedingBackup(limit: number): Promise; + countSessionsNeedingBackup(txn?: unknown): Promise; + unmarkSessionsNeedingBackup(sessions: ISession[], txn?: unknown): Promise; + markSessionsNeedingBackup(sessions: ISession[], txn?: unknown): Promise; + addSharedHistoryInboundGroupSession(roomId: string, senderKey: string, sessionId: string, txn?: unknown): void; + getSharedHistoryInboundGroupSessions( + roomId: string, + txn?: IDBTransaction, + ): Promise<[senderKey: string, sessionId: string][]>; + + // Session key backups + doTxn(mode: Mode, stores: Iterable, func: (txn: unknown) => T, log?: PrefixedLogger): Promise; +} + +export type Mode = "readonly" | "readwrite"; + +export interface ISession { + senderKey: string; + sessionId: string; + sessionData?: InboundGroupSessionData; +} + +export interface ISessionInfo { + deviceKey?: string; + sessionId?: string; + session?: string; + lastReceivedMessageTs?: number; +} + +export interface IDeviceData { + devices: { + [ userId: string ]: { + [ deviceId: string ]: IDevice; + }; + }; + trackingStatus: { + [ userId: string ]: TrackingStatus; + }; + crossSigningInfo?: Record; + syncToken?: string; +} + +export interface IProblem { + type: string; + fixed: boolean; + time: number; +} + +export interface IWithheld { + // eslint-disable-next-line camelcase + room_id: string; + code: string; + reason: string; +} /** * Represents an outgoing room key request diff --git a/src/crypto/store/indexeddb-crypto-store-backend.js b/src/crypto/store/indexeddb-crypto-store-backend.ts similarity index 74% rename from src/crypto/store/indexeddb-crypto-store-backend.js rename to src/crypto/store/indexeddb-crypto-store-backend.ts index 9a54a3537..b4735ad5b 100644 --- a/src/crypto/store/indexeddb-crypto-store-backend.js +++ b/src/crypto/store/indexeddb-crypto-store-backend.ts @@ -1,7 +1,5 @@ /* -Copyright 2017 Vector Creations Ltd -Copyright 2018 New Vector Ltd -Copyright 2020 The Matrix.org Foundation C.I.C. +Copyright 2017 - 2021 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -16,8 +14,24 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { logger } from '../../logger'; +import { logger, PrefixedLogger } from '../../logger'; import * as utils from "../../utils"; +import { + CryptoStore, + IDeviceData, + IProblem, + ISession, + ISessionInfo, + IWithheld, + Mode, + OutgoingRoomKeyRequest, +} from "./base"; +import { IRoomKeyRequestBody } from "../index"; +import { ICrossSigningKey } from "../../client"; +import { IOlmDevice } from "../algorithms/megolm"; +import { IRoomEncryption } from "../RoomList"; +import { InboundGroupSessionData } from "../../@types/partials"; +import { IEncryptedPayload } from "../aes"; export const VERSION = 10; const PROFILE_TRANSACTIONS = false; @@ -29,18 +43,17 @@ const PROFILE_TRANSACTIONS = false; * * @implements {module:crypto/store/base~CryptoStore} */ -export class Backend { +export class Backend implements CryptoStore { + private nextTxnId = 0; + /** * @param {IDBDatabase} db */ - constructor(db) { - this._db = db; - this._nextTxnId = 0; - + constructor(private db: IDBDatabase) { // make sure we close the db on `onversionchange` - otherwise // attempts to delete the database will block (and subsequent // attempts to re-create it will also block). - db.onversionchange = (ev) => { + db.onversionchange = () => { logger.log(`versionchange for indexeddb ${this._dbName}: closing`); db.close(); }; @@ -56,11 +69,11 @@ export class Backend { * {@link module:crypto/store/base~OutgoingRoomKeyRequest}: either the * same instance as passed in, or the existing one. */ - getOrAddOutgoingRoomKeyRequest(request) { + public getOrAddOutgoingRoomKeyRequest(request: OutgoingRoomKeyRequest): Promise { const requestBody = request.requestBody; return new Promise((resolve, reject) => { - const txn = this._db.transaction("outgoingRoomKeyRequests", "readwrite"); + const txn = this.db.transaction("outgoingRoomKeyRequests", "readwrite"); txn.onerror = reject; // first see if we already have an entry for this request. @@ -99,9 +112,9 @@ export class Backend { * {@link module:crypto/store/base~OutgoingRoomKeyRequest}, or null if * not found */ - getOutgoingRoomKeyRequest(requestBody) { + public getOutgoingRoomKeyRequest(requestBody: IRoomKeyRequestBody): Promise { return new Promise((resolve, reject) => { - const txn = this._db.transaction("outgoingRoomKeyRequests", "readonly"); + const txn = this.db.transaction("outgoingRoomKeyRequests", "readonly"); txn.onerror = reject; this._getOutgoingRoomKeyRequest(txn, requestBody, (existing) => { @@ -122,7 +135,12 @@ export class Backend { * {@link module:crypto/store/base~OutgoingRoomKeyRequest}, or null if * not found. */ - _getOutgoingRoomKeyRequest(txn, requestBody, callback) { + // eslint-disable-next-line @typescript-eslint/naming-convention + private _getOutgoingRoomKeyRequest( + txn: IDBTransaction, + requestBody: IRoomKeyRequestBody, + callback: (req: OutgoingRoomKeyRequest | null) => void, + ): void { const store = txn.objectStore("outgoingRoomKeyRequests"); const idx = store.index("session"); @@ -131,8 +149,8 @@ export class Backend { requestBody.session_id, ]); - cursorReq.onsuccess = (ev) => { - const cursor = ev.target.result; + cursorReq.onsuccess = () => { + const cursor = cursorReq.result; if (!cursor) { // no match found callback(null); @@ -162,7 +180,7 @@ export class Backend { * there are no pending requests in those states. If there are multiple * requests in those states, an arbitrary one is chosen. */ - getOutgoingRoomKeyRequestByState(wantedStates) { + public getOutgoingRoomKeyRequestByState(wantedStates: number[]): Promise { if (wantedStates.length === 0) { return Promise.resolve(null); } @@ -195,7 +213,7 @@ export class Backend { cursorReq.onsuccess = onsuccess; } - const txn = this._db.transaction("outgoingRoomKeyRequests", "readonly"); + const txn = this.db.transaction("outgoingRoomKeyRequests", "readonly"); const store = txn.objectStore("outgoingRoomKeyRequests"); const wantedState = wantedStates[stateIndex]; @@ -210,19 +228,23 @@ export class Backend { * @param {Number} wantedState * @return {Promise>} All elements in a given state */ - getAllOutgoingRoomKeyRequestsByState(wantedState) { + public getAllOutgoingRoomKeyRequestsByState(wantedState: number): Promise { return new Promise((resolve, reject) => { - const txn = this._db.transaction("outgoingRoomKeyRequests", "readonly"); + const txn = this.db.transaction("outgoingRoomKeyRequests", "readonly"); const store = txn.objectStore("outgoingRoomKeyRequests"); const index = store.index("state"); const request = index.getAll(wantedState); - request.onsuccess = (ev) => resolve(ev.target.result); - request.onerror = (ev) => reject(ev.target.error); + request.onsuccess = () => resolve(request.result); + request.onerror = () => reject(request.error); }); } - getOutgoingRoomKeyRequestsByTarget(userId, deviceId, wantedStates) { + public getOutgoingRoomKeyRequestsByTarget( + userId: string, + deviceId: string, + wantedStates: number[], + ): Promise { let stateIndex = 0; const results = []; @@ -248,7 +270,7 @@ export class Backend { } } - const txn = this._db.transaction("outgoingRoomKeyRequests", "readonly"); + const txn = this.db.transaction("outgoingRoomKeyRequests", "readonly"); const store = txn.objectStore("outgoingRoomKeyRequests"); const wantedState = wantedStates[stateIndex]; @@ -270,7 +292,11 @@ export class Backend { * {@link module:crypto/store/base~OutgoingRoomKeyRequest} * updated request, or null if no matching row was found */ - updateOutgoingRoomKeyRequest(requestId, expectedState, updates) { + public updateOutgoingRoomKeyRequest( + requestId: string, + expectedState: number, + updates: Partial, + ): Promise { let result = null; function onsuccess(ev) { @@ -291,9 +317,8 @@ export class Backend { result = data; } - const txn = this._db.transaction("outgoingRoomKeyRequests", "readwrite"); - const cursorReq = txn.objectStore("outgoingRoomKeyRequests") - .openCursor(requestId); + const txn = this.db.transaction("outgoingRoomKeyRequests", "readwrite"); + const cursorReq = txn.objectStore("outgoingRoomKeyRequests").openCursor(requestId); cursorReq.onsuccess = onsuccess; return promiseifyTxn(txn).then(() => result); } @@ -307,12 +332,14 @@ export class Backend { * * @returns {Promise} resolves once the operation is completed */ - deleteOutgoingRoomKeyRequest(requestId, expectedState) { - const txn = this._db.transaction("outgoingRoomKeyRequests", "readwrite"); - const cursorReq = txn.objectStore("outgoingRoomKeyRequests") - .openCursor(requestId); - cursorReq.onsuccess = (ev) => { - const cursor = ev.target.result; + public deleteOutgoingRoomKeyRequest( + requestId: string, + expectedState: number, + ): Promise { + const txn = this.db.transaction("outgoingRoomKeyRequests", "readwrite"); + const cursorReq = txn.objectStore("outgoingRoomKeyRequests").openCursor(requestId); + cursorReq.onsuccess = () => { + const cursor = cursorReq.result; if (!cursor) { return; } @@ -326,12 +353,12 @@ export class Backend { } cursor.delete(); }; - return promiseifyTxn(txn); + return promiseifyTxn(txn); } // Olm Account - getAccount(txn, func) { + public getAccount(txn: IDBTransaction, func: (accountPickle: string) => void): void { const objectStore = txn.objectStore("account"); const getReq = objectStore.get("-"); getReq.onsuccess = function() { @@ -343,12 +370,12 @@ export class Backend { }; } - storeAccount(txn, newData) { + public storeAccount(txn: IDBTransaction, accountPickle: string): void { const objectStore = txn.objectStore("account"); - objectStore.put(newData, "-"); + objectStore.put(accountPickle, "-"); } - getCrossSigningKeys(txn, func) { + public getCrossSigningKeys(txn: IDBTransaction, func: (keys: Record) => void): void { const objectStore = txn.objectStore("account"); const getReq = objectStore.get("crossSigningKeys"); getReq.onsuccess = function() { @@ -360,7 +387,11 @@ export class Backend { }; } - getSecretStorePrivateKey(txn, func, type) { + public getSecretStorePrivateKey( + txn: IDBTransaction, + func: (key: IEncryptedPayload | null) => T, + type: string, + ): void { const objectStore = txn.objectStore("account"); const getReq = objectStore.get(`ssss_cache:${type}`); getReq.onsuccess = function() { @@ -372,19 +403,19 @@ export class Backend { }; } - storeCrossSigningKeys(txn, keys) { + public storeCrossSigningKeys(txn: IDBTransaction, keys: Record): void { const objectStore = txn.objectStore("account"); objectStore.put(keys, "crossSigningKeys"); } - storeSecretStorePrivateKey(txn, type, key) { + public storeSecretStorePrivateKey(txn: IDBTransaction, type: string, key: IEncryptedPayload): void { const objectStore = txn.objectStore("account"); objectStore.put(key, `ssss_cache:${type}`); } // Olm Sessions - countEndToEndSessions(txn, func) { + public countEndToEndSessions(txn: IDBTransaction, func: (count: number) => void): void { const objectStore = txn.objectStore("sessions"); const countReq = objectStore.count(); countReq.onsuccess = function() { @@ -396,7 +427,11 @@ export class Backend { }; } - getEndToEndSessions(deviceKey, txn, func) { + public getEndToEndSessions( + deviceKey: string, + txn: IDBTransaction, + func: (sessions: { [sessionId: string]: ISessionInfo }) => void, + ): void { const objectStore = txn.objectStore("sessions"); const idx = objectStore.index("deviceKey"); const getReq = idx.openCursor(deviceKey); @@ -419,7 +454,12 @@ export class Backend { }; } - getEndToEndSession(deviceKey, sessionId, txn, func) { + public getEndToEndSession( + deviceKey: string, + sessionId: string, + txn: IDBTransaction, + func: (sessions: { [ sessionId: string ]: ISessionInfo }) => void, + ): void { const objectStore = txn.objectStore("sessions"); const getReq = objectStore.get([deviceKey, sessionId]); getReq.onsuccess = function() { @@ -438,7 +478,7 @@ export class Backend { }; } - getAllEndToEndSessions(txn, func) { + public getAllEndToEndSessions(txn: IDBTransaction, func: (session: ISessionInfo) => void): void { const objectStore = txn.objectStore("sessions"); const getReq = objectStore.openCursor(); getReq.onsuccess = function() { @@ -456,7 +496,12 @@ export class Backend { }; } - storeEndToEndSession(deviceKey, sessionId, sessionInfo, txn) { + public storeEndToEndSession( + deviceKey: string, + sessionId: string, + sessionInfo: ISessionInfo, + txn: IDBTransaction, + ): void { const objectStore = txn.objectStore("sessions"); objectStore.put({ deviceKey, @@ -466,8 +511,8 @@ export class Backend { }); } - async storeEndToEndSessionProblem(deviceKey, type, fixed) { - const txn = this._db.transaction("session_problems", "readwrite"); + public async storeEndToEndSessionProblem(deviceKey: string, type: string, fixed: boolean): Promise { + const txn = this.db.transaction("session_problems", "readwrite"); const objectStore = txn.objectStore("session_problems"); objectStore.put({ deviceKey, @@ -478,13 +523,13 @@ export class Backend { return promiseifyTxn(txn); } - async getEndToEndSessionProblem(deviceKey, timestamp) { + public async getEndToEndSessionProblem(deviceKey: string, timestamp: number): Promise { let result; - const txn = this._db.transaction("session_problems", "readwrite"); + const txn = this.db.transaction("session_problems", "readwrite"); const objectStore = txn.objectStore("session_problems"); const index = objectStore.index("deviceKey"); const req = index.getAll(deviceKey); - req.onsuccess = (event) => { + req.onsuccess = () => { const problems = req.result; if (!problems.length) { result = null; @@ -511,14 +556,14 @@ export class Backend { } // FIXME: we should probably prune this when devices get deleted - async filterOutNotifiedErrorDevices(devices) { - const txn = this._db.transaction("notified_error_devices", "readwrite"); + public async filterOutNotifiedErrorDevices(devices: IOlmDevice[]): Promise { + const txn = this.db.transaction("notified_error_devices", "readwrite"); const objectStore = txn.objectStore("notified_error_devices"); - const ret = []; + const ret: IOlmDevice[] = []; await Promise.all(devices.map((device) => { - return new Promise((resolve) => { + return new Promise((resolve) => { const { userId, deviceInfo } = device; const getReq = objectStore.get([userId, deviceInfo.deviceId]); getReq.onsuccess = function() { @@ -536,9 +581,14 @@ export class Backend { // Inbound group sessions - getEndToEndInboundGroupSession(senderCurve25519Key, sessionId, txn, func) { - let session = false; - let withheld = false; + public getEndToEndInboundGroupSession( + senderCurve25519Key: string, + sessionId: string, + txn: IDBTransaction, + func: (groupSession: InboundGroupSessionData | null, groupSessionWithheld: IWithheld | null) => void, + ): void { + let session: InboundGroupSessionData | boolean = false; + let withheld: IWithheld | boolean = false; const objectStore = txn.objectStore("inbound_group_sessions"); const getReq = objectStore.get([senderCurve25519Key, sessionId]); getReq.onsuccess = function() { @@ -549,7 +599,7 @@ export class Backend { session = null; } if (withheld !== false) { - func(session, withheld); + func(session as InboundGroupSessionData, withheld as IWithheld); } } catch (e) { abortWithException(txn, e); @@ -566,7 +616,7 @@ export class Backend { withheld = null; } if (session !== false) { - func(session, withheld); + func(session as InboundGroupSessionData, withheld as IWithheld); } } catch (e) { abortWithException(txn, e); @@ -574,7 +624,7 @@ export class Backend { }; } - getAllEndToEndInboundGroupSessions(txn, func) { + public getAllEndToEndInboundGroupSessions(txn: IDBTransaction, func: (session: ISession | null) => void): void { const objectStore = txn.objectStore("inbound_group_sessions"); const getReq = objectStore.openCursor(); getReq.onsuccess = function() { @@ -600,7 +650,12 @@ export class Backend { }; } - addEndToEndInboundGroupSession(senderCurve25519Key, sessionId, sessionData, txn) { + public addEndToEndInboundGroupSession( + senderCurve25519Key: string, + sessionId: string, + sessionData: InboundGroupSessionData, + txn: IDBTransaction, + ): void { const objectStore = txn.objectStore("inbound_group_sessions"); const addReq = objectStore.add({ senderCurve25519Key, sessionId, session: sessionData, @@ -623,23 +678,31 @@ export class Backend { }; } - storeEndToEndInboundGroupSession(senderCurve25519Key, sessionId, sessionData, txn) { + public storeEndToEndInboundGroupSession( + senderCurve25519Key: string, + sessionId: string, + sessionData: InboundGroupSessionData, + txn: IDBTransaction, + ): void { const objectStore = txn.objectStore("inbound_group_sessions"); objectStore.put({ senderCurve25519Key, sessionId, session: sessionData, }); } - storeEndToEndInboundGroupSessionWithheld( - senderCurve25519Key, sessionId, sessionData, txn, - ) { + public storeEndToEndInboundGroupSessionWithheld( + senderCurve25519Key: string, + sessionId: string, + sessionData: IWithheld, + txn: IDBTransaction, + ): void { const objectStore = txn.objectStore("inbound_group_sessions_withheld"); objectStore.put({ senderCurve25519Key, sessionId, session: sessionData, }); } - getEndToEndDeviceData(txn, func) { + public getEndToEndDeviceData(txn: IDBTransaction, func: (deviceData: IDeviceData | null) => void): void { const objectStore = txn.objectStore("device_data"); const getReq = objectStore.get("-"); getReq.onsuccess = function() { @@ -651,24 +714,24 @@ export class Backend { }; } - storeEndToEndDeviceData(deviceData, txn) { + public storeEndToEndDeviceData(deviceData: IDeviceData, txn: IDBTransaction): void { const objectStore = txn.objectStore("device_data"); objectStore.put(deviceData, "-"); } - storeEndToEndRoom(roomId, roomInfo, txn) { + public storeEndToEndRoom(roomId: string, roomInfo: IRoomEncryption, txn: IDBTransaction): void { const objectStore = txn.objectStore("rooms"); objectStore.put(roomInfo, roomId); } - getEndToEndRooms(txn, func) { + public getEndToEndRooms(txn: IDBTransaction, func: (rooms: Record) => void): void { const rooms = {}; const objectStore = txn.objectStore("rooms"); const getReq = objectStore.openCursor(); getReq.onsuccess = function() { const cursor = getReq.result; if (cursor) { - rooms[cursor.key] = cursor.value; + rooms[cursor.key as string] = cursor.value; cursor.continue(); } else { try { @@ -682,11 +745,11 @@ export class Backend { // session backups - getSessionsNeedingBackup(limit) { + public getSessionsNeedingBackup(limit: number): Promise { return new Promise((resolve, reject) => { const sessions = []; - const txn = this._db.transaction( + const txn = this.db.transaction( ["sessions_needing_backup", "inbound_group_sessions"], "readonly", ); @@ -716,9 +779,9 @@ export class Backend { }); } - countSessionsNeedingBackup(txn) { + public countSessionsNeedingBackup(txn?: IDBTransaction): Promise { if (!txn) { - txn = this._db.transaction("sessions_needing_backup", "readonly"); + txn = this.db.transaction("sessions_needing_backup", "readonly"); } const objectStore = txn.objectStore("sessions_needing_backup"); return new Promise((resolve, reject) => { @@ -728,12 +791,12 @@ export class Backend { }); } - unmarkSessionsNeedingBackup(sessions, txn) { + public async unmarkSessionsNeedingBackup(sessions: ISession[], txn?: IDBTransaction): Promise { if (!txn) { - txn = this._db.transaction("sessions_needing_backup", "readwrite"); + txn = this.db.transaction("sessions_needing_backup", "readwrite"); } const objectStore = txn.objectStore("sessions_needing_backup"); - return Promise.all(sessions.map((session) => { + await Promise.all(sessions.map((session) => { return new Promise((resolve, reject) => { const req = objectStore.delete([session.senderKey, session.sessionId]); req.onsuccess = resolve; @@ -742,12 +805,12 @@ export class Backend { })); } - markSessionsNeedingBackup(sessions, txn) { + public async markSessionsNeedingBackup(sessions: ISession[], txn?: IDBTransaction): Promise { if (!txn) { - txn = this._db.transaction("sessions_needing_backup", "readwrite"); + txn = this.db.transaction("sessions_needing_backup", "readwrite"); } const objectStore = txn.objectStore("sessions_needing_backup"); - return Promise.all(sessions.map((session) => { + await Promise.all(sessions.map((session) => { return new Promise((resolve, reject) => { const req = objectStore.put({ senderCurve25519Key: session.senderKey, @@ -759,9 +822,14 @@ export class Backend { })); } - addSharedHistoryInboundGroupSession(roomId, senderKey, sessionId, txn) { + public addSharedHistoryInboundGroupSession( + roomId: string, + senderKey: string, + sessionId: string, + txn?: IDBTransaction, + ): void { if (!txn) { - txn = this._db.transaction( + txn = this.db.transaction( "shared_history_inbound_group_sessions", "readwrite", ); } @@ -774,9 +842,12 @@ export class Backend { }; } - getSharedHistoryInboundGroupSessions(roomId, txn) { + public getSharedHistoryInboundGroupSessions( + roomId: string, + txn?: IDBTransaction, + ): Promise<[senderKey: string, sessionId: string][]> { if (!txn) { - txn = this._db.transaction( + txn = this.db.transaction( "shared_history_inbound_group_sessions", "readonly", ); } @@ -791,16 +862,21 @@ export class Backend { }); } - doTxn(mode, stores, func, log = logger) { + public doTxn( + mode: Mode, + stores: Iterable, + func: (txn: IDBTransaction) => T, + log: PrefixedLogger = logger, + ): Promise { let startTime; let description; if (PROFILE_TRANSACTIONS) { - const txnId = this._nextTxnId++; + const txnId = this.nextTxnId++; startTime = Date.now(); description = `${mode} crypto store transaction ${txnId} in ${stores}`; log.debug(`Starting ${description}`); } - const txn = this._db.transaction(stores, mode); + const txn = this.db.transaction(stores, mode); const promise = promiseifyTxn(txn); const result = func(txn); if (PROFILE_TRANSACTIONS) { @@ -818,7 +894,7 @@ export class Backend { } } -export function upgradeDatabase(db, oldVersion) { +export function upgradeDatabase(db: IDBDatabase, oldVersion: number): void { logger.log( `Upgrading IndexedDBCryptoStore from version ${oldVersion}` + ` to ${VERSION}`, @@ -874,7 +950,7 @@ export function upgradeDatabase(db, oldVersion) { // Expand as needed. } -function createDatabase(db) { +function createDatabase(db: IDBDatabase): void { const outgoingRoomKeyRequestsStore = db.createObjectStore("outgoingRoomKeyRequests", { keyPath: "requestId" }); @@ -887,15 +963,19 @@ function createDatabase(db) { outgoingRoomKeyRequestsStore.createIndex("state", "state"); } +interface IWrappedIDBTransaction extends IDBTransaction { + _mx_abortexception: Error; // eslint-disable-line camelcase +} + /* * Aborts a transaction with a given exception * The transaction promise will be rejected with this exception. */ -function abortWithException(txn, e) { +function abortWithException(txn: IDBTransaction, e: Error) { // We cheekily stick our exception onto the transaction object here // We could alternatively make the thing we pass back to the app // an object containing the transaction and exception. - txn._mx_abortexception = e; + (txn as IWrappedIDBTransaction)._mx_abortexception = e; try { txn.abort(); } catch (e) { @@ -904,28 +984,28 @@ function abortWithException(txn, e) { } } -function promiseifyTxn(txn) { +function promiseifyTxn(txn: IDBTransaction): Promise { return new Promise((resolve, reject) => { txn.oncomplete = () => { - if (txn._mx_abortexception !== undefined) { - reject(txn._mx_abortexception); + if ((txn as IWrappedIDBTransaction)._mx_abortexception !== undefined) { + reject((txn as IWrappedIDBTransaction)._mx_abortexception); } - resolve(); + resolve(null); }; txn.onerror = (event) => { - if (txn._mx_abortexception !== undefined) { - reject(txn._mx_abortexception); + if ((txn as IWrappedIDBTransaction)._mx_abortexception !== undefined) { + reject((txn as IWrappedIDBTransaction)._mx_abortexception); } else { logger.log("Error performing indexeddb txn", event); - reject(event.target.error); + reject(txn.error); } }; txn.onabort = (event) => { - if (txn._mx_abortexception !== undefined) { - reject(txn._mx_abortexception); + if ((txn as IWrappedIDBTransaction)._mx_abortexception !== undefined) { + reject((txn as IWrappedIDBTransaction)._mx_abortexception); } else { logger.log("Error performing indexeddb txn", event); - reject(event.target.error); + reject(txn.error); } }; }); diff --git a/src/crypto/store/indexeddb-crypto-store.js b/src/crypto/store/indexeddb-crypto-store.ts similarity index 62% rename from src/crypto/store/indexeddb-crypto-store.js rename to src/crypto/store/indexeddb-crypto-store.ts index 02a99e4e2..18008ae72 100644 --- a/src/crypto/store/indexeddb-crypto-store.js +++ b/src/crypto/store/indexeddb-crypto-store.ts @@ -1,7 +1,5 @@ /* -Copyright 2017 Vector Creations Ltd -Copyright 2018 New Vector Ltd -Copyright 2020 The Matrix.org Foundation C.I.C. +Copyright 2017 - 2021 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -16,12 +14,28 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { logger } from '../../logger'; +import { logger, PrefixedLogger } from '../../logger'; import { LocalStorageCryptoStore } from './localStorage-crypto-store'; import { MemoryCryptoStore } from './memory-crypto-store'; import * as IndexedDBCryptoStoreBackend from './indexeddb-crypto-store-backend'; import { InvalidCryptoStoreError } from '../../errors'; import * as IndexedDBHelpers from "../../indexeddb-helpers"; +import { + CryptoStore, + IDeviceData, + IProblem, + ISession, + ISessionInfo, + IWithheld, + Mode, + OutgoingRoomKeyRequest, +} from "./base"; +import { IRoomKeyRequestBody } from "../index"; +import { ICrossSigningKey } from "../../client"; +import { IOlmDevice } from "../algorithms/megolm"; +import { IRoomEncryption } from "../RoomList"; +import { InboundGroupSessionData } from "../../@types/partials"; +import { IEncryptedPayload } from "../aes"; /** * Internal module. indexeddb storage for e2e. @@ -35,23 +49,30 @@ import * as IndexedDBHelpers from "../../indexeddb-helpers"; * * @implements {module:crypto/store/base~CryptoStore} */ -export class IndexedDBCryptoStore { +export class IndexedDBCryptoStore implements CryptoStore { + public static STORE_ACCOUNT = 'account'; + public static STORE_SESSIONS = 'sessions'; + public static STORE_INBOUND_GROUP_SESSIONS = 'inbound_group_sessions'; + public static STORE_INBOUND_GROUP_SESSIONS_WITHHELD = 'inbound_group_sessions_withheld'; + public static STORE_SHARED_HISTORY_INBOUND_GROUP_SESSIONS = 'shared_history_inbound_group_sessions'; + public static STORE_DEVICE_DATA = 'device_data'; + public static STORE_ROOMS = 'rooms'; + public static STORE_BACKUP = 'sessions_needing_backup'; + + public static exists(indexedDB: IDBFactory, dbName: string): Promise { + return IndexedDBHelpers.exists(indexedDB, dbName); + } + + private backendPromise: Promise = null; + private backend: CryptoStore = null; + /** * Create a new IndexedDBCryptoStore * * @param {IDBFactory} indexedDB global indexedDB instance * @param {string} dbName name of db to connect to */ - constructor(indexedDB, dbName) { - this._indexedDB = indexedDB; - this._dbName = dbName; - this._backendPromise = null; - this._backend = null; - } - - static exists(indexedDB, dbName) { - return IndexedDBHelpers.exists(indexedDB, dbName); - } + constructor(private readonly indexedDB: IDBFactory, private readonly dbName: string) {} /** * Ensure the database exists and is up-to-date, or fall back to @@ -62,25 +83,23 @@ export class IndexedDBCryptoStore { * @return {Promise} resolves to either an IndexedDBCryptoStoreBackend.Backend, * or a MemoryCryptoStore */ - startup() { - if (this._backendPromise) { - return this._backendPromise; + public startup(): Promise { + if (this.backendPromise) { + return this.backendPromise; } - this._backendPromise = new Promise((resolve, reject) => { - if (!this._indexedDB) { + this.backendPromise = new Promise((resolve, reject) => { + if (!this.indexedDB) { reject(new Error('no indexeddb support available')); return; } - logger.log(`connecting to indexeddb ${this._dbName}`); + logger.log(`connecting to indexeddb ${this.dbName}`); - const req = this._indexedDB.open( - this._dbName, IndexedDBCryptoStoreBackend.VERSION, - ); + const req = this.indexedDB.open(this.dbName, IndexedDBCryptoStoreBackend.VERSION); req.onupgradeneeded = (ev) => { - const db = ev.target.result; + const db = req.result; const oldVersion = ev.oldVersion; IndexedDBCryptoStoreBackend.upgradeDatabase(db, oldVersion); }; @@ -93,13 +112,13 @@ export class IndexedDBCryptoStore { req.onerror = (ev) => { logger.log("Error connecting to indexeddb", ev); - reject(ev.target.error); + reject(req.error); }; - req.onsuccess = (r) => { - const db = r.target.result; + req.onsuccess = () => { + const db = req.result; - logger.log(`connected to indexeddb ${this._dbName}`); + logger.log(`connected to indexeddb ${this.dbName}`); resolve(new IndexedDBCryptoStoreBackend.Backend(db)); }; }).then((backend) => { @@ -114,9 +133,7 @@ export class IndexedDBCryptoStore { ], (txn) => { backend.getEndToEndInboundGroupSession('', '', txn, () => {}); - }).then(() => { - return backend; - }, + }).then(() => backend, ); }).catch((e) => { if (e.name === 'VersionError') { @@ -126,7 +143,7 @@ export class IndexedDBCryptoStore { throw new InvalidCryptoStoreError(InvalidCryptoStoreError.TOO_NEW); } logger.warn( - `unable to connect to indexeddb ${this._dbName}` + + `unable to connect to indexeddb ${this.dbName}` + `: falling back to localStorage store: ${e}`, ); @@ -139,10 +156,11 @@ export class IndexedDBCryptoStore { return new MemoryCryptoStore(); } }).then(backend => { - this._backend = backend; + this.backend = backend; + return backend as CryptoStore; }); - return this._backendPromise; + return this.backendPromise; } /** @@ -150,15 +168,15 @@ export class IndexedDBCryptoStore { * * @returns {Promise} resolves when the store has been cleared. */ - deleteAllData() { - return new Promise((resolve, reject) => { - if (!this._indexedDB) { + public deleteAllData(): Promise { + return new Promise((resolve, reject) => { + if (!this.indexedDB) { reject(new Error('no indexeddb support available')); return; } - logger.log(`Removing indexeddb instance: ${this._dbName}`); - const req = this._indexedDB.deleteDatabase(this._dbName); + logger.log(`Removing indexeddb instance: ${this.dbName}`); + const req = this.indexedDB.deleteDatabase(this.dbName); req.onblocked = () => { logger.log( @@ -168,11 +186,11 @@ export class IndexedDBCryptoStore { req.onerror = (ev) => { logger.log("Error deleting data from indexeddb", ev); - reject(ev.target.error); + reject(req.error); }; req.onsuccess = () => { - logger.log(`Removed indexeddb instance: ${this._dbName}`); + logger.log(`Removed indexeddb instance: ${this.dbName}`); resolve(); }; }).catch((e) => { @@ -193,8 +211,8 @@ export class IndexedDBCryptoStore { * {@link module:crypto/store/base~OutgoingRoomKeyRequest}: either the * same instance as passed in, or the existing one. */ - getOrAddOutgoingRoomKeyRequest(request) { - return this._backend.getOrAddOutgoingRoomKeyRequest(request); + public getOrAddOutgoingRoomKeyRequest(request: OutgoingRoomKeyRequest): Promise { + return this.backend.getOrAddOutgoingRoomKeyRequest(request); } /** @@ -207,8 +225,8 @@ export class IndexedDBCryptoStore { * {@link module:crypto/store/base~OutgoingRoomKeyRequest}, or null if * not found */ - getOutgoingRoomKeyRequest(requestBody) { - return this._backend.getOutgoingRoomKeyRequest(requestBody); + public getOutgoingRoomKeyRequest(requestBody: IRoomKeyRequestBody): Promise { + return this.backend.getOutgoingRoomKeyRequest(requestBody); } /** @@ -221,8 +239,8 @@ export class IndexedDBCryptoStore { * there are no pending requests in those states. If there are multiple * requests in those states, an arbitrary one is chosen. */ - getOutgoingRoomKeyRequestByState(wantedStates) { - return this._backend.getOutgoingRoomKeyRequestByState(wantedStates); + public getOutgoingRoomKeyRequestByState(wantedStates: number[]): Promise { + return this.backend.getOutgoingRoomKeyRequestByState(wantedStates); } /** @@ -232,8 +250,8 @@ export class IndexedDBCryptoStore { * @param {Number} wantedState * @return {Promise>} Returns an array of requests in the given state */ - getAllOutgoingRoomKeyRequestsByState(wantedState) { - return this._backend.getAllOutgoingRoomKeyRequestsByState(wantedState); + public getAllOutgoingRoomKeyRequestsByState(wantedState: number): Promise { + return this.backend.getAllOutgoingRoomKeyRequestsByState(wantedState); } /** @@ -246,8 +264,12 @@ export class IndexedDBCryptoStore { * @return {Promise} resolves to a list of all the * {@link module:crypto/store/base~OutgoingRoomKeyRequest} */ - getOutgoingRoomKeyRequestsByTarget(userId, deviceId, wantedStates) { - return this._backend.getOutgoingRoomKeyRequestsByTarget( + public getOutgoingRoomKeyRequestsByTarget( + userId: string, + deviceId: string, + wantedStates: number[], + ): Promise { + return this.backend.getOutgoingRoomKeyRequestsByTarget( userId, deviceId, wantedStates, ); } @@ -264,8 +286,12 @@ export class IndexedDBCryptoStore { * {@link module:crypto/store/base~OutgoingRoomKeyRequest} * updated request, or null if no matching row was found */ - updateOutgoingRoomKeyRequest(requestId, expectedState, updates) { - return this._backend.updateOutgoingRoomKeyRequest( + public updateOutgoingRoomKeyRequest( + requestId: string, + expectedState: number, + updates: Partial, + ): Promise { + return this.backend.updateOutgoingRoomKeyRequest( requestId, expectedState, updates, ); } @@ -279,8 +305,11 @@ export class IndexedDBCryptoStore { * * @returns {Promise} resolves once the operation is completed */ - deleteOutgoingRoomKeyRequest(requestId, expectedState) { - return this._backend.deleteOutgoingRoomKeyRequest(requestId, expectedState); + public deleteOutgoingRoomKeyRequest( + requestId: string, + expectedState: number, + ): Promise { + return this.backend.deleteOutgoingRoomKeyRequest(requestId, expectedState); } // Olm Account @@ -292,8 +321,8 @@ export class IndexedDBCryptoStore { * @param {*} txn An active transaction. See doTxn(). * @param {function(string)} func Called with the account pickle */ - getAccount(txn, func) { - this._backend.getAccount(txn, func); + public getAccount(txn: IDBTransaction, func: (accountPickle: string) => void) { + this.backend.getAccount(txn, func); } /** @@ -301,10 +330,10 @@ export class IndexedDBCryptoStore { * This requires an active transaction. See doTxn(). * * @param {*} txn An active transaction. See doTxn(). - * @param {string} newData The new account pickle to store. + * @param {string} accountPickle The new account pickle to store. */ - storeAccount(txn, newData) { - this._backend.storeAccount(txn, newData); + public storeAccount(txn: IDBTransaction, accountPickle: string): void { + this.backend.storeAccount(txn, accountPickle); } /** @@ -315,8 +344,8 @@ export class IndexedDBCryptoStore { * @param {function(string)} func Called with the account keys object: * { key_type: base64 encoded seed } where key type = user_signing_key_seed or self_signing_key_seed */ - getCrossSigningKeys(txn, func) { - this._backend.getCrossSigningKeys(txn, func); + public getCrossSigningKeys(txn: IDBTransaction, func: (keys: Record) => void): void { + this.backend.getCrossSigningKeys(txn, func); } /** @@ -324,8 +353,12 @@ export class IndexedDBCryptoStore { * @param {function(string)} func Called with the private key * @param {string} type A key type */ - getSecretStorePrivateKey(txn, func, type) { - this._backend.getSecretStorePrivateKey(txn, func, type); + public getSecretStorePrivateKey( + txn: IDBTransaction, + func: (key: IEncryptedPayload | null) => T, + type: string, + ): void { + this.backend.getSecretStorePrivateKey(txn, func, type); } /** @@ -334,8 +367,8 @@ export class IndexedDBCryptoStore { * @param {*} txn An active transaction. See doTxn(). * @param {string} keys keys object as getCrossSigningKeys() */ - storeCrossSigningKeys(txn, keys) { - this._backend.storeCrossSigningKeys(txn, keys); + public storeCrossSigningKeys(txn: IDBTransaction, keys: Record): void { + this.backend.storeCrossSigningKeys(txn, keys); } /** @@ -345,8 +378,8 @@ export class IndexedDBCryptoStore { * @param {string} type The type of cross-signing private key to store * @param {string} key keys object as getCrossSigningKeys() */ - storeSecretStorePrivateKey(txn, type, key) { - this._backend.storeSecretStorePrivateKey(txn, type, key); + public storeSecretStorePrivateKey(txn: IDBTransaction, type: string, key: IEncryptedPayload): void { + this.backend.storeSecretStorePrivateKey(txn, type, key); } // Olm sessions @@ -356,8 +389,8 @@ export class IndexedDBCryptoStore { * @param {*} txn An active transaction. See doTxn(). * @param {function(int)} func Called with the count of sessions */ - countEndToEndSessions(txn, func) { - this._backend.countEndToEndSessions(txn, func); + public countEndToEndSessions(txn: IDBTransaction, func: (count: number) => void): void { + this.backend.countEndToEndSessions(txn, func); } /** @@ -372,8 +405,13 @@ export class IndexedDBCryptoStore { * timestamp in milliseconds at which the session last received * a message. */ - getEndToEndSession(deviceKey, sessionId, txn, func) { - this._backend.getEndToEndSession(deviceKey, sessionId, txn, func); + public getEndToEndSession( + deviceKey: string, + sessionId: string, + txn: IDBTransaction, + func: (sessions: { [ sessionId: string ]: ISessionInfo }) => void, + ): void { + this.backend.getEndToEndSession(deviceKey, sessionId, txn, func); } /** @@ -387,8 +425,12 @@ export class IndexedDBCryptoStore { * timestamp in milliseconds at which the session last received * a message. */ - getEndToEndSessions(deviceKey, txn, func) { - this._backend.getEndToEndSessions(deviceKey, txn, func); + public getEndToEndSessions( + deviceKey: string, + txn: IDBTransaction, + func: (sessions: { [sessionId: string]: ISessionInfo }) => void, + ): void { + this.backend.getEndToEndSessions(deviceKey, txn, func); } /** @@ -398,8 +440,8 @@ export class IndexedDBCryptoStore { * an object with, deviceKey, lastReceivedMessageTs, sessionId * and session keys. */ - getAllEndToEndSessions(txn, func) { - this._backend.getAllEndToEndSessions(txn, func); + public getAllEndToEndSessions(txn: IDBTransaction, func: (session: ISessionInfo) => void): void { + this.backend.getAllEndToEndSessions(txn, func); } /** @@ -409,22 +451,25 @@ export class IndexedDBCryptoStore { * @param {string} sessionInfo Session information object * @param {*} txn An active transaction. See doTxn(). */ - storeEndToEndSession(deviceKey, sessionId, sessionInfo, txn) { - this._backend.storeEndToEndSession( - deviceKey, sessionId, sessionInfo, txn, - ); + public storeEndToEndSession( + deviceKey: string, + sessionId: string, + sessionInfo: ISessionInfo, + txn: IDBTransaction, + ): void { + this.backend.storeEndToEndSession(deviceKey, sessionId, sessionInfo, txn); } - storeEndToEndSessionProblem(deviceKey, type, fixed) { - return this._backend.storeEndToEndSessionProblem(deviceKey, type, fixed); + public storeEndToEndSessionProblem(deviceKey: string, type: string, fixed: boolean): Promise { + return this.backend.storeEndToEndSessionProblem(deviceKey, type, fixed); } - getEndToEndSessionProblem(deviceKey, timestamp) { - return this._backend.getEndToEndSessionProblem(deviceKey, timestamp); + public getEndToEndSessionProblem(deviceKey: string, timestamp: number): Promise { + return this.backend.getEndToEndSessionProblem(deviceKey, timestamp); } - filterOutNotifiedErrorDevices(devices) { - return this._backend.filterOutNotifiedErrorDevices(devices); + public filterOutNotifiedErrorDevices(devices: IOlmDevice[]): Promise { + return this.backend.filterOutNotifiedErrorDevices(devices); } // Inbound group sessions @@ -438,10 +483,13 @@ export class IndexedDBCryptoStore { * @param {function(object)} func Called with A map from sessionId * to Base64 end-to-end session. */ - getEndToEndInboundGroupSession(senderCurve25519Key, sessionId, txn, func) { - this._backend.getEndToEndInboundGroupSession( - senderCurve25519Key, sessionId, txn, func, - ); + public getEndToEndInboundGroupSession( + senderCurve25519Key: string, + sessionId: string, + txn: IDBTransaction, + func: (groupSession: InboundGroupSessionData | null, groupSessionWithheld: IWithheld | null) => void, + ): void { + this.backend.getEndToEndInboundGroupSession(senderCurve25519Key, sessionId, txn, func); } /** @@ -451,8 +499,11 @@ export class IndexedDBCryptoStore { * in the store with an object having keys {senderKey, sessionId, * sessionData}, then once with null to indicate the end of the list. */ - getAllEndToEndInboundGroupSessions(txn, func) { - this._backend.getAllEndToEndInboundGroupSessions(txn, func); + public getAllEndToEndInboundGroupSessions( + txn: IDBTransaction, + func: (session: ISession | null) => void, + ): void { + this.backend.getAllEndToEndInboundGroupSessions(txn, func); } /** @@ -464,10 +515,13 @@ export class IndexedDBCryptoStore { * @param {object} sessionData The session data structure * @param {*} txn An active transaction. See doTxn(). */ - addEndToEndInboundGroupSession(senderCurve25519Key, sessionId, sessionData, txn) { - this._backend.addEndToEndInboundGroupSession( - senderCurve25519Key, sessionId, sessionData, txn, - ); + public addEndToEndInboundGroupSession( + senderCurve25519Key: string, + sessionId: string, + sessionData: InboundGroupSessionData, + txn: IDBTransaction, + ): void { + this.backend.addEndToEndInboundGroupSession(senderCurve25519Key, sessionId, sessionData, txn); } /** @@ -479,18 +533,22 @@ export class IndexedDBCryptoStore { * @param {object} sessionData The session data structure * @param {*} txn An active transaction. See doTxn(). */ - storeEndToEndInboundGroupSession(senderCurve25519Key, sessionId, sessionData, txn) { - this._backend.storeEndToEndInboundGroupSession( - senderCurve25519Key, sessionId, sessionData, txn, - ); + public storeEndToEndInboundGroupSession( + senderCurve25519Key: string, + sessionId: string, + sessionData: InboundGroupSessionData, + txn: IDBTransaction, + ): void { + this.backend.storeEndToEndInboundGroupSession(senderCurve25519Key, sessionId, sessionData, txn); } - storeEndToEndInboundGroupSessionWithheld( - senderCurve25519Key, sessionId, sessionData, txn, - ) { - this._backend.storeEndToEndInboundGroupSessionWithheld( - senderCurve25519Key, sessionId, sessionData, txn, - ); + public storeEndToEndInboundGroupSessionWithheld( + senderCurve25519Key: string, + sessionId: string, + sessionData: IWithheld, + txn: IDBTransaction, + ): void { + this.backend.storeEndToEndInboundGroupSessionWithheld(senderCurve25519Key, sessionId, sessionData, txn); } // End-to-end device tracking @@ -505,8 +563,8 @@ export class IndexedDBCryptoStore { * @param {Object} deviceData * @param {*} txn An active transaction. See doTxn(). */ - storeEndToEndDeviceData(deviceData, txn) { - this._backend.storeEndToEndDeviceData(deviceData, txn); + public storeEndToEndDeviceData(deviceData: IDeviceData, txn: IDBTransaction): void { + this.backend.storeEndToEndDeviceData(deviceData, txn); } /** @@ -516,8 +574,8 @@ export class IndexedDBCryptoStore { * @param {function(Object)} func Function called with the * device data */ - getEndToEndDeviceData(txn, func) { - this._backend.getEndToEndDeviceData(txn, func); + public getEndToEndDeviceData(txn: IDBTransaction, func: (deviceData: IDeviceData | null) => void): void { + this.backend.getEndToEndDeviceData(txn, func); } // End to End Rooms @@ -528,8 +586,8 @@ export class IndexedDBCryptoStore { * @param {object} roomInfo The end-to-end info for the room. * @param {*} txn An active transaction. See doTxn(). */ - storeEndToEndRoom(roomId, roomInfo, txn) { - this._backend.storeEndToEndRoom(roomId, roomInfo, txn); + public storeEndToEndRoom(roomId: string, roomInfo: IRoomEncryption, txn: IDBTransaction): void { + this.backend.storeEndToEndRoom(roomId, roomInfo, txn); } /** @@ -537,20 +595,20 @@ export class IndexedDBCryptoStore { * @param {*} txn An active transaction. See doTxn(). * @param {function(Object)} func Function called with the end to end encrypted rooms */ - getEndToEndRooms(txn, func) { - this._backend.getEndToEndRooms(txn, func); + public getEndToEndRooms(txn: IDBTransaction, func: (rooms: Record) => void): void { + this.backend.getEndToEndRooms(txn, func); } // session backups /** * Get the inbound group sessions that need to be backed up. - * @param {integer} limit The maximum number of sessions to retrieve. 0 + * @param {number} limit The maximum number of sessions to retrieve. 0 * for no limit. * @returns {Promise} resolves to an array of inbound group sessions */ - getSessionsNeedingBackup(limit) { - return this._backend.getSessionsNeedingBackup(limit); + public getSessionsNeedingBackup(limit: number): Promise { + return this.backend.getSessionsNeedingBackup(limit); } /** @@ -558,8 +616,8 @@ export class IndexedDBCryptoStore { * @param {*} txn An active transaction. See doTxn(). (optional) * @returns {Promise} resolves to the number of sessions */ - countSessionsNeedingBackup(txn) { - return this._backend.countSessionsNeedingBackup(txn); + public countSessionsNeedingBackup(txn?: IDBTransaction): Promise { + return this.backend.countSessionsNeedingBackup(txn); } /** @@ -568,8 +626,8 @@ export class IndexedDBCryptoStore { * @param {*} txn An active transaction. See doTxn(). (optional) * @returns {Promise} resolves when the sessions are unmarked */ - unmarkSessionsNeedingBackup(sessions, txn) { - return this._backend.unmarkSessionsNeedingBackup(sessions, txn); + public unmarkSessionsNeedingBackup(sessions: ISession[], txn?: IDBTransaction): Promise { + return this.backend.unmarkSessionsNeedingBackup(sessions, txn); } /** @@ -578,8 +636,8 @@ export class IndexedDBCryptoStore { * @param {*} txn An active transaction. See doTxn(). (optional) * @returns {Promise} resolves when the sessions are marked */ - markSessionsNeedingBackup(sessions, txn) { - return this._backend.markSessionsNeedingBackup(sessions, txn); + public markSessionsNeedingBackup(sessions: ISession[], txn?: IDBTransaction): Promise { + return this.backend.markSessionsNeedingBackup(sessions, txn); } /** @@ -589,10 +647,13 @@ export class IndexedDBCryptoStore { * @param {string} sessionId The ID of the session * @param {*} txn An active transaction. See doTxn(). (optional) */ - addSharedHistoryInboundGroupSession(roomId, senderKey, sessionId, txn) { - this._backend.addSharedHistoryInboundGroupSession( - roomId, senderKey, sessionId, txn, - ); + public addSharedHistoryInboundGroupSession( + roomId: string, + senderKey: string, + sessionId: string, + txn?: IDBTransaction, + ): void { + this.backend.addSharedHistoryInboundGroupSession(roomId, senderKey, sessionId, txn); } /** @@ -601,8 +662,11 @@ export class IndexedDBCryptoStore { * @param {*} txn An active transaction. See doTxn(). (optional) * @returns {Promise} Resolves to an array of [senderKey, sessionId] */ - getSharedHistoryInboundGroupSessions(roomId, txn) { - return this._backend.getSharedHistoryInboundGroupSessions(roomId, txn); + public getSharedHistoryInboundGroupSessions( + roomId: string, + txn?: IDBTransaction, + ): Promise<[senderKey: string, sessionId: string][]> { + return this.backend.getSharedHistoryInboundGroupSessions(roomId, txn); } /** @@ -627,18 +691,7 @@ export class IndexedDBCryptoStore { * reject with that exception. On synchronous backends, the * exception will propagate to the caller of the getFoo method. */ - doTxn(mode, stores, func, log) { - return this._backend.doTxn(mode, stores, func, log); + doTxn(mode: Mode, stores: Iterable, func: (txn: IDBTransaction) => T, log?: PrefixedLogger): Promise { + return this.backend.doTxn(mode, stores, func, log); } } - -IndexedDBCryptoStore.STORE_ACCOUNT = 'account'; -IndexedDBCryptoStore.STORE_SESSIONS = 'sessions'; -IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS = 'inbound_group_sessions'; -IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS_WITHHELD - = 'inbound_group_sessions_withheld'; -IndexedDBCryptoStore.STORE_SHARED_HISTORY_INBOUND_GROUP_SESSIONS - = 'shared_history_inbound_group_sessions'; -IndexedDBCryptoStore.STORE_DEVICE_DATA = 'device_data'; -IndexedDBCryptoStore.STORE_ROOMS = 'rooms'; -IndexedDBCryptoStore.STORE_BACKUP = 'sessions_needing_backup'; diff --git a/src/crypto/store/localStorage-crypto-store.js b/src/crypto/store/localStorage-crypto-store.ts similarity index 62% rename from src/crypto/store/localStorage-crypto-store.js rename to src/crypto/store/localStorage-crypto-store.ts index 0a982311e..2a96a7123 100644 --- a/src/crypto/store/localStorage-crypto-store.js +++ b/src/crypto/store/localStorage-crypto-store.ts @@ -1,6 +1,5 @@ /* -Copyright 2017, 2018 New Vector Ltd -Copyright 2020 The Matrix.org Foundation C.I.C. +Copyright 2017 - 2021 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -17,6 +16,12 @@ limitations under the License. import { logger } from '../../logger'; import { MemoryCryptoStore } from './memory-crypto-store'; +import { IDeviceData, IProblem, ISession, ISessionInfo, IWithheld, Mode } from "./base"; +import { IOlmDevice } from "../algorithms/megolm"; +import { IRoomEncryption } from "../RoomList"; +import { ICrossSigningKey } from "../../client"; +import { InboundGroupSessionData } from "../../@types/partials"; +import { IEncryptedPayload } from "../aes"; /** * Internal module. Partial localStorage backed storage for e2e. @@ -38,23 +43,23 @@ const KEY_INBOUND_SESSION_WITHHELD_PREFIX = E2E_PREFIX + "inboundgroupsessions.w const KEY_ROOMS_PREFIX = E2E_PREFIX + "rooms/"; const KEY_SESSIONS_NEEDING_BACKUP = E2E_PREFIX + "sessionsneedingbackup"; -function keyEndToEndSessions(deviceKey) { +function keyEndToEndSessions(deviceKey: string): string { return E2E_PREFIX + "sessions/" + deviceKey; } -function keyEndToEndSessionProblems(deviceKey) { +function keyEndToEndSessionProblems(deviceKey: string): string { return E2E_PREFIX + "session.problems/" + deviceKey; } -function keyEndToEndInboundGroupSession(senderKey, sessionId) { +function keyEndToEndInboundGroupSession(senderKey: string, sessionId: string): string { return KEY_INBOUND_SESSION_PREFIX + senderKey + "/" + sessionId; } -function keyEndToEndInboundGroupSessionWithheld(senderKey, sessionId) { +function keyEndToEndInboundGroupSessionWithheld(senderKey: string, sessionId: string): string { return KEY_INBOUND_SESSION_WITHHELD_PREFIX + senderKey + "/" + sessionId; } -function keyEndToEndRoomsPrefix(roomId) { +function keyEndToEndRoomsPrefix(roomId: string): string { return KEY_ROOMS_PREFIX + roomId; } @@ -62,24 +67,23 @@ function keyEndToEndRoomsPrefix(roomId) { * @implements {module:crypto/store/base~CryptoStore} */ export class LocalStorageCryptoStore extends MemoryCryptoStore { - constructor(webStore) { - super(); - this.store = webStore; - } - - static exists(webStore) { - const length = webStore.length; + public static exists(store: Storage): boolean { + const length = store.length; for (let i = 0; i < length; i++) { - if (webStore.key(i).startsWith(E2E_PREFIX)) { + if (store.key(i).startsWith(E2E_PREFIX)) { return true; } } return false; } + constructor(private readonly store: Storage) { + super(); + } + // Olm Sessions - countEndToEndSessions(txn, func) { + public countEndToEndSessions(txn: unknown, func: (count: number) => void): void { let count = 0; for (let i = 0; i < this.store.length; ++i) { if (this.store.key(i).startsWith(keyEndToEndSessions(''))) ++count; @@ -87,9 +91,10 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore { func(count); } - _getEndToEndSessions(deviceKey, txn, func) { + // eslint-disable-next-line @typescript-eslint/naming-convention + private _getEndToEndSessions(deviceKey: string): Record { const sessions = getJsonItem(this.store, keyEndToEndSessions(deviceKey)); - const fixedSessions = {}; + const fixedSessions: Record = {}; // fix up any old sessions to be objects rather than just the base64 pickle for (const [sid, val] of Object.entries(sessions || {})) { @@ -105,16 +110,25 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore { return fixedSessions; } - getEndToEndSession(deviceKey, sessionId, txn, func) { + public getEndToEndSession( + deviceKey: string, + sessionId: string, + txn: unknown, + func: (session: ISessionInfo) => void, + ): void { const sessions = this._getEndToEndSessions(deviceKey); func(sessions[sessionId] || {}); } - getEndToEndSessions(deviceKey, txn, func) { + public getEndToEndSessions( + deviceKey: string, + txn: unknown, + func: (sessions: { [sessionId: string]: ISessionInfo }) => void, + ): void { func(this._getEndToEndSessions(deviceKey) || {}); } - getAllEndToEndSessions(txn, func) { + public getAllEndToEndSessions(txn: unknown, func: (session: ISessionInfo) => void): void { for (let i = 0; i < this.store.length; ++i) { if (this.store.key(i).startsWith(keyEndToEndSessions(''))) { const deviceKey = this.store.key(i).split('/')[1]; @@ -125,17 +139,15 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore { } } - storeEndToEndSession(deviceKey, sessionId, sessionInfo, txn) { + public storeEndToEndSession(deviceKey: string, sessionId: string, sessionInfo: ISessionInfo, txn: unknown): void { const sessions = this._getEndToEndSessions(deviceKey) || {}; sessions[sessionId] = sessionInfo; - setJsonItem( - this.store, keyEndToEndSessions(deviceKey), sessions, - ); + setJsonItem(this.store, keyEndToEndSessions(deviceKey), sessions); } - async storeEndToEndSessionProblem(deviceKey, type, fixed) { + public async storeEndToEndSessionProblem(deviceKey: string, type: string, fixed: boolean): Promise { const key = keyEndToEndSessionProblems(deviceKey); - const problems = getJsonItem(this.store, key) || []; + const problems = getJsonItem(this.store, key) || []; problems.push({ type, fixed, time: Date.now() }); problems.sort((a, b) => { return a.time - b.time; @@ -143,9 +155,9 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore { setJsonItem(this.store, key, problems); } - async getEndToEndSessionProblem(deviceKey, timestamp) { + async getEndToEndSessionProblem(deviceKey: string, timestamp: number): Promise { const key = keyEndToEndSessionProblems(deviceKey); - const problems = getJsonItem(this.store, key) || []; + const problems = getJsonItem(this.store, key) || []; if (!problems.length) { return null; } @@ -162,9 +174,8 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore { } } - async filterOutNotifiedErrorDevices(devices) { - const notifiedErrorDevices = - getJsonItem(this.store, KEY_NOTIFIED_ERROR_DEVICES) || {}; + public async filterOutNotifiedErrorDevices(devices: IOlmDevice[]): Promise { + const notifiedErrorDevices = getJsonItem(this.store, KEY_NOTIFIED_ERROR_DEVICES) || {}; const ret = []; for (const device of devices) { @@ -187,7 +198,12 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore { // Inbound Group Sessions - getEndToEndInboundGroupSession(senderCurve25519Key, sessionId, txn, func) { + public getEndToEndInboundGroupSession( + senderCurve25519Key: string, + sessionId: string, + txn: unknown, + func: (groupSession: InboundGroupSessionData | null, groupSessionWithheld: IWithheld | null) => void, + ): void { func( getJsonItem( this.store, @@ -200,7 +216,7 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore { ); } - getAllEndToEndInboundGroupSessions(txn, func) { + public getAllEndToEndInboundGroupSessions(txn: unknown, func: (session: ISession | null) => void): void { for (let i = 0; i < this.store.length; ++i) { const key = this.store.key(i); if (key.startsWith(KEY_INBOUND_SESSION_PREFIX)) { @@ -219,7 +235,12 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore { func(null); } - addEndToEndInboundGroupSession(senderCurve25519Key, sessionId, sessionData, txn) { + public addEndToEndInboundGroupSession( + senderCurve25519Key: string, + sessionId: string, + sessionData: InboundGroupSessionData, + txn: unknown, + ): void { const existing = getJsonItem( this.store, keyEndToEndInboundGroupSession(senderCurve25519Key, sessionId), @@ -231,7 +252,12 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore { } } - storeEndToEndInboundGroupSession(senderCurve25519Key, sessionId, sessionData, txn) { + public storeEndToEndInboundGroupSession( + senderCurve25519Key: string, + sessionId: string, + sessionData: InboundGroupSessionData, + txn: unknown, + ): void { setJsonItem( this.store, keyEndToEndInboundGroupSession(senderCurve25519Key, sessionId), @@ -239,9 +265,12 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore { ); } - storeEndToEndInboundGroupSessionWithheld( - senderCurve25519Key, sessionId, sessionData, txn, - ) { + public storeEndToEndInboundGroupSessionWithheld( + senderCurve25519Key: string, + sessionId: string, + sessionData: IWithheld, + txn: unknown, + ): void { setJsonItem( this.store, keyEndToEndInboundGroupSessionWithheld(senderCurve25519Key, sessionId), @@ -249,25 +278,19 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore { ); } - getEndToEndDeviceData(txn, func) { - func(getJsonItem( - this.store, KEY_DEVICE_DATA, - )); + public getEndToEndDeviceData(txn: unknown, func: (deviceData: IDeviceData | null) => void): void { + func(getJsonItem(this.store, KEY_DEVICE_DATA)); } - storeEndToEndDeviceData(deviceData, txn) { - setJsonItem( - this.store, KEY_DEVICE_DATA, deviceData, - ); + public storeEndToEndDeviceData(deviceData: IDeviceData, txn: unknown): void { + setJsonItem(this.store, KEY_DEVICE_DATA, deviceData); } - storeEndToEndRoom(roomId, roomInfo, txn) { - setJsonItem( - this.store, keyEndToEndRoomsPrefix(roomId), roomInfo, - ); + public storeEndToEndRoom(roomId: string, roomInfo: IRoomEncryption, txn: unknown): void { + setJsonItem(this.store, keyEndToEndRoomsPrefix(roomId), roomInfo); } - getEndToEndRooms(txn, func) { + public getEndToEndRooms(txn: unknown, func: (rooms: Record) => void): void { const result = {}; const prefix = keyEndToEndRoomsPrefix(''); @@ -281,9 +304,8 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore { func(result); } - getSessionsNeedingBackup(limit) { - const sessionsNeedingBackup - = getJsonItem(this.store, KEY_SESSIONS_NEEDING_BACKUP) || {}; + public getSessionsNeedingBackup(limit: number): Promise { + const sessionsNeedingBackup = getJsonItem(this.store, KEY_SESSIONS_NEEDING_BACKUP) || {}; const sessions = []; for (const session in sessionsNeedingBackup) { @@ -309,13 +331,12 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore { return Promise.resolve(sessions); } - countSessionsNeedingBackup() { - const sessionsNeedingBackup - = getJsonItem(this.store, KEY_SESSIONS_NEEDING_BACKUP) || {}; + public countSessionsNeedingBackup(): Promise { + const sessionsNeedingBackup = getJsonItem(this.store, KEY_SESSIONS_NEEDING_BACKUP) || {}; return Promise.resolve(Object.keys(sessionsNeedingBackup).length); } - unmarkSessionsNeedingBackup(sessions) { + public unmarkSessionsNeedingBackup(sessions: ISession[]): Promise { const sessionsNeedingBackup = getJsonItem(this.store, KEY_SESSIONS_NEEDING_BACKUP) || {}; for (const session of sessions) { @@ -327,7 +348,7 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore { return Promise.resolve(); } - markSessionsNeedingBackup(sessions) { + public markSessionsNeedingBackup(sessions: ISession[]): Promise { const sessionsNeedingBackup = getJsonItem(this.store, KEY_SESSIONS_NEEDING_BACKUP) || {}; for (const session of sessions) { @@ -344,52 +365,46 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore { * * @returns {Promise} Promise which resolves when the store has been cleared. */ - deleteAllData() { + public deleteAllData(): Promise { this.store.removeItem(KEY_END_TO_END_ACCOUNT); return Promise.resolve(); } // Olm account - getAccount(txn, func) { - const account = getJsonItem(this.store, KEY_END_TO_END_ACCOUNT); - func(account); + public getAccount(txn: unknown, func: (accountPickle: string) => void): void { + const accountPickle = getJsonItem(this.store, KEY_END_TO_END_ACCOUNT); + func(accountPickle); } - storeAccount(txn, newData) { - setJsonItem( - this.store, KEY_END_TO_END_ACCOUNT, newData, - ); + public storeAccount(txn: unknown, accountPickle: string): void { + setJsonItem(this.store, KEY_END_TO_END_ACCOUNT, accountPickle); } - getCrossSigningKeys(txn, func) { - const keys = getJsonItem(this.store, KEY_CROSS_SIGNING_KEYS); + public getCrossSigningKeys(txn: unknown, func: (keys: Record) => void): void { + const keys = getJsonItem>(this.store, KEY_CROSS_SIGNING_KEYS); func(keys); } - getSecretStorePrivateKey(txn, func, type) { - const key = getJsonItem(this.store, E2E_PREFIX + `ssss_cache.${type}`); + public getSecretStorePrivateKey(txn: unknown, func: (key: IEncryptedPayload | null) => T, type: string): T { + const key = getJsonItem(this.store, E2E_PREFIX + `ssss_cache.${type}`); func(key); } - storeCrossSigningKeys(txn, keys) { - setJsonItem( - this.store, KEY_CROSS_SIGNING_KEYS, keys, - ); + public storeCrossSigningKeys(txn: unknown, keys: Record): void { + setJsonItem(this.store, KEY_CROSS_SIGNING_KEYS, keys); } - storeSecretStorePrivateKey(txn, type, key) { - setJsonItem( - this.store, E2E_PREFIX + `ssss_cache.${type}`, key, - ); + public storeSecretStorePrivateKey(txn: unknown, type: string, key: IEncryptedPayload): void { + setJsonItem(this.store, E2E_PREFIX + `ssss_cache.${type}`, key); } - doTxn(mode, stores, func) { + doTxn(mode: Mode, stores: Iterable, func: (txn: unknown) => T): Promise { return Promise.resolve(func(null)); } } -function getJsonItem(store, key) { +function getJsonItem(store: Storage, key: string): T | null { try { // if the key is absent, store.getItem() returns null, and // JSON.parse(null) === null, so this returns null. @@ -401,6 +416,6 @@ function getJsonItem(store, key) { return null; } -function setJsonItem(store, key, val) { +function setJsonItem(store: Storage, key: string, val: T): void { store.setItem(key, JSON.stringify(val)); } diff --git a/src/crypto/store/memory-crypto-store.js b/src/crypto/store/memory-crypto-store.ts similarity index 51% rename from src/crypto/store/memory-crypto-store.js rename to src/crypto/store/memory-crypto-store.ts index 9577fcad3..eca11b9e9 100644 --- a/src/crypto/store/memory-crypto-store.js +++ b/src/crypto/store/memory-crypto-store.ts @@ -1,7 +1,5 @@ /* -Copyright 2017 Vector Creations Ltd -Copyright 2018 New Vector Ltd -Copyright 2020 The Matrix.org Foundation C.I.C. +Copyright 2017 - 2021 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -18,6 +16,22 @@ limitations under the License. import { logger } from '../../logger'; import * as utils from "../../utils"; +import { + CryptoStore, + IDeviceData, + IProblem, + ISession, + ISessionInfo, + IWithheld, + Mode, + OutgoingRoomKeyRequest, +} from "./base"; +import { IRoomKeyRequestBody } from "../index"; +import { ICrossSigningKey } from "../../client"; +import { IOlmDevice } from "../algorithms/megolm"; +import { IRoomEncryption } from "../RoomList"; +import { InboundGroupSessionData } from "../../@types/partials"; +import { IEncryptedPayload } from "../aes"; /** * Internal module. in-memory storage for e2e. @@ -28,32 +42,22 @@ import * as utils from "../../utils"; /** * @implements {module:crypto/store/base~CryptoStore} */ -export class MemoryCryptoStore { - constructor() { - this._outgoingRoomKeyRequests = []; - this._account = null; - this._crossSigningKeys = null; - this._privateKeys = {}; - this._backupKeys = {}; +export class MemoryCryptoStore implements CryptoStore { + private outgoingRoomKeyRequests: OutgoingRoomKeyRequest[] = []; + private account: string = null; + private crossSigningKeys: Record = null; + private privateKeys: Record = {}; - // Map of {devicekey -> {sessionId -> session pickle}} - this._sessions = {}; - // Map of {devicekey -> array of problems} - this._sessionProblems = {}; - // Map of {userId -> deviceId -> true} - this._notifiedErrorDevices = {}; - // Map of {senderCurve25519Key+'/'+sessionId -> session data object} - this._inboundGroupSessions = {}; - this._inboundGroupSessionsWithheld = {}; - // Opaque device data object - this._deviceData = null; - // roomId -> Opaque roomInfo object - this._rooms = {}; - // Set of {senderCurve25519Key+'/'+sessionId} - this._sessionsNeedingBackup = {}; - // roomId -> array of [senderKey, sessionId] - this._sharedHistoryInboundGroupSessions = {}; - } + private sessions: { [deviceKey: string]: { [sessionId: string]: ISessionInfo } } = {}; + private sessionProblems: { [deviceKey: string]: IProblem[] } = {}; + private notifiedErrorDevices: { [userId: string]: { [deviceId: string]: boolean } } = {}; + private inboundGroupSessions: { [sessionKey: string]: InboundGroupSessionData } = {}; + private inboundGroupSessionsWithheld: Record = {}; + // Opaque device data object + private deviceData: IDeviceData = null; + private rooms: { [roomId: string]: IRoomEncryption } = {}; + private sessionsNeedingBackup: { [sessionKey: string]: boolean } = {}; + private sharedHistoryInboundGroupSessions: { [roomId: string]: [senderKey: string, sessionId: string][] } = {}; /** * Ensure the database exists and is up-to-date. @@ -62,7 +66,7 @@ export class MemoryCryptoStore { * * @return {Promise} resolves to the store. */ - async startup() { + public async startup(): Promise { // No startup work to do for the memory store. return this; } @@ -72,7 +76,7 @@ export class MemoryCryptoStore { * * @returns {Promise} Promise which resolves when the store has been cleared. */ - deleteAllData() { + public deleteAllData(): Promise { return Promise.resolve(); } @@ -86,7 +90,7 @@ export class MemoryCryptoStore { * {@link module:crypto/store/base~OutgoingRoomKeyRequest}: either the * same instance as passed in, or the existing one. */ - getOrAddOutgoingRoomKeyRequest(request) { + public getOrAddOutgoingRoomKeyRequest(request: OutgoingRoomKeyRequest): Promise { const requestBody = request.requestBody; return utils.promiseTry(() => { @@ -109,7 +113,7 @@ export class MemoryCryptoStore { `enqueueing key request for ${requestBody.room_id} / ` + requestBody.session_id, ); - this._outgoingRoomKeyRequests.push(request); + this.outgoingRoomKeyRequests.push(request); return request; }); } @@ -124,7 +128,7 @@ export class MemoryCryptoStore { * {@link module:crypto/store/base~OutgoingRoomKeyRequest}, or null if * not found */ - getOutgoingRoomKeyRequest(requestBody) { + public getOutgoingRoomKeyRequest(requestBody: IRoomKeyRequestBody): Promise { return Promise.resolve(this._getOutgoingRoomKeyRequest(requestBody)); } @@ -139,8 +143,9 @@ export class MemoryCryptoStore { * @return {module:crypto/store/base~OutgoingRoomKeyRequest?} * the matching request, or null if not found */ - _getOutgoingRoomKeyRequest(requestBody) { - for (const existing of this._outgoingRoomKeyRequests) { + // eslint-disable-next-line @typescript-eslint/naming-convention + private _getOutgoingRoomKeyRequest(requestBody: IRoomKeyRequestBody): OutgoingRoomKeyRequest | null { + for (const existing of this.outgoingRoomKeyRequests) { if (utils.deepCompare(existing.requestBody, requestBody)) { return existing; } @@ -157,8 +162,8 @@ export class MemoryCryptoStore { * {@link module:crypto/store/base~OutgoingRoomKeyRequest}, or null if * there are no pending requests in those states */ - getOutgoingRoomKeyRequestByState(wantedStates) { - for (const req of this._outgoingRoomKeyRequests) { + public getOutgoingRoomKeyRequestByState(wantedStates: number[]): Promise { + for (const req of this.outgoingRoomKeyRequests) { for (const state of wantedStates) { if (req.state === state) { return Promise.resolve(req); @@ -173,18 +178,22 @@ export class MemoryCryptoStore { * @param {Number} wantedState * @return {Promise>} All OutgoingRoomKeyRequests in state */ - getAllOutgoingRoomKeyRequestsByState(wantedState) { + public getAllOutgoingRoomKeyRequestsByState(wantedState: number): Promise { return Promise.resolve( - this._outgoingRoomKeyRequests.filter( + this.outgoingRoomKeyRequests.filter( (r) => r.state == wantedState, ), ); } - getOutgoingRoomKeyRequestsByTarget(userId, deviceId, wantedStates) { + public getOutgoingRoomKeyRequestsByTarget( + userId: string, + deviceId: string, + wantedStates: number[], + ): Promise { const results = []; - for (const req of this._outgoingRoomKeyRequests) { + for (const req of this.outgoingRoomKeyRequests) { for (const state of wantedStates) { if (req.state === state && req.recipients.includes({ userId, deviceId })) { results.push(req); @@ -206,8 +215,12 @@ export class MemoryCryptoStore { * {@link module:crypto/store/base~OutgoingRoomKeyRequest} * updated request, or null if no matching row was found */ - updateOutgoingRoomKeyRequest(requestId, expectedState, updates) { - for (const req of this._outgoingRoomKeyRequests) { + public updateOutgoingRoomKeyRequest( + requestId: string, + expectedState: number, + updates: Partial, + ): Promise { + for (const req of this.outgoingRoomKeyRequests) { if (req.requestId !== requestId) { continue; } @@ -235,9 +248,12 @@ export class MemoryCryptoStore { * * @returns {Promise} resolves once the operation is completed */ - deleteOutgoingRoomKeyRequest(requestId, expectedState) { - for (let i = 0; i < this._outgoingRoomKeyRequests.length; i++) { - const req = this._outgoingRoomKeyRequests[i]; + public deleteOutgoingRoomKeyRequest( + requestId: string, + expectedState: number, + ): Promise { + for (let i = 0; i < this.outgoingRoomKeyRequests.length; i++) { + const req = this.outgoingRoomKeyRequests[i]; if (req.requestId !== requestId) { continue; @@ -251,7 +267,7 @@ export class MemoryCryptoStore { return Promise.resolve(null); } - this._outgoingRoomKeyRequests.splice(i, 1); + this.outgoingRoomKeyRequests.splice(i, 1); return Promise.resolve(req); } @@ -260,48 +276,57 @@ export class MemoryCryptoStore { // Olm Account - getAccount(txn, func) { - func(this._account); + public getAccount(txn: unknown, func: (accountPickle: string) => void) { + func(this.account); } - storeAccount(txn, newData) { - this._account = newData; + public storeAccount(txn: unknown, accountPickle: string): void { + this.account = accountPickle; } - getCrossSigningKeys(txn, func) { - func(this._crossSigningKeys); + public getCrossSigningKeys(txn: unknown, func: (keys: Record) => void): void { + func(this.crossSigningKeys); } - getSecretStorePrivateKey(txn, func, type) { - const result = this._privateKeys[type]; + public getSecretStorePrivateKey(txn: unknown, func: (key: IEncryptedPayload | null) => T, type: string): T { + const result = this.privateKeys[type]; return func(result || null); } - storeCrossSigningKeys(txn, keys) { - this._crossSigningKeys = keys; + public storeCrossSigningKeys(txn: unknown, keys: Record): void { + this.crossSigningKeys = keys; } - storeSecretStorePrivateKey(txn, type, key) { - this._privateKeys[type] = key; + public storeSecretStorePrivateKey(txn: unknown, type: string, key: IEncryptedPayload): void { + this.privateKeys[type] = key; } // Olm Sessions - countEndToEndSessions(txn, func) { - return Object.keys(this._sessions).length; + public countEndToEndSessions(txn: unknown, func: (count: number) => void): void { + return Object.keys(this.sessions).length; } - getEndToEndSession(deviceKey, sessionId, txn, func) { - const deviceSessions = this._sessions[deviceKey] || {}; + public getEndToEndSession( + deviceKey: string, + sessionId: string, + txn: unknown, + func: (session: ISessionInfo) => void, + ): void { + const deviceSessions = this.sessions[deviceKey] || {}; func(deviceSessions[sessionId] || null); } - getEndToEndSessions(deviceKey, txn, func) { - func(this._sessions[deviceKey] || {}); + public getEndToEndSessions( + deviceKey: string, + txn: unknown, + func: (sessions: { [sessionId: string]: ISessionInfo }) => void, + ): void { + func(this.sessions[deviceKey] || {}); } - getAllEndToEndSessions(txn, func) { - Object.entries(this._sessions).forEach(([deviceKey, deviceSessions]) => { + public getAllEndToEndSessions(txn: unknown, func: (session: ISessionInfo) => void): void { + Object.entries(this.sessions).forEach(([deviceKey, deviceSessions]) => { Object.entries(deviceSessions).forEach(([sessionId, session]) => { func({ ...session, @@ -312,26 +337,25 @@ export class MemoryCryptoStore { }); } - storeEndToEndSession(deviceKey, sessionId, sessionInfo, txn) { - let deviceSessions = this._sessions[deviceKey]; + public storeEndToEndSession(deviceKey: string, sessionId: string, sessionInfo: ISessionInfo, txn: unknown): void { + let deviceSessions = this.sessions[deviceKey]; if (deviceSessions === undefined) { deviceSessions = {}; - this._sessions[deviceKey] = deviceSessions; + this.sessions[deviceKey] = deviceSessions; } deviceSessions[sessionId] = sessionInfo; } - async storeEndToEndSessionProblem(deviceKey, type, fixed) { - const problems = this._sessionProblems[deviceKey] - = this._sessionProblems[deviceKey] || []; + public async storeEndToEndSessionProblem(deviceKey: string, type: string, fixed: boolean): Promise { + const problems = this.sessionProblems[deviceKey] = this.sessionProblems[deviceKey] || []; problems.push({ type, fixed, time: Date.now() }); problems.sort((a, b) => { return a.time - b.time; }); } - async getEndToEndSessionProblem(deviceKey, timestamp) { - const problems = this._sessionProblems[deviceKey] || []; + public async getEndToEndSessionProblem(deviceKey: string, timestamp: number): Promise { + const problems = this.sessionProblems[deviceKey] || []; if (!problems.length) { return null; } @@ -348,9 +372,9 @@ export class MemoryCryptoStore { } } - async filterOutNotifiedErrorDevices(devices) { - const notifiedErrorDevices = this._notifiedErrorDevices; - const ret = []; + public async filterOutNotifiedErrorDevices(devices: IOlmDevice[]): Promise { + const notifiedErrorDevices = this.notifiedErrorDevices; + const ret: IOlmDevice[] = []; for (const device of devices) { const { userId, deviceInfo } = device; @@ -370,16 +394,24 @@ export class MemoryCryptoStore { // Inbound Group Sessions - getEndToEndInboundGroupSession(senderCurve25519Key, sessionId, txn, func) { + public getEndToEndInboundGroupSession( + senderCurve25519Key: string, + sessionId: string, + txn: unknown, + func: (groupSession: InboundGroupSessionData | null, groupSessionWithheld: IWithheld | null) => void, + ): void { const k = senderCurve25519Key+'/'+sessionId; func( - this._inboundGroupSessions[k] || null, - this._inboundGroupSessionsWithheld[k] || null, + this.inboundGroupSessions[k] || null, + this.inboundGroupSessionsWithheld[k] || null, ); } - getAllEndToEndInboundGroupSessions(txn, func) { - for (const key of Object.keys(this._inboundGroupSessions)) { + public getAllEndToEndInboundGroupSessions( + txn: unknown, + func: (session: ISession | null) => void, + ): void { + for (const key of Object.keys(this.inboundGroupSessions)) { // we can't use split, as the components we are trying to split out // might themselves contain '/' characters. We rely on the // senderKey being a (32-byte) curve25519 key, base64-encoded @@ -388,58 +420,71 @@ export class MemoryCryptoStore { func({ senderKey: key.substr(0, 43), sessionId: key.substr(44), - sessionData: this._inboundGroupSessions[key], + sessionData: this.inboundGroupSessions[key], }); } func(null); } - addEndToEndInboundGroupSession(senderCurve25519Key, sessionId, sessionData, txn) { + public addEndToEndInboundGroupSession( + senderCurve25519Key: string, + sessionId: string, + sessionData: InboundGroupSessionData, + txn: unknown, + ): void { const k = senderCurve25519Key+'/'+sessionId; - if (this._inboundGroupSessions[k] === undefined) { - this._inboundGroupSessions[k] = sessionData; + if (this.inboundGroupSessions[k] === undefined) { + this.inboundGroupSessions[k] = sessionData; } } - storeEndToEndInboundGroupSession(senderCurve25519Key, sessionId, sessionData, txn) { - this._inboundGroupSessions[senderCurve25519Key+'/'+sessionId] = sessionData; + public storeEndToEndInboundGroupSession( + senderCurve25519Key: string, + sessionId: string, + sessionData: InboundGroupSessionData, + txn: unknown, + ): void { + this.inboundGroupSessions[senderCurve25519Key+'/'+sessionId] = sessionData; } - storeEndToEndInboundGroupSessionWithheld( - senderCurve25519Key, sessionId, sessionData, txn, - ) { + public storeEndToEndInboundGroupSessionWithheld( + senderCurve25519Key: string, + sessionId: string, + sessionData: IWithheld, + txn: unknown, + ): void { const k = senderCurve25519Key+'/'+sessionId; - this._inboundGroupSessionsWithheld[k] = sessionData; + this.inboundGroupSessionsWithheld[k] = sessionData; } // Device Data - getEndToEndDeviceData(txn, func) { - func(this._deviceData); + public getEndToEndDeviceData(txn: unknown, func: (deviceData: IDeviceData | null) => void): void { + func(this.deviceData); } - storeEndToEndDeviceData(deviceData, txn) { - this._deviceData = deviceData; + public storeEndToEndDeviceData(deviceData: IDeviceData, txn: unknown): void { + this.deviceData = deviceData; } // E2E rooms - storeEndToEndRoom(roomId, roomInfo, txn) { - this._rooms[roomId] = roomInfo; + public storeEndToEndRoom(roomId: string, roomInfo: IRoomEncryption, txn: unknown): void { + this.rooms[roomId] = roomInfo; } - getEndToEndRooms(txn, func) { - func(this._rooms); + public getEndToEndRooms(txn: unknown, func: (rooms: Record) => void): void { + func(this.rooms); } - getSessionsNeedingBackup(limit) { - const sessions = []; - for (const session in this._sessionsNeedingBackup) { - if (this._inboundGroupSessions[session]) { + public getSessionsNeedingBackup(limit: number): Promise { + const sessions: ISession[] = []; + for (const session in this.sessionsNeedingBackup) { + if (this.inboundGroupSessions[session]) { sessions.push({ senderKey: session.substr(0, 43), sessionId: session.substr(44), - sessionData: this._inboundGroupSessions[session], + sessionData: this.inboundGroupSessions[session], }); if (limit && session.length >= limit) { break; @@ -449,39 +494,39 @@ export class MemoryCryptoStore { return Promise.resolve(sessions); } - countSessionsNeedingBackup() { - return Promise.resolve(Object.keys(this._sessionsNeedingBackup).length); + public countSessionsNeedingBackup(): Promise { + return Promise.resolve(Object.keys(this.sessionsNeedingBackup).length); } - unmarkSessionsNeedingBackup(sessions) { + public unmarkSessionsNeedingBackup(sessions: ISession[]): Promise { for (const session of sessions) { const sessionKey = session.senderKey + '/' + session.sessionId; - delete this._sessionsNeedingBackup[sessionKey]; + delete this.sessionsNeedingBackup[sessionKey]; } return Promise.resolve(); } - markSessionsNeedingBackup(sessions) { + public markSessionsNeedingBackup(sessions: ISession[]): Promise { for (const session of sessions) { const sessionKey = session.senderKey + '/' + session.sessionId; - this._sessionsNeedingBackup[sessionKey] = true; + this.sessionsNeedingBackup[sessionKey] = true; } return Promise.resolve(); } - addSharedHistoryInboundGroupSession(roomId, senderKey, sessionId) { - const sessions = this._sharedHistoryInboundGroupSessions[roomId] || []; + public addSharedHistoryInboundGroupSession(roomId: string, senderKey: string, sessionId: string): void { + const sessions = this.sharedHistoryInboundGroupSessions[roomId] || []; sessions.push([senderKey, sessionId]); - this._sharedHistoryInboundGroupSessions[roomId] = sessions; + this.sharedHistoryInboundGroupSessions[roomId] = sessions; } - getSharedHistoryInboundGroupSessions(roomId) { - return Promise.resolve(this._sharedHistoryInboundGroupSessions[roomId] || []); + public getSharedHistoryInboundGroupSessions(roomId: string): Promise<[senderKey: string, sessionId: string][]> { + return Promise.resolve(this.sharedHistoryInboundGroupSessions[roomId] || []); } // Session key backups - doTxn(mode, stores, func) { + public doTxn(mode: Mode, stores: Iterable, func: (txn?: unknown) => T): Promise { return Promise.resolve(func(null)); } } diff --git a/src/indexeddb-helpers.js b/src/indexeddb-helpers.ts similarity index 88% rename from src/indexeddb-helpers.js rename to src/indexeddb-helpers.ts index 01123338d..84de7c0e9 100644 --- a/src/indexeddb-helpers.js +++ b/src/indexeddb-helpers.ts @@ -22,8 +22,8 @@ limitations under the License. * @param {string} dbName The database name to test for * @returns {boolean} Whether the database exists */ -export function exists(indexedDB, dbName) { - return new Promise((resolve, reject) => { +export function exists(indexedDB: IDBFactory, dbName: string): Promise { + return new Promise((resolve, reject) => { let exists = true; const req = indexedDB.open(dbName); req.onupgradeneeded = () => { @@ -31,7 +31,7 @@ export function exists(indexedDB, dbName) { // should only fire if the DB did not exist before at any version. exists = false; }; - req.onblocked = () => reject(); + req.onblocked = () => reject(req.error); req.onsuccess = () => { const db = req.result; db.close(); @@ -45,6 +45,6 @@ export function exists(indexedDB, dbName) { } resolve(exists); }; - req.onerror = ev => reject(ev.target.error); + req.onerror = ev => reject(req.error); }); } diff --git a/src/logger.ts b/src/logger.ts index bd1f98b25..d80fb2048 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -62,7 +62,7 @@ log.methodFactory = function(methodName, logLevel, loggerName) { export const logger: PrefixedLogger = log.getLogger(DEFAULT_NAMESPACE); logger.setLevel(log.levels.DEBUG); -interface PrefixedLogger extends Logger { +export interface PrefixedLogger extends Logger { withPrefix?: (prefix: string) => PrefixedLogger; prefix?: string; } diff --git a/src/store/indexeddb-local-backend.ts b/src/store/indexeddb-local-backend.ts index 9a7e803bb..c5c25fc19 100644 --- a/src/store/indexeddb-local-backend.ts +++ b/src/store/indexeddb-local-backend.ts @@ -117,7 +117,7 @@ function reqAsCursorPromise(req: IDBRequest): Promise { dbName = "matrix-js-sdk:" + (dbName || "default"); return IndexedDBHelpers.exists(indexedDB, dbName); } diff --git a/src/store/indexeddb.ts b/src/store/indexeddb.ts index f41a898c6..51fa88d5f 100644 --- a/src/store/indexeddb.ts +++ b/src/store/indexeddb.ts @@ -47,7 +47,7 @@ interface IOpts extends IBaseOpts { } export class IndexedDBStore extends MemoryStore { - static exists(indexedDB: IDBFactory, dbName: string): boolean { + static exists(indexedDB: IDBFactory, dbName: string): Promise { return LocalIndexedDBStoreBackend.exists(indexedDB, dbName); } From f227a307c147aa6643ec47d5380063b834e4cfe9 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Sat, 24 Jul 2021 11:40:30 +0100 Subject: [PATCH 059/118] Fix some confused signatures/types --- src/crypto/store/base.ts | 2 +- .../store/indexeddb-crypto-store-backend.ts | 15 ++++++++++++--- src/crypto/store/indexeddb-crypto-store.ts | 4 ++-- src/crypto/store/localStorage-crypto-store.ts | 2 +- src/crypto/store/memory-crypto-store.ts | 6 +++--- 5 files changed, 19 insertions(+), 10 deletions(-) diff --git a/src/crypto/store/base.ts b/src/crypto/store/base.ts index 03ba6ef8e..7ace6f2da 100644 --- a/src/crypto/store/base.ts +++ b/src/crypto/store/base.ts @@ -60,7 +60,7 @@ export interface CryptoStore { getAccount(txn: unknown, func: (accountPickle: string) => void); storeAccount(txn: unknown, accountPickle: string): void; getCrossSigningKeys(txn: unknown, func: (keys: Record) => void): void; - getSecretStorePrivateKey(txn: unknown, func: (key: IEncryptedPayload | null) => T, type: string): void; + getSecretStorePrivateKey(txn: unknown, func: (key: IEncryptedPayload | null) => void, type: string): void; storeCrossSigningKeys(txn: unknown, keys: Record): void; storeSecretStorePrivateKey(txn: unknown, type: string, key: IEncryptedPayload): void; diff --git a/src/crypto/store/indexeddb-crypto-store-backend.ts b/src/crypto/store/indexeddb-crypto-store-backend.ts index b4735ad5b..20553f1e9 100644 --- a/src/crypto/store/indexeddb-crypto-store-backend.ts +++ b/src/crypto/store/indexeddb-crypto-store-backend.ts @@ -54,11 +54,20 @@ export class Backend implements CryptoStore { // attempts to delete the database will block (and subsequent // attempts to re-create it will also block). db.onversionchange = () => { - logger.log(`versionchange for indexeddb ${this._dbName}: closing`); + logger.log(`versionchange for indexeddb ${this.db.name}: closing`); db.close(); }; } + public async startup(): Promise { + // No work to do, as the startup is done by the caller (e.g IndexedDBCryptoStore) + // by passing us a ready IDBDatabase instance + return this; + } + public async deleteAllData(): Promise { + throw Error("This is not implemented, call IDBFactory::deleteDatabase(dbName) instead."); + } + /** * Look for an existing outgoing room key request, and if none is found, * add a new one @@ -387,9 +396,9 @@ export class Backend implements CryptoStore { }; } - public getSecretStorePrivateKey( + public getSecretStorePrivateKey( txn: IDBTransaction, - func: (key: IEncryptedPayload | null) => T, + func: (key: IEncryptedPayload | null) => void, type: string, ): void { const objectStore = txn.objectStore("account"); diff --git a/src/crypto/store/indexeddb-crypto-store.ts b/src/crypto/store/indexeddb-crypto-store.ts index 18008ae72..268d62128 100644 --- a/src/crypto/store/indexeddb-crypto-store.ts +++ b/src/crypto/store/indexeddb-crypto-store.ts @@ -353,9 +353,9 @@ export class IndexedDBCryptoStore implements CryptoStore { * @param {function(string)} func Called with the private key * @param {string} type A key type */ - public getSecretStorePrivateKey( + public getSecretStorePrivateKey( txn: IDBTransaction, - func: (key: IEncryptedPayload | null) => T, + func: (key: IEncryptedPayload | null) => void, type: string, ): void { this.backend.getSecretStorePrivateKey(txn, func, type); diff --git a/src/crypto/store/localStorage-crypto-store.ts b/src/crypto/store/localStorage-crypto-store.ts index 2a96a7123..6e0492191 100644 --- a/src/crypto/store/localStorage-crypto-store.ts +++ b/src/crypto/store/localStorage-crypto-store.ts @@ -386,7 +386,7 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore { func(keys); } - public getSecretStorePrivateKey(txn: unknown, func: (key: IEncryptedPayload | null) => T, type: string): T { + public getSecretStorePrivateKey(txn: unknown, func: (key: IEncryptedPayload | null) => void, type: string): void { const key = getJsonItem(this.store, E2E_PREFIX + `ssss_cache.${type}`); func(key); } diff --git a/src/crypto/store/memory-crypto-store.ts b/src/crypto/store/memory-crypto-store.ts index eca11b9e9..da0bb1cf3 100644 --- a/src/crypto/store/memory-crypto-store.ts +++ b/src/crypto/store/memory-crypto-store.ts @@ -288,9 +288,9 @@ export class MemoryCryptoStore implements CryptoStore { func(this.crossSigningKeys); } - public getSecretStorePrivateKey(txn: unknown, func: (key: IEncryptedPayload | null) => T, type: string): T { + public getSecretStorePrivateKey(txn: unknown, func: (key: IEncryptedPayload | null) => void, type: string): void { const result = this.privateKeys[type]; - return func(result || null); + func(result || null); } public storeCrossSigningKeys(txn: unknown, keys: Record): void { @@ -304,7 +304,7 @@ export class MemoryCryptoStore implements CryptoStore { // Olm Sessions public countEndToEndSessions(txn: unknown, func: (count: number) => void): void { - return Object.keys(this.sessions).length; + func(Object.keys(this.sessions).length); } public getEndToEndSession( From c340e6f1ba6540c7177c83df2e2b80684c654e4d Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Sat, 24 Jul 2021 12:01:23 +0100 Subject: [PATCH 060/118] delint and fix tests --- src/client.ts | 3 -- src/crypto/OutgoingRoomKeyRequestManager.ts | 55 ++++++++++----------- src/crypto/store/memory-crypto-store.ts | 2 +- 3 files changed, 28 insertions(+), 32 deletions(-) diff --git a/src/client.ts b/src/client.ts index 72cabc6ce..ac7647479 100644 --- a/src/client.ts +++ b/src/client.ts @@ -89,9 +89,6 @@ import { IRecoveryKey, ISecretStorageKeyInfo, } from "./crypto/api"; -import { MemoryCryptoStore } from "./crypto/store/memory-crypto-store"; -import { LocalStorageCryptoStore } from "./crypto/store/localStorage-crypto-store"; -import { IndexedDBCryptoStore } from "./crypto/store/indexeddb-crypto-store"; import { SyncState } from "./sync.api"; import { EventTimelineSet } from "./models/event-timeline-set"; import { VerificationRequest } from "./crypto/verification/request/VerificationRequest"; diff --git a/src/crypto/OutgoingRoomKeyRequestManager.ts b/src/crypto/OutgoingRoomKeyRequestManager.ts index b152f248d..e053d8072 100644 --- a/src/crypto/OutgoingRoomKeyRequestManager.ts +++ b/src/crypto/OutgoingRoomKeyRequestManager.ts @@ -137,9 +137,7 @@ export class OutgoingRoomKeyRequestManager { recipients: IRoomKeyRequestRecipient[], resend = false, ): Promise { - const req = await this.cryptoStore.getOutgoingRoomKeyRequest( - requestBody, - ); + const req = await this.cryptoStore.getOutgoingRoomKeyRequest(requestBody); if (!req) { await this.cryptoStore.getOrAddOutgoingRoomKeyRequest({ requestBody: requestBody, @@ -237,10 +235,10 @@ export class OutgoingRoomKeyRequestManager { * @returns {Promise} resolves when the request has been updated in our * pending list. */ - public cancelRoomKeyRequest(requestBody: IRoomKeyRequestBody): Promise { + public cancelRoomKeyRequest(requestBody: IRoomKeyRequestBody): Promise { return this.cryptoStore.getOutgoingRoomKeyRequest( requestBody, - ).then((req) => { + ).then((req): unknown => { if (!req) { // no request was made for this key return; @@ -263,8 +261,7 @@ export class OutgoingRoomKeyRequestManager { 'deleting unnecessary room key request for ' + stringifyRequestBody(requestBody), ); - return this.cryptoStore.deleteOutgoingRoomKeyRequest(req.requestId, RoomKeyRequestState.Unsent) - .then(); // match Promise signature + return this.cryptoStore.deleteOutgoingRoomKeyRequest(req.requestId, RoomKeyRequestState.Unsent); case RoomKeyRequestState.Sent: { // send a cancellation. @@ -414,7 +411,7 @@ export class OutgoingRoomKeyRequestManager { } // given a RoomKeyRequest, send it and update the request record - private async sendOutgoingRoomKeyRequest(req: OutgoingRoomKeyRequest): Promise { + private sendOutgoingRoomKeyRequest(req: OutgoingRoomKeyRequest): Promise { logger.log( `Requesting keys for ${stringifyRequestBody(req.requestBody)}` + ` from ${stringifyRecipientList(req.recipients)}` + @@ -428,19 +425,19 @@ export class OutgoingRoomKeyRequestManager { body: req.requestBody, }; - await this.sendMessageToDevices(requestMessage, req.recipients, req.requestTxnId || req.requestId); - await this.cryptoStore.updateOutgoingRoomKeyRequest( - req.requestId, RoomKeyRequestState.Unsent, - { state: RoomKeyRequestState.Sent }, - ); + return this.sendMessageToDevices( + requestMessage, req.recipients, req.requestTxnId || req.requestId, + ).then(() => { + return this.cryptoStore.updateOutgoingRoomKeyRequest( + req.requestId, RoomKeyRequestState.Unsent, + { state: RoomKeyRequestState.Sent }, + ); + }); } // Given a RoomKeyRequest, cancel it and delete the request record unless // andResend is set, in which case transition to UNSENT. - private async sendOutgoingRoomKeyRequestCancellation( - req: OutgoingRoomKeyRequest, - andResend = false, - ): Promise { + private sendOutgoingRoomKeyRequestCancellation(req: OutgoingRoomKeyRequest, andResend = false): Promise { logger.log( `Sending cancellation for key request for ` + `${stringifyRequestBody(req.requestBody)} to ` + @@ -454,19 +451,21 @@ export class OutgoingRoomKeyRequestManager { request_id: req.requestId, }; - await this.sendMessageToDevices(requestMessage, req.recipients, req.cancellationTxnId); - if (andResend) { - // We want to resend, so transition to UNSENT - await this.cryptoStore.updateOutgoingRoomKeyRequest( - req.requestId, - RoomKeyRequestState.CancellationPendingAndWillResend, - { state: RoomKeyRequestState.Unsent }, - ); - } else { - await this.cryptoStore.deleteOutgoingRoomKeyRequest( + return this.sendMessageToDevices( + requestMessage, req.recipients, req.cancellationTxnId, + ).then(() => { + if (andResend) { + // We want to resend, so transition to UNSENT + return this.cryptoStore.updateOutgoingRoomKeyRequest( + req.requestId, + RoomKeyRequestState.CancellationPendingAndWillResend, + { state: RoomKeyRequestState.Unsent }, + ); + } + return this.cryptoStore.deleteOutgoingRoomKeyRequest( req.requestId, RoomKeyRequestState.CancellationPending, ); - } + }); } // send a RoomKeyRequest to a list of recipients diff --git a/src/crypto/store/memory-crypto-store.ts b/src/crypto/store/memory-crypto-store.ts index da0bb1cf3..6668f7a6d 100644 --- a/src/crypto/store/memory-crypto-store.ts +++ b/src/crypto/store/memory-crypto-store.ts @@ -225,7 +225,7 @@ export class MemoryCryptoStore implements CryptoStore { continue; } - if (req.state != expectedState) { + if (req.state !== expectedState) { logger.warn( `Cannot update room key request from ${expectedState} ` + `as it was already updated to ${req.state}`, From db4b6fe2af175984f316468800e8f90985a64039 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Sat, 24 Jul 2021 12:10:45 +0100 Subject: [PATCH 061/118] Update to beta better-docs to fix the gendocs issue --- package.json | 2 +- yarn.lock | 115 ++++++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 106 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index 854021a71..6bad277b6 100644 --- a/package.json +++ b/package.json @@ -83,7 +83,7 @@ "@typescript-eslint/parser": "^4.17.0", "babel-jest": "^26.6.3", "babelify": "^10.0.0", - "better-docs": "^2.3.2", + "better-docs": "^2.4.0-beta.9", "browserify": "^17.0.0", "docdash": "^1.2.0", "eslint": "7.18.0", diff --git a/yarn.lock b/yarn.lock index 563a8e3ba..46d7ff503 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1910,16 +1910,19 @@ bcrypt-pbkdf@^1.0.0: dependencies: tweetnacl "^0.14.3" -better-docs@^2.3.2: - version "2.3.2" - resolved "https://registry.yarnpkg.com/better-docs/-/better-docs-2.3.2.tgz#0de059301c49669a4350409d8c235868cf8bcbf7" - integrity sha512-VlbXQgEftaynJSaPa853XB5WqTlPoQQr2TnxIkKi6OsyJJxF42Ke+9SIES/hqTe58aaBnuoDGrIzOso8RdNx6Q== +better-docs@^2.4.0-beta.9: + version "2.4.0-beta.9" + resolved "https://registry.yarnpkg.com/better-docs/-/better-docs-2.4.0-beta.9.tgz#9ffa25b90b9a0fe4eb97528faedf53c04c416cfe" + integrity sha512-ehR/4gxVE8+hIdiDN2sP/YOYxbOptUpuXcV0qMcN2snyn+gWAtFBqXmrRUcFJRj+e6HCdD3I2fK8Jy50+cvhVg== dependencies: brace "^0.11.1" + marked "^1.1.1" + prism-react-renderer "^1.1.1" react-ace "^6.5.0" react-docgen "^5.3.0" react-frame-component "^4.1.1" - typescript "^3.7.5" + react-live "^2.2.2" + react-simple-code-editor "^0.11.0" underscore "^1.9.1" vue-docgen-api "^3.22.0" vue2-ace-editor "^0.0.13" @@ -2159,6 +2162,18 @@ bser@2.1.1: dependencies: node-int64 "^0.4.0" +buble@0.19.6: + version "0.19.6" + resolved "https://registry.yarnpkg.com/buble/-/buble-0.19.6.tgz#915909b6bd5b11ee03b1c885ec914a8b974d34d3" + integrity sha512-9kViM6nJA1Q548Jrd06x0geh+BG2ru2+RMDkIHHgJY/8AcyCs34lTHwra9BX7YdPrZXd5aarkpr/SY8bmPgPdg== + dependencies: + chalk "^2.4.1" + magic-string "^0.25.1" + minimist "^1.2.0" + os-homedir "^1.0.1" + regexpu-core "^4.2.0" + vlq "^1.0.0" + buffer-from@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" @@ -2280,7 +2295,7 @@ center-align@^0.1.1: align-text "^0.1.3" lazy-cache "^1.0.3" -chalk@^2.0.0: +chalk@^2.0.0, chalk@^2.4.1: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== @@ -2479,6 +2494,16 @@ component-emitter@^1.2.1: resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg== +component-props@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/component-props/-/component-props-1.1.1.tgz#f9b7df9b9927b6e6d97c9bd272aa867670f34944" + integrity sha1-+bffm5kntubZfJvScqqGdnDzSUQ= + +component-xor@0.0.4: + version "0.0.4" + resolved "https://registry.yarnpkg.com/component-xor/-/component-xor-0.0.4.tgz#c55d83ccc1b94cd5089a4e93fa7891c7263e59aa" + integrity sha1-xV2DzMG5TNUImk6T+niRxyY+Wao= + concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" @@ -2544,7 +2569,7 @@ core-js-compat@^3.14.0, core-js-compat@^3.15.0: browserslist "^4.16.6" semver "7.0.0" -core-js@^2.4.0, core-js@^2.5.3, core-js@^2.6.5: +core-js@^2.4.0, core-js@^2.4.1, core-js@^2.5.3, core-js@^2.6.5: version "2.6.12" resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.12.tgz#d9333dfa7b065e347cc5682219d6f690859cc2ec" integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ== @@ -2818,6 +2843,14 @@ doctypes@^1.1.0: resolved "https://registry.yarnpkg.com/doctypes/-/doctypes-1.1.0.tgz#ea80b106a87538774e8a3a4a5afe293de489e0a9" integrity sha1-6oCxBqh1OHdOijpKWv4pPeSJ4Kk= +dom-iterator@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/dom-iterator/-/dom-iterator-1.0.0.tgz#9c09899846ec41c2d257adc4d6015e4759ef05ad" + integrity sha512-7dsMOQI07EMU98gQM8NSB3GsAiIeBYIPKpnxR3c9xOvdvBjChAcOM0iJ222I3p5xyiZO9e5oggkNaCusuTdYig== + dependencies: + component-props "1.1.1" + component-xor "0.0.4" + domain-browser@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda" @@ -4949,6 +4982,13 @@ lru-cache@^6.0.0: dependencies: yallist "^4.0.0" +magic-string@^0.25.1: + version "0.25.7" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.7.tgz#3f497d6fd34c669c6798dcb821f2ef31f5445051" + integrity sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA== + dependencies: + sourcemap-codec "^1.4.4" + make-dir@^2.0.0, make-dir@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5" @@ -4999,6 +5039,11 @@ markdown-it@^10.0.0: mdurl "^1.0.1" uc.micro "^1.0.5" +marked@^1.1.1: + version "1.2.9" + resolved "https://registry.yarnpkg.com/marked/-/marked-1.2.9.tgz#53786f8b05d4c01a2a5a76b7d1ec9943d29d72dc" + integrity sha512-H8lIX2SvyitGX+TRdtS06m1jHMijKN/XjfH6Ooii9fvxMlh8QdqBfBDkGUpMWH2kQNrtixjzYUa3SH8ROTgRRw== + marked@^2.0.3: version "2.1.3" resolved "https://registry.yarnpkg.com/marked/-/marked-2.1.3.tgz#bd017cef6431724fd4b27e0657f5ceb14bff3753" @@ -5406,6 +5451,11 @@ os-browserify@~0.3.0: resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27" integrity sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc= +os-homedir@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" + integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M= + p-each-series@^2.1.0: version "2.2.0" resolved "https://registry.yarnpkg.com/p-each-series/-/p-each-series-2.2.0.tgz#105ab0357ce72b202a8a8b94933672657b5e2a9a" @@ -5638,6 +5688,11 @@ pretty-format@^26.0.0, pretty-format@^26.6.2: ansi-styles "^4.0.0" react-is "^17.0.1" +prism-react-renderer@^1.0.1, prism-react-renderer@^1.1.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/prism-react-renderer/-/prism-react-renderer-1.2.1.tgz#392460acf63540960e5e3caa699d851264e99b89" + integrity sha512-w23ch4f75V1Tnz8DajsYKvY5lF7H1+WvzvLUcF0paFxkTHSp42RS0H5CttdN2Q8RR3DRGZ9v5xD/h3n8C8kGmg== + private@^0.1.8: version "0.1.8" resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff" @@ -5673,7 +5728,7 @@ prompts@^2.0.1: kleur "^3.0.3" sisteransi "^1.0.5" -prop-types@^15.7.2: +prop-types@^15.5.8, prop-types@^15.7.2: version "15.7.2" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ== @@ -5917,6 +5972,29 @@ react-is@^17.0.1: resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== +react-live@^2.2.2: + version "2.2.3" + resolved "https://registry.yarnpkg.com/react-live/-/react-live-2.2.3.tgz#260f99194213799f0005e473e7a4154c699d6a7c" + integrity sha512-tpKruvfytNETuzO3o1mrQUj180GVrq35IE8F5gH1NJVPt4szYCx83/dOSCOyjgRhhc3gQvl0pQ3k/CjOjwJkKQ== + dependencies: + buble "0.19.6" + core-js "^2.4.1" + dom-iterator "^1.0.0" + prism-react-renderer "^1.0.1" + prop-types "^15.5.8" + react-simple-code-editor "^0.10.0" + unescape "^1.0.1" + +react-simple-code-editor@^0.10.0: + version "0.10.0" + resolved "https://registry.yarnpkg.com/react-simple-code-editor/-/react-simple-code-editor-0.10.0.tgz#73e7ac550a928069715482aeb33ccba36efe2373" + integrity sha512-bL5W5mAxSW6+cLwqqVWY47Silqgy2DKDTR4hDBrLrUqC5BXc29YVx17l2IZk5v36VcDEq1Bszu2oHm1qBwKqBA== + +react-simple-code-editor@^0.11.0: + version "0.11.0" + resolved "https://registry.yarnpkg.com/react-simple-code-editor/-/react-simple-code-editor-0.11.0.tgz#bb57c7c29b570f2ab229872599eac184f5bc673c" + integrity sha512-xGfX7wAzspl113ocfKQAR8lWPhavGWHL3xSzNLeseDRHysT+jzRBi/ExdUqevSMos+7ZtdfeuBOXtgk9HTwsrw== + read-only-stream@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/read-only-stream/-/read-only-stream-2.0.0.tgz#2724fd6a8113d73764ac288d4386270c1dbf17f0" @@ -6054,7 +6132,7 @@ regexpp@^3.1.0: resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2" integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg== -regexpu-core@^4.7.1: +regexpu-core@^4.2.0, regexpu-core@^4.7.1: version "4.7.1" resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-4.7.1.tgz#2dea5a9a07233298fbf0db91fa9abc4c6e0f8ad6" integrity sha512-ywH2VUraA44DZQuRKzARmw6S66mr48pQVva4LBeRhcOltJ6hExvWly5ZjFLYo67xbIxb6W1q4bAGtgfEl20zfQ== @@ -6474,6 +6552,11 @@ source-map@^0.7.3, source-map@~0.7.2: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== +sourcemap-codec@^1.4.4: + version "1.4.8" + resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4" + integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA== + spdx-correct@^3.0.0: version "3.1.1" resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.1.tgz#dece81ac9c1e6713e5f7d1b6f17d468fa53d89a9" @@ -6976,7 +7059,7 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= -typescript@^3.2.2, typescript@^3.7.5: +typescript@^3.2.2: version "3.9.10" resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.10.tgz#70f3910ac7a51ed6bef79da7800690b19bf778b8" integrity sha512-w6fIxVE/H1PkLKcCPsFqKE7Kv7QUwhU8qQY2MueZXWx5cPZdwFupLgKK3vntcK98BtNHZtAF4LA/yl2a7k8R6Q== @@ -7056,6 +7139,13 @@ underscore@^1.9.1, underscore@~1.13.1: resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.13.1.tgz#0c1c6bd2df54b6b69f2314066d65b6cde6fcf9d1" integrity sha512-hzSoAVtJF+3ZtiFX0VgfFPHEDRm7Y/QPjGyNo4TVdnDTdft3tr8hEkD25a1jC+TjTuE7tkHGKkhwCgs9dgBB2g== +unescape@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/unescape/-/unescape-1.0.1.tgz#956e430f61cad8a4d57d82c518f5e6cc5d0dda96" + integrity sha512-O0+af1Gs50lyH1nUu3ZyYS1cRh01Q/kUKatTOkSs7jukXE6/NebucDVxyiDsA9AQ4JC1V1jUH9EO8JX2nMDgGQ== + dependencies: + extend-shallow "^2.0.1" + unhomoglyph@^1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/unhomoglyph/-/unhomoglyph-1.0.6.tgz#ea41f926d0fcf598e3b8bb2980c2ddac66b081d3" @@ -7211,6 +7301,11 @@ verror@1.10.0: core-util-is "1.0.2" extsprintf "^1.2.0" +vlq@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/vlq/-/vlq-1.0.1.tgz#c003f6e7c0b4c1edd623fd6ee50bbc0d6a1de468" + integrity sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w== + vm-browserify@^1.0.0: version "1.1.2" resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0" From faf237e58836bd6c5ef9c512faa0b1a5b72f452e Mon Sep 17 00:00:00 2001 From: Brad Murray Date: Sun, 25 Jul 2021 11:19:36 -0400 Subject: [PATCH 062/118] Respect the comment in getClearContent --- src/models/event.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/models/event.ts b/src/models/event.ts index 506dd5d48..cb54996c0 100644 --- a/src/models/event.ts +++ b/src/models/event.ts @@ -732,7 +732,7 @@ export class MatrixEvent extends EventEmitter { * @returns {Object} The cleartext (decrypted) content for the event */ public getClearContent(): IContent | null { - return this.clearEvent?.content || null; + return this.clearEvent ? this.clearEvent.content : null; } /** From e85b3b6a8d1f36441c270e900338031eb4313ce3 Mon Sep 17 00:00:00 2001 From: Brad Murray Date: Sun, 25 Jul 2021 11:41:08 -0400 Subject: [PATCH 063/118] Fix the tests and add a test --- spec/integ/megolm-integ.spec.js | 64 +++++++++++++++++++++++++++++++++ src/models/event.ts | 2 +- 2 files changed, 65 insertions(+), 1 deletion(-) diff --git a/spec/integ/megolm-integ.spec.js b/spec/integ/megolm-integ.spec.js index e2bc34c25..727d1030b 100644 --- a/spec/integ/megolm-integ.spec.js +++ b/spec/integ/megolm-integ.spec.js @@ -1025,4 +1025,68 @@ describe("megolm", function() { }); }); }); + + it("Alice can decrypt a message with falsey content", function() { + return aliceTestClient.start().then(() => { + return createOlmSession(testOlmAccount, aliceTestClient); + }).then((p2pSession) => { + const groupSession = new Olm.OutboundGroupSession(); + groupSession.create(); + + // make the room_key event + const roomKeyEncrypted = encryptGroupSessionKey({ + senderKey: testSenderKey, + recipient: aliceTestClient, + p2pSession: p2pSession, + groupSession: groupSession, + room_id: ROOM_ID, + }); + + const plaintext = { + type: "m.room.message", + content: undefined, + room_id: ROOM_ID, + } + + const messageEncrypted = { + event_id: 'test_megolm_event', + content: { + algorithm: "m.megolm.v1.aes-sha2", + ciphertext: groupSession.encrypt(JSON.stringify(plaintext)), + device_id: "testDevice", + sender_key: testSenderKey, + session_id: groupSession.session_id(), + }, + type: "m.room.encrypted", + }; + + // Alice gets both the events in a single sync + const syncResponse = { + next_batch: 1, + to_device: { + events: [roomKeyEncrypted], + }, + rooms: { + join: {}, + }, + }; + syncResponse.rooms.join[ROOM_ID] = { + timeline: { + events: [messageEncrypted], + }, + }; + + aliceTestClient.httpBackend.when("GET", "/sync").respond(200, syncResponse); + return aliceTestClient.flushSync(); + }).then(function() { + const room = aliceTestClient.client.getRoom(ROOM_ID); + const event = room.getLiveTimeline().getEvents()[0]; + expect(event.isEncrypted()).toBe(true); + return testUtils.awaitDecryption(event); + }).then((event) => { + expect(event.getRoomId()).toEqual(ROOM_ID); + expect(event.getContent()).toEqual({}); + expect(event.getClearContent()).toBeUndefined(); + }); + }); }); diff --git a/src/models/event.ts b/src/models/event.ts index 27a3ba391..6d72d5a8c 100644 --- a/src/models/event.ts +++ b/src/models/event.ts @@ -534,7 +534,7 @@ export class MatrixEvent extends EventEmitter { throw new Error("Attempt to decrypt event which isn't encrypted"); } - if (this.isDecryptionFailure()) { + if (this.clearEvent?.content && this.clearEvent.content.msgtype !== "m.bad.encrypted") { // we may want to just ignore this? let's start with rejecting it. throw new Error( "Attempt to decrypt event which has already been decrypted", From 8aa283d99410564487dd8ba2bff2027a962394f7 Mon Sep 17 00:00:00 2001 From: Brad Murray Date: Sun, 25 Jul 2021 11:45:09 -0400 Subject: [PATCH 064/118] Fix lint in tests --- spec/integ/megolm-integ.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/integ/megolm-integ.spec.js b/spec/integ/megolm-integ.spec.js index 727d1030b..f2150743e 100644 --- a/spec/integ/megolm-integ.spec.js +++ b/spec/integ/megolm-integ.spec.js @@ -1046,7 +1046,7 @@ describe("megolm", function() { type: "m.room.message", content: undefined, room_id: ROOM_ID, - } + }; const messageEncrypted = { event_id: 'test_megolm_event', From 4f8c20ddaee7e47c50f2c269b486584bb4bdae62 Mon Sep 17 00:00:00 2001 From: Brad Murray Date: Mon, 26 Jul 2021 07:49:42 -0400 Subject: [PATCH 065/118] Fix reattempting decryption check --- spec/unit/crypto.spec.js | 2 +- src/models/event.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/unit/crypto.spec.js b/spec/unit/crypto.spec.js index 816b952b1..878f79fbd 100644 --- a/spec/unit/crypto.spec.js +++ b/spec/unit/crypto.spec.js @@ -274,7 +274,7 @@ describe("Crypto", function() { // alice encrypts each event, and then bob tries to decrypt // them without any keys, so that they'll be in pending await aliceClient.crypto.encryptEvent(event, aliceRoom); - event.clearEvent = {}; + event.clearEvent = undefined; event.senderCurve25519Key = null; event.claimedEd25519Key = null; try { diff --git a/src/models/event.ts b/src/models/event.ts index 6d72d5a8c..47f6e1aee 100644 --- a/src/models/event.ts +++ b/src/models/event.ts @@ -534,7 +534,7 @@ export class MatrixEvent extends EventEmitter { throw new Error("Attempt to decrypt event which isn't encrypted"); } - if (this.clearEvent?.content && this.clearEvent.content.msgtype !== "m.bad.encrypted") { + if (this.clearEvent && !this.isDecryptionFailure()) { // we may want to just ignore this? let's start with rejecting it. throw new Error( "Attempt to decrypt event which has already been decrypted", From 6b8bb467903583839c03682ff2c5cba99d00d807 Mon Sep 17 00:00:00 2001 From: Dariusz Niemczyk Date: Mon, 26 Jul 2021 16:37:47 +0200 Subject: [PATCH 066/118] Update ESLint config --- yarn.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/yarn.lock b/yarn.lock index 563a8e3ba..d58868e4a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2984,8 +2984,8 @@ eslint-config-google@^0.14.0: integrity sha512-WsbX4WbjuMvTdeVL6+J3rK1RGhCTqjsFjX7UMSMgZiyxxaNLkoJENbrGExzERFeoTpGw3F3FypTiWAP9ZXzkEw== "eslint-plugin-matrix-org@github:matrix-org/eslint-plugin-matrix-org#main": - version "0.3.3" - resolved "https://codeload.github.com/matrix-org/eslint-plugin-matrix-org/tar.gz/50d6bdf6704dd95016d5f1f824f00cac6eaa64e1" + version "0.3.4" + resolved "https://codeload.github.com/matrix-org/eslint-plugin-matrix-org/tar.gz/45f6937539192e3820edcafc4d6d4d4187e85a6a" eslint-rule-composer@^0.3.0: version "0.3.0" From 21f107d7340e118d4ca6cf5ab51fc5e4fff7d39f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Tue, 27 Jul 2021 14:19:54 +0200 Subject: [PATCH 067/118] Add types for MSC3291 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/@types/event.ts | 1 + src/webrtc/callEventTypes.ts | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/src/@types/event.ts b/src/@types/event.ts index 42c7d0529..f1f5094f8 100644 --- a/src/@types/event.ts +++ b/src/@types/event.ts @@ -53,6 +53,7 @@ export enum EventType { CallReject = "m.call.reject", CallSelectAnswer = "m.call.select_answer", CallNegotiate = "m.call.negotiate", + CallSDPStreamMetadataChanged = "org.matrix.call.sdp_stream_metadata_changed", CallReplaces = "m.call.replaces", CallAssertedIdentity = "m.call.asserted_identity", CallAssertedIdentityPrefix = "org.matrix.call.asserted_identity", diff --git a/src/webrtc/callEventTypes.ts b/src/webrtc/callEventTypes.ts index 532567843..c380181f0 100644 --- a/src/webrtc/callEventTypes.ts +++ b/src/webrtc/callEventTypes.ts @@ -11,6 +11,8 @@ export enum SDPStreamMetadataPurpose { export interface SDPStreamMetadataObject { purpose: SDPStreamMetadataPurpose; + audio_muted: boolean; + video_muted: boolean; } export interface SDPStreamMetadata { @@ -40,6 +42,10 @@ export interface MCallOfferNegotiate { [SDPStreamMetadataKey]: SDPStreamMetadata; } +export interface MCallSDPStreamMetadataChanged { + [SDPStreamMetadataKey]: SDPStreamMetadata; +} + export interface MCallReplacesTarget { id: string; display_name: string; From 8e6040ad6f8ec31c9a908cea4d86e5fc4b51dc1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Tue, 27 Jul 2021 14:23:58 +0200 Subject: [PATCH 068/118] Give CallFeed a mute state MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/webrtc/callFeed.ts | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/webrtc/callFeed.ts b/src/webrtc/callFeed.ts index 69d6170ab..085291b8c 100644 --- a/src/webrtc/callFeed.ts +++ b/src/webrtc/callFeed.ts @@ -21,6 +21,7 @@ import { RoomMember } from "../models/room-member"; export enum CallFeedEvent { NewStream = "new_stream", + MuteStateChanged = "mute_state_changed" } export class CallFeed extends EventEmitter { @@ -30,6 +31,8 @@ export class CallFeed extends EventEmitter { public purpose: SDPStreamMetadataPurpose, private client: MatrixClient, private roomId: string, + private audioMuted?: boolean, + private videoMuted?: boolean, ) { super(); } @@ -51,15 +54,13 @@ export class CallFeed extends EventEmitter { return this.userId === this.client.getUserId(); } - // TODO: The two following methods should be later replaced - // by something that will also check if the remote is muted /** * Returns true if audio is muted or if there are no audio * tracks, otherwise returns false * @returns {boolean} is audio muted? */ public isAudioMuted(): boolean { - return this.stream.getAudioTracks().length === 0; + return this.stream.getAudioTracks().length === 0 || this.audioMuted; } /** @@ -69,7 +70,7 @@ export class CallFeed extends EventEmitter { */ public isVideoMuted(): boolean { // We assume only one video track - return this.stream.getVideoTracks().length === 0; + return this.stream.getVideoTracks().length === 0 || this.videoMuted; } /** @@ -81,4 +82,14 @@ export class CallFeed extends EventEmitter { this.stream = newStream; this.emit(CallFeedEvent.NewStream, this.stream); } + + public setAudioMuted(muted: boolean): void { + this.audioMuted = muted; + this.emit(CallFeedEvent.MuteStateChanged, this.audioMuted, this.videoMuted); + } + + public setVideoMuted(muted: boolean): void { + this.videoMuted = muted; + this.emit(CallFeedEvent.MuteStateChanged, this.audioMuted, this.videoMuted); + } } From 1df90f8fc7ffed34023ebcb3bb0a5b32bc02fd94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Tue, 27 Jul 2021 14:31:13 +0200 Subject: [PATCH 069/118] Basic implementation of MSC3291 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/webrtc/call.ts | 59 +++++++++++++++++++++++----------- src/webrtc/callEventHandler.ts | 9 ++++++ 2 files changed, 50 insertions(+), 18 deletions(-) diff --git a/src/webrtc/call.ts b/src/webrtc/call.ts index 6a9a8be76..3c25e22ac 100644 --- a/src/webrtc/call.ts +++ b/src/webrtc/call.ts @@ -36,6 +36,7 @@ import { SDPStreamMetadataPurpose, SDPStreamMetadata, SDPStreamMetadataKey, + MCallSDPStreamMetadataChanged, } from './callEventTypes'; import { CallFeed } from './callFeed'; @@ -353,8 +354,6 @@ export class MatrixCall extends EventEmitter { this.makingOffer = false; this.remoteOnHold = false; - this.micMuted = false; - this.vidMuted = false; this.feeds = []; @@ -398,6 +397,14 @@ export class MatrixCall extends EventEmitter { return this.remoteAssertedIdentity; } + public get localUsermediaFeed(): CallFeed { + return this.getLocalFeeds().find((feed) => feed.purpose === SDPStreamMetadataPurpose.Usermedia); + } + + private getFeedByStreamId(streamId: string): CallFeed { + return this.getFeeds().find((feed) => feed.stream.id === streamId); + } + /** * Returns an array of all CallFeeds * @returns {Array} CallFeeds @@ -427,10 +434,12 @@ export class MatrixCall extends EventEmitter { * @returns {SDPStreamMetadata} localSDPStreamMetadata */ private getLocalSDPStreamMetadata(): SDPStreamMetadata { - const metadata = {}; + const metadata: SDPStreamMetadata = {}; for (const localFeed of this.getLocalFeeds()) { metadata[localFeed.stream.id] = { purpose: localFeed.purpose, + audio_muted: localFeed.isAudioMuted(), + video_muted: localFeed.isVideoMuted(), }; } logger.debug("Got local SDPStreamMetadata", metadata); @@ -455,6 +464,8 @@ export class MatrixCall extends EventEmitter { const userId = this.getOpponentMember().userId; const purpose = this.remoteSDPStreamMetadata[stream.id].purpose; + const audioMuted = this.remoteSDPStreamMetadata[stream.id].audio_muted; + const videoMuted = this.remoteSDPStreamMetadata[stream.id].video_muted; if (!purpose) { logger.warn(`Ignoring stream with id ${stream.id} because we didn't get any metadata about it`); @@ -467,7 +478,7 @@ export class MatrixCall extends EventEmitter { if (existingFeed) { existingFeed.setNewStream(stream); } else { - this.feeds.push(new CallFeed(stream, userId, purpose, this.client, this.roomId)); + this.feeds.push(new CallFeed(stream, userId, purpose, this.client, this.roomId, audioMuted, videoMuted)); this.emit(CallEvent.FeedsChanged, this.feeds); } @@ -494,7 +505,7 @@ export class MatrixCall extends EventEmitter { // Try to find a feed with the same stream id as the new stream, // if we find it replace the old stream with the new one - const feed = this.feeds.find((feed) => feed.stream.id === stream.id); + const feed = this.getFeedByStreamId(stream.id); if (feed) { feed.setNewStream(stream); } else { @@ -513,7 +524,7 @@ export class MatrixCall extends EventEmitter { if (existingFeed) { existingFeed.setNewStream(stream); } else { - this.feeds.push(new CallFeed(stream, userId, purpose, this.client, this.roomId)); + this.feeds.push(new CallFeed(stream, userId, purpose, this.client, this.roomId, true, true)); this.emit(CallEvent.FeedsChanged, this.feeds); } @@ -551,7 +562,7 @@ export class MatrixCall extends EventEmitter { private deleteFeedByStream(stream: MediaStream) { logger.debug(`Removing feed with stream id ${stream.id}`); - const feed = this.feeds.find((feed) => feed.stream.id === stream.id); + const feed = this.getFeedByStreamId(stream.id); if (!feed) { logger.warn(`Didn't find the feed with stream id ${stream.id} to delete`); return; @@ -887,7 +898,7 @@ export class MatrixCall extends EventEmitter { * @param {boolean} muted True to mute the outbound video. */ setLocalVideoMuted(muted: boolean) { - this.vidMuted = muted; + this.localUsermediaFeed?.setVideoMuted(muted); this.updateMuteStatus(); } @@ -901,8 +912,7 @@ export class MatrixCall extends EventEmitter { * (including if the call is not set up yet). */ isLocalVideoMuted(): boolean { - if (this.type === CallType.Voice) return true; - return this.vidMuted; + return this.localUsermediaFeed?.isVideoMuted(); } /** @@ -910,7 +920,7 @@ export class MatrixCall extends EventEmitter { * @param {boolean} muted True to mute the mic. */ setMicrophoneMuted(muted: boolean) { - this.micMuted = muted; + this.localUsermediaFeed?.setAudioMuted(muted); this.updateMuteStatus(); } @@ -924,7 +934,7 @@ export class MatrixCall extends EventEmitter { * is not set up yet). */ isMicrophoneMuted(): boolean { - return this.micMuted; + return this.localUsermediaFeed?.isAudioMuted(); } /** @@ -987,14 +997,14 @@ export class MatrixCall extends EventEmitter { } private updateMuteStatus() { - if (!this.localAVStream) { - return; - } + this.sendVoipEvent(EventType.CallSDPStreamMetadataChanged, { + [SDPStreamMetadataKey]: this.getLocalSDPStreamMetadata(), + }); + + const micShouldBeMuted = this.localUsermediaFeed?.isAudioMuted() || this.remoteOnHold; + const vidShouldBeMuted = this.localUsermediaFeed?.isVideoMuted() || this.remoteOnHold; - const micShouldBeMuted = this.micMuted || this.remoteOnHold; setTracksEnabled(this.localAVStream.getAudioTracks(), !micShouldBeMuted); - - const vidShouldBeMuted = this.vidMuted || this.remoteOnHold; setTracksEnabled(this.localAVStream.getVideoTracks(), !vidShouldBeMuted); } @@ -1318,6 +1328,19 @@ export class MatrixCall extends EventEmitter { } } + public onSDPStreamMetadataChangedReceived(event: MatrixEvent): void { + // TODO: What if the values is missing + // TODO: Handle purpose changes + const content = event.getContent(); + const metadata = content[SDPStreamMetadataKey]; + this.remoteSDPStreamMetadata = metadata; + for (const feed of this.getRemoteFeeds()) { + const streamId = feed.stream.id; + feed.setAudioMuted(metadata[streamId].audio_muted); + feed.setVideoMuted(metadata[streamId].video_muted); + } + } + async onAssertedIdentityReceived(event: MatrixEvent) { if (!event.getContent().asserted_identity) return; diff --git a/src/webrtc/callEventHandler.ts b/src/webrtc/callEventHandler.ts index 263ddbf9b..fa498e633 100644 --- a/src/webrtc/callEventHandler.ts +++ b/src/webrtc/callEventHandler.ts @@ -297,6 +297,15 @@ export class CallEventHandler { } call.onAssertedIdentityReceived(event); + } else if (event.getType() === EventType.CallSDPStreamMetadataChanged) { + if (!call) return; + + if (event.getContent().party_id === call.ourPartyId) { + // Ignore remote echo + return; + } + + call.onSDPStreamMetadataChangedReceived(event); } } } From 9b5302ebf29738c7e061c3488e13ab70719163d8 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Tue, 27 Jul 2021 15:43:43 +0100 Subject: [PATCH 070/118] Prepare changelog for v12.2.0-rc.1 --- CHANGELOG.md | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f352afcf2..e770173aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,54 @@ +Changes in [12.2.0-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v12.2.0-rc.1) (2021-07-27) +============================================================================================================ +[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v12.1.0...v12.2.0-rc.1) + + * Update ESLint config + [\#1810](https://github.com/matrix-org/matrix-js-sdk/pull/1810) + * Clean up Event.clearEvent handling to fix a bug where malformed events with + falsey content wouldn't be considered decrypted + [\#1807](https://github.com/matrix-org/matrix-js-sdk/pull/1807) + * Update matrix-org-eslint-plugin and tighten max warning limit + [\#1806](https://github.com/matrix-org/matrix-js-sdk/pull/1806) + * Initial MSC3244 support and type cleanup + [\#1745](https://github.com/matrix-org/matrix-js-sdk/pull/1745) + * Functional members + [\#1771](https://github.com/matrix-org/matrix-js-sdk/pull/1771) + * yarn upgrade + [\#1802](https://github.com/matrix-org/matrix-js-sdk/pull/1802) + * Improve calculateRoomName performances by using Intl.Collator + [\#1801](https://github.com/matrix-org/matrix-js-sdk/pull/1801) + * Conform to new typescript eslint rules + [\#1798](https://github.com/matrix-org/matrix-js-sdk/pull/1798) + * Actually print IndexedDB command error if there is one + [\#1799](https://github.com/matrix-org/matrix-js-sdk/pull/1799) + * Update eslint-plugin-matrix-org + [\#1796](https://github.com/matrix-org/matrix-js-sdk/pull/1796) + * Add minimal types for "notification settings" UI + [\#1778](https://github.com/matrix-org/matrix-js-sdk/pull/1778) + * Switch from `url` to URL constructor + [\#1791](https://github.com/matrix-org/matrix-js-sdk/pull/1791) + * Switch callEventHandler from listening on `event` to `Room.timeline` + [\#1789](https://github.com/matrix-org/matrix-js-sdk/pull/1789) + * Contributing guidelines for new changelog generation + [\#1787](https://github.com/matrix-org/matrix-js-sdk/pull/1787) + * Fix link to CONTRIBUTING.md + [\#1786](https://github.com/matrix-org/matrix-js-sdk/pull/1786) + * Use an unstable prefix for MSC2885: Hidden read receipts + [\#1788](https://github.com/matrix-org/matrix-js-sdk/pull/1788) + * Standardise spelling and casing of homeserver, identity server, and + integration manager + [\#1782](https://github.com/matrix-org/matrix-js-sdk/pull/1782) + * Convert CONTRIBUTING to markdown + [\#1785](https://github.com/matrix-org/matrix-js-sdk/pull/1785) + * Use webpack worker-loader instead of homegrown hack + [\#1780](https://github.com/matrix-org/matrix-js-sdk/pull/1780) + * Convert IndexedDB store & worker to Typescript + [\#1779](https://github.com/matrix-org/matrix-js-sdk/pull/1779) + * Expose MatrixEvent's internal clearEvent as a function + [\#1784](https://github.com/matrix-org/matrix-js-sdk/pull/1784) + * Improve and consolidate typing + [\#1777](https://github.com/matrix-org/matrix-js-sdk/pull/1777) + Changes in [12.1.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v12.1.0) (2021-07-19) ================================================================================================== [Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v12.1.0-rc.1...v12.1.0) From f20b3211e6375054b32455bcb1fcfa7a127b3bc7 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Tue, 27 Jul 2021 15:43:44 +0100 Subject: [PATCH 071/118] v12.2.0-rc.1 --- package.json | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 854021a71..8ed7e6eaf 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-js-sdk", - "version": "12.1.0", + "version": "12.2.0-rc.1", "description": "Matrix Client-Server SDK for Javascript", "scripts": { "prepublishOnly": "yarn build", @@ -29,7 +29,7 @@ "keywords": [ "matrix-org" ], - "main": "./src/index.ts", + "main": "./lib/index.js", "browser": "./lib/browser-index.js", "matrix_src_main": "./src/index.ts", "matrix_src_browser": "./src/browser-index.js", @@ -111,5 +111,6 @@ "coverageReporters": [ "text" ] - } + }, + "typings": "./lib/index.d.ts" } From 959e3f68a47addd71c2f15b74af49d549afe0ecd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Tue, 27 Jul 2021 17:51:13 +0200 Subject: [PATCH 072/118] Handle DTMF support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/webrtc/call.ts | 22 ++++++++++++---------- src/webrtc/callEventTypes.ts | 1 + 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/webrtc/call.ts b/src/webrtc/call.ts index 6a9a8be76..1b846efec 100644 --- a/src/webrtc/call.ts +++ b/src/webrtc/call.ts @@ -394,6 +394,10 @@ export class MatrixCall extends EventEmitter { return Boolean(this.opponentCaps && this.opponentCaps["m.call.transferee"]); } + public opponentSupportsDTMF(): boolean { + return Boolean(this.opponentCaps && this.opponentCaps["m.call.dtmf"]); + } + public getRemoteAssertedIdentity(): AssertedIdentity { return this.remoteAssertedIdentity; } @@ -1032,11 +1036,10 @@ export class MatrixCall extends EventEmitter { [SDPStreamMetadataKey]: this.getLocalSDPStreamMetadata(), } as MCallAnswer; - if (this.client.supportsCallTransfer) { - answerContent.capabilities = { - 'm.call.transferee': true, - }; - } + answerContent.capabilities = { + 'm.call.transferee': true, + 'm.call.dtmf': false, + }; // We have just taken the local description from the peerconnection which will // contain all the local candidates added so far, so we can discard any candidates @@ -1374,11 +1377,10 @@ export class MatrixCall extends EventEmitter { content.description = this.peerConn.localDescription; } - if (this.client.supportsCallTransfer) { - content.capabilities = { - 'm.call.transferee': true, - }; - } + content.capabilities = { + 'm.call.transferee': true, + 'm.call.dtmf': false, + }; content[SDPStreamMetadataKey] = this.getLocalSDPStreamMetadata(); diff --git a/src/webrtc/callEventTypes.ts b/src/webrtc/callEventTypes.ts index 532567843..c243b8055 100644 --- a/src/webrtc/callEventTypes.ts +++ b/src/webrtc/callEventTypes.ts @@ -24,6 +24,7 @@ interface CallOfferAnswer { export interface CallCapabilities { 'm.call.transferee': boolean; + 'm.call.dtmf': boolean; } export interface MCallAnswer { From 35ca7fdc48a4a9b1cc9297e21256b6eb13be9e93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Wed, 28 Jul 2021 08:15:50 +0200 Subject: [PATCH 073/118] Fix call transfer support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/webrtc/call.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/webrtc/call.ts b/src/webrtc/call.ts index 1b846efec..dc8837ce3 100644 --- a/src/webrtc/call.ts +++ b/src/webrtc/call.ts @@ -1037,7 +1037,7 @@ export class MatrixCall extends EventEmitter { } as MCallAnswer; answerContent.capabilities = { - 'm.call.transferee': true, + 'm.call.transferee': this.client.supportsCallTransfer, 'm.call.dtmf': false, }; @@ -1378,7 +1378,7 @@ export class MatrixCall extends EventEmitter { } content.capabilities = { - 'm.call.transferee': true, + 'm.call.transferee': this.client.supportsCallTransfer, 'm.call.dtmf': false, }; From 6fda2a0c57d298e58376bbcef378123fda461a24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Wed, 28 Jul 2021 14:15:00 +0200 Subject: [PATCH 074/118] Handle purpose changes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/webrtc/call.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/webrtc/call.ts b/src/webrtc/call.ts index 3c25e22ac..82eccf5ca 100644 --- a/src/webrtc/call.ts +++ b/src/webrtc/call.ts @@ -1330,7 +1330,6 @@ export class MatrixCall extends EventEmitter { public onSDPStreamMetadataChangedReceived(event: MatrixEvent): void { // TODO: What if the values is missing - // TODO: Handle purpose changes const content = event.getContent(); const metadata = content[SDPStreamMetadataKey]; this.remoteSDPStreamMetadata = metadata; @@ -1338,6 +1337,7 @@ export class MatrixCall extends EventEmitter { const streamId = feed.stream.id; feed.setAudioMuted(metadata[streamId].audio_muted); feed.setVideoMuted(metadata[streamId].video_muted); + feed.purpose = metadata[streamId].purpose; } } From 8de6c5aad182aff1b36de17b24dd5f901ea2686e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Wed, 28 Jul 2021 14:59:57 +0200 Subject: [PATCH 075/118] Fix typo which caused all feeds to be muted by default MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/webrtc/call.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/webrtc/call.ts b/src/webrtc/call.ts index 82eccf5ca..898916658 100644 --- a/src/webrtc/call.ts +++ b/src/webrtc/call.ts @@ -509,7 +509,7 @@ export class MatrixCall extends EventEmitter { if (feed) { feed.setNewStream(stream); } else { - this.feeds.push(new CallFeed(stream, userId, purpose, this.client, this.roomId)); + this.feeds.push(new CallFeed(stream, userId, purpose, this.client, this.roomId, false, false)); this.emit(CallEvent.FeedsChanged, this.feeds); } @@ -524,7 +524,7 @@ export class MatrixCall extends EventEmitter { if (existingFeed) { existingFeed.setNewStream(stream); } else { - this.feeds.push(new CallFeed(stream, userId, purpose, this.client, this.roomId, true, true)); + this.feeds.push(new CallFeed(stream, userId, purpose, this.client, this.roomId, false, false)); this.emit(CallEvent.FeedsChanged, this.feeds); } From 057eb0f2a547ba9769a210457806d6e6a2ecd842 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Wed, 28 Jul 2021 15:00:21 +0200 Subject: [PATCH 076/118] Make mute state props mandetory MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/webrtc/callFeed.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/webrtc/callFeed.ts b/src/webrtc/callFeed.ts index 085291b8c..b82c68535 100644 --- a/src/webrtc/callFeed.ts +++ b/src/webrtc/callFeed.ts @@ -31,8 +31,8 @@ export class CallFeed extends EventEmitter { public purpose: SDPStreamMetadataPurpose, private client: MatrixClient, private roomId: string, - private audioMuted?: boolean, - private videoMuted?: boolean, + private audioMuted: boolean, + private videoMuted: boolean, ) { super(); } From b11a8459d8161a26bead13cc19516a99005d65e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Wed, 28 Jul 2021 15:16:24 +0200 Subject: [PATCH 077/118] Add a method for setting remoteSDPStreamMetadata MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/webrtc/call.ts | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/src/webrtc/call.ts b/src/webrtc/call.ts index 898916658..1ef551dbc 100644 --- a/src/webrtc/call.ts +++ b/src/webrtc/call.ts @@ -612,7 +612,7 @@ export class MatrixCall extends EventEmitter { const sdpStreamMetadata = invite[SDPStreamMetadataKey]; if (sdpStreamMetadata) { - this.remoteSDPStreamMetadata = sdpStreamMetadata; + this.updateRemoteSDPStreamMetadata(sdpStreamMetadata); } else { logger.debug("Did not get any SDPStreamMetadata! Can not send/receive multiple streams"); } @@ -1221,7 +1221,7 @@ export class MatrixCall extends EventEmitter { const sdpStreamMetadata = event.getContent()[SDPStreamMetadataKey]; if (sdpStreamMetadata) { - this.remoteSDPStreamMetadata = sdpStreamMetadata; + this.updateRemoteSDPStreamMetadata(sdpStreamMetadata); } else { logger.warn("Did not get any SDPStreamMetadata! Can not send/receive multiple streams"); } @@ -1296,9 +1296,9 @@ export class MatrixCall extends EventEmitter { const prevLocalOnHold = this.isLocalOnHold(); - const metadata = event.getContent()[SDPStreamMetadataKey]; - if (metadata) { - this.remoteSDPStreamMetadata = metadata; + const sdpStreamMetadata = event.getContent()[SDPStreamMetadataKey]; + if (sdpStreamMetadata) { + this.updateRemoteSDPStreamMetadata(sdpStreamMetadata); } else { logger.warn("Received negotiation event without SDPStreamMetadata!"); } @@ -1328,19 +1328,22 @@ export class MatrixCall extends EventEmitter { } } - public onSDPStreamMetadataChangedReceived(event: MatrixEvent): void { - // TODO: What if the values is missing - const content = event.getContent(); - const metadata = content[SDPStreamMetadataKey]; + private updateRemoteSDPStreamMetadata(metadata: SDPStreamMetadata): void { this.remoteSDPStreamMetadata = metadata; for (const feed of this.getRemoteFeeds()) { const streamId = feed.stream.id; - feed.setAudioMuted(metadata[streamId].audio_muted); - feed.setVideoMuted(metadata[streamId].video_muted); - feed.purpose = metadata[streamId].purpose; + feed.setAudioMuted(metadata[streamId]?.audio_muted ?? feed.isAudioMuted()); + feed.setVideoMuted(metadata[streamId]?.video_muted ?? feed.isVideoMuted()); + feed.purpose = metadata[streamId]?.purpose ?? feed.purpose; } } + public onSDPStreamMetadataChangedReceived(event: MatrixEvent): void { + const content = event.getContent(); + const metadata = content[SDPStreamMetadataKey]; + this.updateRemoteSDPStreamMetadata(metadata); + } + async onAssertedIdentityReceived(event: MatrixEvent) { if (!event.getContent().asserted_identity) return; From 23ab3e3ec0a8c6bedba9be80aac5a2b8e9814601 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Wed, 28 Jul 2021 15:29:28 +0200 Subject: [PATCH 078/118] Test mute metadata gets mapped onto feeds MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- spec/unit/webrtc/call.spec.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/spec/unit/webrtc/call.spec.ts b/spec/unit/webrtc/call.spec.ts index 69a3553b0..400c5ecb3 100644 --- a/spec/unit/webrtc/call.spec.ts +++ b/spec/unit/webrtc/call.spec.ts @@ -321,16 +321,19 @@ describe('Call', function() { [SDPStreamMetadataKey]: { "stream_id": { purpose: SDPStreamMetadataPurpose.Usermedia, + audio_muted: true, + video_muted: false, }, }, }; }, }); - call.pushRemoteFeed({ id: "stream_id" }); - expect(call.getFeeds().find((feed) => { - return feed.stream.id === "stream_id"; - })?.purpose).toBe(SDPStreamMetadataPurpose.Usermedia); + call.pushRemoteFeed({ id: "stream_id", getAudioTracks: () => ["track1"], getVideoTracks: () => ["track1"] }); + const feed = call.getFeeds().find((feed) => feed.stream.id === "stream_id"); + expect(feed?.purpose).toBe(SDPStreamMetadataPurpose.Usermedia); + expect(feed?.isAudioMuted()).toBeTruthy(); + expect(feed?.isVideoMuted()).not.toBeTruthy(); }); it("should fallback to replaceTrack() if the other side doesn't support SPDStreamMetadata", async () => { From ca7a4570947c2369963d88dddbe43151fd4c386f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Wed, 28 Jul 2021 17:56:51 +0200 Subject: [PATCH 079/118] Add recursivelyAssign() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/utils.ts | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/utils.ts b/src/utils.ts index 5abed06af..9d6460878 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -693,3 +693,25 @@ const collator = new Intl.Collator(); export function compare(a: string, b: string): number { return collator.compare(a, b); } + +/** + * This function is similar to Object.assign() but it assigns recursively and + * allows you to ignore nullish values from the source + * + * @param {Object} target + * @param {Object} source + * @returns the target object + */ +export function recursivelyAssign(target: T, source: S, ignoreNullish = false): T { + for (const [sourceKey, sourceValue] of Object.entries(source)) { + if (target[sourceKey] instanceof Object && sourceValue) { + recursivelyAssign(target[sourceKey], sourceValue); + continue; + } + if ((sourceValue !== null && sourceValue !== undefined) || !ignoreNullish) { + target[sourceKey] = sourceValue; + continue; + } + } + return target; +} From 2d25150a213ce63ba60f3be43f687f64d64005c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Wed, 28 Jul 2021 17:57:16 +0200 Subject: [PATCH 080/118] Write tests for recursivelyAssign() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- spec/unit/utils.spec.ts | 64 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/spec/unit/utils.spec.ts b/spec/unit/utils.spec.ts index 9611511b4..12a83b235 100644 --- a/spec/unit/utils.spec.ts +++ b/spec/unit/utils.spec.ts @@ -493,4 +493,68 @@ describe("utils", function() { expect(deepSortedObjectEntries(input)).toMatchObject(output); }); }); + + describe("recursivelyAssign", () => { + it("doesn't override with null/undefined", () => { + const result = utils.recursivelyAssign( + { + string: "Hello world", + object: {}, + float: 0.1, + }, { + string: null, + object: undefined, + }, + true, + ); + + expect(result).toStrictEqual({ + string: "Hello world", + object: {}, + float: 0.1, + }); + }); + + it("assigns recursively", () => { + const result = utils.recursivelyAssign( + { + number: 42, + object: { + message: "Hello world", + day: "Monday", + langs: { + compiled: ["c++"], + }, + }, + thing: "string", + }, { + number: 2, + object: { + message: "How are you", + day: "Friday", + langs: { + compiled: ["c++", "c"], + }, + }, + thing: { + aSubThing: "something", + }, + }, + ); + + expect(result).toStrictEqual({ + number: 2, + object: { + message: "How are you", + day: "Friday", + langs: { + compiled: ["c++", "c"], + }, + }, + thing: { + aSubThing: "something", + }, + }); + }); + }); }); From 606aa29381b7d1e692bbd08f7c74619798859327 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Wed, 28 Jul 2021 18:37:48 +0200 Subject: [PATCH 081/118] Use recursivelyAssign MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/webrtc/call.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/webrtc/call.ts b/src/webrtc/call.ts index 1ef551dbc..b6687fb6b 100644 --- a/src/webrtc/call.ts +++ b/src/webrtc/call.ts @@ -1329,7 +1329,7 @@ export class MatrixCall extends EventEmitter { } private updateRemoteSDPStreamMetadata(metadata: SDPStreamMetadata): void { - this.remoteSDPStreamMetadata = metadata; + metadata = utils.recursivelyAssign(this.remoteSDPStreamMetadata, metadata, true); for (const feed of this.getRemoteFeeds()) { const streamId = feed.stream.id; feed.setAudioMuted(metadata[streamId]?.audio_muted ?? feed.isAudioMuted()); From b3c66848e25c0713db813158d46d79521e532d4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Wed, 28 Jul 2021 19:06:24 +0200 Subject: [PATCH 082/118] Handle missing params MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/utils.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/utils.ts b/src/utils.ts index 9d6460878..74dbb4c9f 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -703,6 +703,9 @@ export function compare(a: string, b: string): number { * @returns the target object */ export function recursivelyAssign(target: T, source: S, ignoreNullish = false): T { + if (!target) target = Object({}); + if (!source) source = Object({}); + for (const [sourceKey, sourceValue] of Object.entries(source)) { if (target[sourceKey] instanceof Object && sourceValue) { recursivelyAssign(target[sourceKey], sourceValue); From 87fd3521ea2449b8c6034d04e8862804618815f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Wed, 28 Jul 2021 20:11:51 +0200 Subject: [PATCH 083/118] Fix types MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/utils.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/utils.ts b/src/utils.ts index 74dbb4c9f..b5701df67 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -702,10 +702,7 @@ export function compare(a: string, b: string): number { * @param {Object} source * @returns the target object */ -export function recursivelyAssign(target: T, source: S, ignoreNullish = false): T { - if (!target) target = Object({}); - if (!source) source = Object({}); - +export function recursivelyAssign(target: Object, source: Object, ignoreNullish = false): any { for (const [sourceKey, sourceValue] of Object.entries(source)) { if (target[sourceKey] instanceof Object && sourceValue) { recursivelyAssign(target[sourceKey], sourceValue); From ca042b36479435a9e9c699ffda5211ba709e7a79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Wed, 28 Jul 2021 20:12:21 +0200 Subject: [PATCH 084/118] Fix handling when remoteSDPStreamMetadata is null MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/webrtc/call.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/webrtc/call.ts b/src/webrtc/call.ts index b6687fb6b..a74246c16 100644 --- a/src/webrtc/call.ts +++ b/src/webrtc/call.ts @@ -1329,12 +1329,12 @@ export class MatrixCall extends EventEmitter { } private updateRemoteSDPStreamMetadata(metadata: SDPStreamMetadata): void { - metadata = utils.recursivelyAssign(this.remoteSDPStreamMetadata, metadata, true); + this.remoteSDPStreamMetadata = utils.recursivelyAssign(this.remoteSDPStreamMetadata || {}, metadata, true); for (const feed of this.getRemoteFeeds()) { const streamId = feed.stream.id; - feed.setAudioMuted(metadata[streamId]?.audio_muted ?? feed.isAudioMuted()); - feed.setVideoMuted(metadata[streamId]?.video_muted ?? feed.isVideoMuted()); - feed.purpose = metadata[streamId]?.purpose ?? feed.purpose; + feed.setAudioMuted(this.remoteSDPStreamMetadata[streamId]?.audio_muted); + feed.setVideoMuted(this.remoteSDPStreamMetadata[streamId]?.video_muted); + feed.purpose = this.remoteSDPStreamMetadata[streamId]?.purpose; } } From f2da3ba6aefce6655ae136fdaf0d21428ae2e56a Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 29 Jul 2021 11:00:30 +0100 Subject: [PATCH 085/118] Apply hidden char check to rawDisplayName too --- src/models/room-member.ts | 6 +++++- src/utils.ts | 3 ++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/models/room-member.ts b/src/models/room-member.ts index e7a98257b..e3d6b66ce 100644 --- a/src/models/room-member.ts +++ b/src/models/room-member.ts @@ -131,7 +131,11 @@ export class RoomMember extends EventEmitter { this.disambiguate, ); - this.rawDisplayName = event.getDirectionalContent().displayname || this.userId; + this.rawDisplayName = event.getDirectionalContent().displayname; + if (!this.rawDisplayName || !utils.removeHiddenChars(this.rawDisplayName)) { + this.rawDisplayName = this.userId; + } + if (oldMembership !== this.membership) { this.updateModifiedTime(); this.emit("RoomMember.membership", event, this, oldMembership); diff --git a/src/utils.ts b/src/utils.ts index 5abed06af..3b0b9e7b7 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -402,10 +402,11 @@ export function normalize(str: string): string { // various width spaces U+2000 - U+200D // LTR and RTL marks U+200E and U+200F // LTR/RTL and other directional formatting marks U+202A - U+202F +// Arabic Letter RTL mark U+061C // Combining characters U+0300 - U+036F // Zero width no-break space (BOM) U+FEFF // eslint-disable-next-line no-misleading-character-class -const removeHiddenCharsRegex = /[\u2000-\u200F\u202A-\u202F\u0300-\u036f\uFEFF\s]/g; +const removeHiddenCharsRegex = /[\u2000-\u200F\u202A-\u202F\u0300-\u036F\uFEFF\u061C\s]/g; export function escapeRegExp(string: string): string { return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); From e6696f78f4b3a521a1f643efc841f3a59ec82865 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Thu, 29 Jul 2021 13:00:48 +0200 Subject: [PATCH 086/118] Allow recieving unprefixed version of m.call.sdp_stream_metadata_changed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/@types/event.ts | 3 ++- src/webrtc/call.ts | 2 +- src/webrtc/callEventHandler.ts | 5 ++++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/@types/event.ts b/src/@types/event.ts index f1f5094f8..56ac83d8a 100644 --- a/src/@types/event.ts +++ b/src/@types/event.ts @@ -53,7 +53,8 @@ export enum EventType { CallReject = "m.call.reject", CallSelectAnswer = "m.call.select_answer", CallNegotiate = "m.call.negotiate", - CallSDPStreamMetadataChanged = "org.matrix.call.sdp_stream_metadata_changed", + CallSDPStreamMetadataChanged = "m.call.sdp_stream_metadata_changed", + CallSDPStreamMetadataChangedPrefix = "org.matrix.call.sdp_stream_metadata_changed", CallReplaces = "m.call.replaces", CallAssertedIdentity = "m.call.asserted_identity", CallAssertedIdentityPrefix = "org.matrix.call.asserted_identity", diff --git a/src/webrtc/call.ts b/src/webrtc/call.ts index c42ae2d1c..a18ccfae7 100644 --- a/src/webrtc/call.ts +++ b/src/webrtc/call.ts @@ -1001,7 +1001,7 @@ export class MatrixCall extends EventEmitter { } private updateMuteStatus() { - this.sendVoipEvent(EventType.CallSDPStreamMetadataChanged, { + this.sendVoipEvent(EventType.CallSDPStreamMetadataChangedPrefix, { [SDPStreamMetadataKey]: this.getLocalSDPStreamMetadata(), }); diff --git a/src/webrtc/callEventHandler.ts b/src/webrtc/callEventHandler.ts index fa498e633..96b233e0f 100644 --- a/src/webrtc/callEventHandler.ts +++ b/src/webrtc/callEventHandler.ts @@ -297,7 +297,10 @@ export class CallEventHandler { } call.onAssertedIdentityReceived(event); - } else if (event.getType() === EventType.CallSDPStreamMetadataChanged) { + } else if ( + event.getType() === EventType.CallSDPStreamMetadataChanged || + event.getType() === EventType.CallSDPStreamMetadataChangedPrefix + ) { if (!call) return; if (event.getContent().party_id === call.ourPartyId) { From 600438d02eb08a0fbefecaf93e9e7b1fb3308af1 Mon Sep 17 00:00:00 2001 From: Hubert Chathi Date: Thu, 29 Jul 2021 11:58:08 -0400 Subject: [PATCH 087/118] only clear bit 63 when we create the IV If we got the IV from somewhere else, we should just use it as-is, rather than trying to re-clear the bit. --- src/crypto/aes.ts | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/crypto/aes.ts b/src/crypto/aes.ts index 736755b89..ba86ffe1b 100644 --- a/src/crypto/aes.ts +++ b/src/crypto/aes.ts @@ -50,12 +50,12 @@ async function encryptNode(data: string, key: Uint8Array, name: string, ivStr?: iv = decodeBase64(ivStr); } else { iv = crypto.randomBytes(16); - } - // clear bit 63 of the IV to stop us hitting the 64-bit counter boundary - // (which would mean we wouldn't be able to decrypt on Android). The loss - // of a single bit of iv is a price we have to pay. - iv[8] &= 0x7f; + // clear bit 63 of the IV to stop us hitting the 64-bit counter boundary + // (which would mean we wouldn't be able to decrypt on Android). The loss + // of a single bit of iv is a price we have to pay. + iv[8] &= 0x7f; + } const [aesKey, hmacKey] = deriveKeysNode(key, name); @@ -137,12 +137,12 @@ async function encryptBrowser(data: string, key: Uint8Array, name: string, ivStr } else { iv = new Uint8Array(16); window.crypto.getRandomValues(iv); - } - // clear bit 63 of the IV to stop us hitting the 64-bit counter boundary - // (which would mean we wouldn't be able to decrypt on Android). The loss - // of a single bit of iv is a price we have to pay. - iv[8] &= 0x7f; + // clear bit 63 of the IV to stop us hitting the 64-bit counter boundary + // (which would mean we wouldn't be able to decrypt on Android). The loss + // of a single bit of iv is a price we have to pay. + iv[8] &= 0x7f; + } const [aesKey, hmacKey] = await deriveKeysBrowser(key, name); const encodedData = new TextEncoder().encode(data); From b15ba8c5e3176b8b13008ea3fe55522dd74514f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 30 Jul 2021 08:53:18 +0200 Subject: [PATCH 088/118] Send hangup reason if the opponent supports it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/webrtc/call.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/webrtc/call.ts b/src/webrtc/call.ts index 6a9a8be76..8facc8826 100644 --- a/src/webrtc/call.ts +++ b/src/webrtc/call.ts @@ -734,9 +734,10 @@ export class MatrixCall extends EventEmitter { // We don't want to send hangup here if we didn't even get to sending an invite if (this.state === CallState.WaitLocalMedia) return; const content = {}; - // Continue to send no reason for user hangups temporarily, until - // clients understand the user_hangup reason (voip v1) - if (reason !== CallErrorCode.UserHangup) content['reason'] = reason; + // Don't send UserHangup reason to older clients + if ((this.opponentVersion && this.opponentVersion >= 1) || reason !== CallErrorCode.UserHangup) { + content["reason"] = reason; + } this.sendVoipEvent(EventType.CallHangup, content); } From cace8f232f959a91e9e5f0a06a302a264bf2fefd Mon Sep 17 00:00:00 2001 From: David Baker Date: Mon, 2 Aug 2021 12:45:25 +0100 Subject: [PATCH 089/118] Changelog for v12.2.0 --- CHANGELOG.md | 52 +++++++++------------------------------------------- 1 file changed, 9 insertions(+), 43 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e770173aa..a4f3eb273 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,53 +1,19 @@ -Changes in [12.2.0-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v12.2.0-rc.1) (2021-07-27) -============================================================================================================ -[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v12.1.0...v12.2.0-rc.1) +Changes in [12.2.0](https://github.com/vector-im/element-desktop/releases/tag/v12.2.0) (2021-07-02) +=================================================================================================== - * Update ESLint config - [\#1810](https://github.com/matrix-org/matrix-js-sdk/pull/1810) - * Clean up Event.clearEvent handling to fix a bug where malformed events with - falsey content wouldn't be considered decrypted - [\#1807](https://github.com/matrix-org/matrix-js-sdk/pull/1807) - * Update matrix-org-eslint-plugin and tighten max warning limit - [\#1806](https://github.com/matrix-org/matrix-js-sdk/pull/1806) - * Initial MSC3244 support and type cleanup - [\#1745](https://github.com/matrix-org/matrix-js-sdk/pull/1745) - * Functional members - [\#1771](https://github.com/matrix-org/matrix-js-sdk/pull/1771) - * yarn upgrade - [\#1802](https://github.com/matrix-org/matrix-js-sdk/pull/1802) +## ✨ Features * Improve calculateRoomName performances by using Intl.Collator [\#1801](https://github.com/matrix-org/matrix-js-sdk/pull/1801) - * Conform to new typescript eslint rules - [\#1798](https://github.com/matrix-org/matrix-js-sdk/pull/1798) - * Actually print IndexedDB command error if there is one - [\#1799](https://github.com/matrix-org/matrix-js-sdk/pull/1799) - * Update eslint-plugin-matrix-org - [\#1796](https://github.com/matrix-org/matrix-js-sdk/pull/1796) - * Add minimal types for "notification settings" UI - [\#1778](https://github.com/matrix-org/matrix-js-sdk/pull/1778) - * Switch from `url` to URL constructor - [\#1791](https://github.com/matrix-org/matrix-js-sdk/pull/1791) * Switch callEventHandler from listening on `event` to `Room.timeline` [\#1789](https://github.com/matrix-org/matrix-js-sdk/pull/1789) - * Contributing guidelines for new changelog generation - [\#1787](https://github.com/matrix-org/matrix-js-sdk/pull/1787) - * Fix link to CONTRIBUTING.md - [\#1786](https://github.com/matrix-org/matrix-js-sdk/pull/1786) - * Use an unstable prefix for MSC2885: Hidden read receipts - [\#1788](https://github.com/matrix-org/matrix-js-sdk/pull/1788) - * Standardise spelling and casing of homeserver, identity server, and - integration manager - [\#1782](https://github.com/matrix-org/matrix-js-sdk/pull/1782) - * Convert CONTRIBUTING to markdown - [\#1785](https://github.com/matrix-org/matrix-js-sdk/pull/1785) - * Use webpack worker-loader instead of homegrown hack - [\#1780](https://github.com/matrix-org/matrix-js-sdk/pull/1780) - * Convert IndexedDB store & worker to Typescript - [\#1779](https://github.com/matrix-org/matrix-js-sdk/pull/1779) * Expose MatrixEvent's internal clearEvent as a function [\#1784](https://github.com/matrix-org/matrix-js-sdk/pull/1784) - * Improve and consolidate typing - [\#1777](https://github.com/matrix-org/matrix-js-sdk/pull/1777) + +## 🐛 Bug Fixes + * Clean up Event.clearEvent handling to fix a bug where malformed events with falsey content wouldn't be considered decrypted + [\#1807](https://github.com/matrix-org/matrix-js-sdk/pull/1807) + * Standardise spelling and casing of homeserver, identity server, and integration manager + [\#1782](https://github.com/matrix-org/matrix-js-sdk/pull/1782) Changes in [12.1.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v12.1.0) (2021-07-19) ================================================================================================== From 4891446dc3d82f5d67b752565b4e1393f2592ea1 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Mon, 2 Aug 2021 13:00:06 +0100 Subject: [PATCH 090/118] v12.2.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8ed7e6eaf..98f1ca10d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-js-sdk", - "version": "12.2.0-rc.1", + "version": "12.2.0", "description": "Matrix Client-Server SDK for Javascript", "scripts": { "prepublishOnly": "yarn build", From 8a1bc98d4eb5cecd9373ed067c9fbfb6718d2e4f Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Mon, 2 Aug 2021 13:03:59 +0100 Subject: [PATCH 091/118] Resetting package fields for development --- package.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index a32187267..470271444 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "keywords": [ "matrix-org" ], - "main": "./lib/index.js", + "main": "./src/index.ts", "browser": "./lib/browser-index.js", "matrix_src_main": "./src/index.ts", "matrix_src_browser": "./src/browser-index.js", @@ -111,6 +111,5 @@ "coverageReporters": [ "text" ] - }, - "typings": "./lib/index.d.ts" + } } From 1e281f6838930aa79534d263f372dabcafb4e71f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Mon, 2 Aug 2021 21:02:09 +0200 Subject: [PATCH 092/118] localAVStream -> localUsermediaStream MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/webrtc/call.ts | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/webrtc/call.ts b/src/webrtc/call.ts index dc8837ce3..65a1d0b05 100644 --- a/src/webrtc/call.ts +++ b/src/webrtc/call.ts @@ -279,7 +279,7 @@ export class MatrixCall extends EventEmitter { private feeds: Array; private screenSharingStream: MediaStream; // TODO: Rename to usermedia rather than AV for consistency - private localAVStream: MediaStream; + private localUsermediaStream: MediaStream; private usermediaSenders: Array; private screensharingSenders: Array; private inviteOrAnswerSent: boolean; @@ -676,7 +676,7 @@ export class MatrixCall extends EventEmitter { logger.debug(`Answering call ${this.callId} of type ${this.type}`); - if (!this.localAVStream && !this.waitForLocalAVStream) { + if (!this.localUsermediaStream && !this.waitForLocalAVStream) { const constraints = getUserMediaContraints( this.type == CallType.Video ? ConstraintsType.Video: @@ -694,8 +694,8 @@ export class MatrixCall extends EventEmitter { this.getUserMediaFailed(e); return; } - } else if (this.localAVStream) { - this.gotUserMediaForAnswer(this.localAVStream); + } else if (this.localUsermediaStream) { + this.gotUserMediaForAnswer(this.localUsermediaStream); } else if (this.waitForLocalAVStream) { this.setState(CallState.WaitLocalMedia); } @@ -713,12 +713,12 @@ export class MatrixCall extends EventEmitter { newCall.waitForLocalAVStream = true; } else if (this.state === CallState.CreateOffer) { logger.debug("Handing local stream to new call"); - newCall.gotUserMediaForAnswer(this.localAVStream); - delete(this.localAVStream); + newCall.gotUserMediaForAnswer(this.localUsermediaStream); + delete(this.localUsermediaStream); } else if (this.state === CallState.InviteSent) { logger.debug("Handing local stream to new call"); - newCall.gotUserMediaForAnswer(this.localAVStream); - delete(this.localAVStream); + newCall.gotUserMediaForAnswer(this.localUsermediaStream); + delete(this.localUsermediaStream); } this.successor = newCall; this.emit(CallEvent.Replaced, newCall); @@ -868,7 +868,7 @@ export class MatrixCall extends EventEmitter { return false; } } else { - const track = this.localAVStream.getTracks().find((track) => { + const track = this.localUsermediaStream.getTracks().find((track) => { return track.kind === "video"; }); const sender = this.usermediaSenders.find((sender) => { @@ -991,15 +991,15 @@ export class MatrixCall extends EventEmitter { } private updateMuteStatus() { - if (!this.localAVStream) { + if (!this.localUsermediaStream) { return; } const micShouldBeMuted = this.micMuted || this.remoteOnHold; - setTracksEnabled(this.localAVStream.getAudioTracks(), !micShouldBeMuted); + setTracksEnabled(this.localUsermediaStream.getAudioTracks(), !micShouldBeMuted); const vidShouldBeMuted = this.vidMuted || this.remoteOnHold; - setTracksEnabled(this.localAVStream.getVideoTracks(), !vidShouldBeMuted); + setTracksEnabled(this.localUsermediaStream.getVideoTracks(), !vidShouldBeMuted); } /** @@ -1016,11 +1016,11 @@ export class MatrixCall extends EventEmitter { return; } - this.localAVStream = stream; + this.localUsermediaStream = stream; this.pushLocalFeed(stream, SDPStreamMetadataPurpose.Usermedia); this.setState(CallState.CreateOffer); - logger.info("Got local AV stream with id " + this.localAVStream.id); + logger.info("Got local AV stream with id " + this.localUsermediaStream.id); logger.debug("gotUserMediaForInvite -> " + this.type); // Now we wait for the negotiationneeded event }; @@ -1079,8 +1079,8 @@ export class MatrixCall extends EventEmitter { this.pushLocalFeed(stream, SDPStreamMetadataPurpose.Usermedia); - this.localAVStream = stream; - logger.info("Got local AV stream with id " + this.localAVStream.id); + this.localUsermediaStream = stream; + logger.info("Got local AV stream with id " + this.localUsermediaStream.id); this.setState(CallState.CreateAnswer); @@ -1731,7 +1731,7 @@ export class MatrixCall extends EventEmitter { } private stopAllMedia() { - logger.debug(`stopAllMedia (stream=${this.localAVStream})`); + logger.debug(`stopAllMedia (stream=${this.localUsermediaStream})`); for (const feed of this.feeds) { for (const track of feed.stream.getTracks()) { From aae8f1c70693b03df861dfab9712e530831a6cdc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Mon, 2 Aug 2021 21:14:59 +0200 Subject: [PATCH 093/118] screenSharingStream -> localScreenSharingStream MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/webrtc/call.ts | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/webrtc/call.ts b/src/webrtc/call.ts index 65a1d0b05..f98334fe9 100644 --- a/src/webrtc/call.ts +++ b/src/webrtc/call.ts @@ -277,7 +277,7 @@ export class MatrixCall extends EventEmitter { private sentEndOfCandidates: boolean; private peerConn: RTCPeerConnection; private feeds: Array; - private screenSharingStream: MediaStream; + private localScreenSharingStream: MediaStream; // TODO: Rename to usermedia rather than AV for consistency private localUsermediaStream: MediaStream; private usermediaSenders: Array; @@ -780,7 +780,7 @@ export class MatrixCall extends EventEmitter { * @returns {boolean} is screensharing */ public isScreensharing(): boolean { - return Boolean(this.screenSharingStream); + return Boolean(this.localScreenSharingStream); } /** @@ -810,9 +810,9 @@ export class MatrixCall extends EventEmitter { logger.debug(`Set screensharing enabled? ${enabled}`); if (enabled) { try { - this.screenSharingStream = await getScreensharingStream(selectDesktopCapturerSource); - if (!this.screenSharingStream) return false; - this.pushLocalFeed(this.screenSharingStream, SDPStreamMetadataPurpose.Screenshare); + this.localScreenSharingStream = await getScreensharingStream(selectDesktopCapturerSource); + if (!this.localScreenSharingStream) return false; + this.pushLocalFeed(this.localScreenSharingStream, SDPStreamMetadataPurpose.Screenshare); return true; } catch (err) { this.emit(CallEvent.Error, @@ -824,11 +824,11 @@ export class MatrixCall extends EventEmitter { for (const sender of this.screensharingSenders) { this.peerConn.removeTrack(sender); } - this.deleteFeedByStream(this.screenSharingStream); - for (const track of this.screenSharingStream.getTracks()) { + this.deleteFeedByStream(this.localScreenSharingStream); + for (const track of this.localScreenSharingStream.getTracks()) { track.stop(); } - this.screenSharingStream = null; + this.localScreenSharingStream = null; return false; } } @@ -847,10 +847,10 @@ export class MatrixCall extends EventEmitter { logger.debug(`Set screensharing enabled? ${enabled} using replaceTrack()`); if (enabled) { try { - this.screenSharingStream = await getScreensharingStream(selectDesktopCapturerSource); - if (!this.screenSharingStream) return false; + this.localScreenSharingStream = await getScreensharingStream(selectDesktopCapturerSource); + if (!this.localScreenSharingStream) return false; - const track = this.screenSharingStream.getTracks().find((track) => { + const track = this.localScreenSharingStream.getTracks().find((track) => { return track.kind === "video"; }); const sender = this.usermediaSenders.find((sender) => { @@ -858,7 +858,7 @@ export class MatrixCall extends EventEmitter { }); sender.replaceTrack(track); - this.pushLocalFeed(this.screenSharingStream, SDPStreamMetadataPurpose.Screenshare, false); + this.pushLocalFeed(this.localScreenSharingStream, SDPStreamMetadataPurpose.Screenshare, false); return true; } catch (err) { @@ -876,11 +876,11 @@ export class MatrixCall extends EventEmitter { }); sender.replaceTrack(track); - this.deleteFeedByStream(this.screenSharingStream); - for (const track of this.screenSharingStream.getTracks()) { + this.deleteFeedByStream(this.localScreenSharingStream); + for (const track of this.localScreenSharingStream.getTracks()) { track.stop(); } - this.screenSharingStream = null; + this.localScreenSharingStream = null; return false; } From eac77a66951b4240bb9b207a414ba2c75e05522c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Mon, 2 Aug 2021 21:15:36 +0200 Subject: [PATCH 094/118] Remove config as it wasn't used MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/webrtc/call.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/webrtc/call.ts b/src/webrtc/call.ts index f98334fe9..46ae9943f 100644 --- a/src/webrtc/call.ts +++ b/src/webrtc/call.ts @@ -284,9 +284,6 @@ export class MatrixCall extends EventEmitter { private screensharingSenders: Array; private inviteOrAnswerSent: boolean; private waitForLocalAVStream: boolean; - // XXX: I don't know why this is called 'config'. - // XXX: Do we even needs this? Seems to be unused - private config: MediaStreamConstraints; private successor: MatrixCall; private opponentMember: RoomMember; private opponentVersion: number; @@ -1800,7 +1797,6 @@ export class MatrixCall extends EventEmitter { this.client.callEventHandler.calls.set(this.callId, this); this.setState(CallState.WaitLocalMedia); this.direction = CallDirection.Outbound; - this.config = constraints; // make sure we have valid turn creds. Unless something's gone wrong, it should // poll and keep the credentials valid so this should be instant. From bc07ad5909f9749c37018d31f6ec73641f39256f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Mon, 2 Aug 2021 21:25:55 +0200 Subject: [PATCH 095/118] Make localScreenSharingStream into a getter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/webrtc/call.ts | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/src/webrtc/call.ts b/src/webrtc/call.ts index 257f30c9b..c7a3fed2b 100644 --- a/src/webrtc/call.ts +++ b/src/webrtc/call.ts @@ -278,8 +278,6 @@ export class MatrixCall extends EventEmitter { private sentEndOfCandidates: boolean; private peerConn: RTCPeerConnection; private feeds: Array; - private localScreenSharingStream: MediaStream; - // TODO: Rename to usermedia rather than AV for consistency private localUsermediaStream: MediaStream; private usermediaSenders: Array; private screensharingSenders: Array; @@ -402,6 +400,14 @@ export class MatrixCall extends EventEmitter { return this.getLocalFeeds().find((feed) => feed.purpose === SDPStreamMetadataPurpose.Usermedia); } + public get localScreenSharingFeed(): CallFeed { + return this.getLocalFeeds().find((feed) => feed.purpose === SDPStreamMetadataPurpose.Screenshare); + } + + private get localScreenSharingStream(): MediaStream { + return this.localScreenSharingFeed?.stream; + } + private getFeedByStreamId(streamId: string): CallFeed { return this.getFeeds().find((feed) => feed.stream.id === streamId); } @@ -818,9 +824,9 @@ export class MatrixCall extends EventEmitter { logger.debug(`Set screensharing enabled? ${enabled}`); if (enabled) { try { - this.localScreenSharingStream = await getScreensharingStream(selectDesktopCapturerSource); - if (!this.localScreenSharingStream) return false; - this.pushLocalFeed(this.localScreenSharingStream, SDPStreamMetadataPurpose.Screenshare); + const stream = await getScreensharingStream(selectDesktopCapturerSource); + if (!stream) return false; + this.pushLocalFeed(stream, SDPStreamMetadataPurpose.Screenshare); return true; } catch (err) { this.emit(CallEvent.Error, @@ -836,7 +842,6 @@ export class MatrixCall extends EventEmitter { for (const track of this.localScreenSharingStream.getTracks()) { track.stop(); } - this.localScreenSharingStream = null; return false; } } @@ -855,10 +860,10 @@ export class MatrixCall extends EventEmitter { logger.debug(`Set screensharing enabled? ${enabled} using replaceTrack()`); if (enabled) { try { - this.localScreenSharingStream = await getScreensharingStream(selectDesktopCapturerSource); - if (!this.localScreenSharingStream) return false; + const stream = await getScreensharingStream(selectDesktopCapturerSource); + if (!stream) return false; - const track = this.localScreenSharingStream.getTracks().find((track) => { + const track = stream.getTracks().find((track) => { return track.kind === "video"; }); const sender = this.usermediaSenders.find((sender) => { @@ -866,7 +871,7 @@ export class MatrixCall extends EventEmitter { }); sender.replaceTrack(track); - this.pushLocalFeed(this.localScreenSharingStream, SDPStreamMetadataPurpose.Screenshare, false); + this.pushLocalFeed(stream, SDPStreamMetadataPurpose.Screenshare, false); return true; } catch (err) { @@ -888,7 +893,6 @@ export class MatrixCall extends EventEmitter { for (const track of this.localScreenSharingStream.getTracks()) { track.stop(); } - this.localScreenSharingStream = null; return false; } From 4631cd1fe350d4620b5c126fcfab0e03307699fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Mon, 2 Aug 2021 21:28:49 +0200 Subject: [PATCH 096/118] Make localUsermediaStream into a getter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/webrtc/call.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/webrtc/call.ts b/src/webrtc/call.ts index c7a3fed2b..bba06255a 100644 --- a/src/webrtc/call.ts +++ b/src/webrtc/call.ts @@ -278,7 +278,6 @@ export class MatrixCall extends EventEmitter { private sentEndOfCandidates: boolean; private peerConn: RTCPeerConnection; private feeds: Array; - private localUsermediaStream: MediaStream; private usermediaSenders: Array; private screensharingSenders: Array; private inviteOrAnswerSent: boolean; @@ -404,6 +403,10 @@ export class MatrixCall extends EventEmitter { return this.getLocalFeeds().find((feed) => feed.purpose === SDPStreamMetadataPurpose.Screenshare); } + public get localUsermediaStream(): MediaStream { + return this.localUsermediaFeed?.stream; + } + private get localScreenSharingStream(): MediaStream { return this.localScreenSharingFeed?.stream; } @@ -728,11 +731,9 @@ export class MatrixCall extends EventEmitter { } else if (this.state === CallState.CreateOffer) { logger.debug("Handing local stream to new call"); newCall.gotUserMediaForAnswer(this.localUsermediaStream); - delete(this.localUsermediaStream); } else if (this.state === CallState.InviteSent) { logger.debug("Handing local stream to new call"); newCall.gotUserMediaForAnswer(this.localUsermediaStream); - delete(this.localUsermediaStream); } this.successor = newCall; this.emit(CallEvent.Replaced, newCall); @@ -1027,7 +1028,6 @@ export class MatrixCall extends EventEmitter { return; } - this.localUsermediaStream = stream; this.pushLocalFeed(stream, SDPStreamMetadataPurpose.Usermedia); this.setState(CallState.CreateOffer); @@ -1090,7 +1090,6 @@ export class MatrixCall extends EventEmitter { this.pushLocalFeed(stream, SDPStreamMetadataPurpose.Usermedia); - this.localUsermediaStream = stream; logger.info("Got local AV stream with id " + this.localUsermediaStream.id); this.setState(CallState.CreateAnswer); From dbb0f66094a90d86949f1b29cab11e74451b0b7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Mon, 2 Aug 2021 21:29:09 +0200 Subject: [PATCH 097/118] Remove unused mute state stuff MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/webrtc/call.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/webrtc/call.ts b/src/webrtc/call.ts index bba06255a..8115185d5 100644 --- a/src/webrtc/call.ts +++ b/src/webrtc/call.ts @@ -295,9 +295,6 @@ export class MatrixCall extends EventEmitter { // This flag represents whether we want the other party to be on hold private remoteOnHold; - private micMuted; - private vidMuted; - // the stats for the call at the point it ended. We can't get these after we // tear the call down, so we just grab a snapshot before we stop the call. // The typescript definitions have this type as 'any' :( From 03134832e91cbdcc30527423e2f280f3a0f8ca06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Mon, 2 Aug 2021 21:30:33 +0200 Subject: [PATCH 098/118] Standardise screensharing casing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/webrtc/call.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/webrtc/call.ts b/src/webrtc/call.ts index 8115185d5..9c0947232 100644 --- a/src/webrtc/call.ts +++ b/src/webrtc/call.ts @@ -396,7 +396,7 @@ export class MatrixCall extends EventEmitter { return this.getLocalFeeds().find((feed) => feed.purpose === SDPStreamMetadataPurpose.Usermedia); } - public get localScreenSharingFeed(): CallFeed { + public get localScreensharingFeed(): CallFeed { return this.getLocalFeeds().find((feed) => feed.purpose === SDPStreamMetadataPurpose.Screenshare); } @@ -404,8 +404,8 @@ export class MatrixCall extends EventEmitter { return this.localUsermediaFeed?.stream; } - private get localScreenSharingStream(): MediaStream { - return this.localScreenSharingFeed?.stream; + private get localScreensharingStream(): MediaStream { + return this.localScreensharingFeed?.stream; } private getFeedByStreamId(streamId: string): CallFeed { @@ -792,7 +792,7 @@ export class MatrixCall extends EventEmitter { * @returns {boolean} is screensharing */ public isScreensharing(): boolean { - return Boolean(this.localScreenSharingStream); + return Boolean(this.localScreensharingStream); } /** @@ -836,8 +836,8 @@ export class MatrixCall extends EventEmitter { for (const sender of this.screensharingSenders) { this.peerConn.removeTrack(sender); } - this.deleteFeedByStream(this.localScreenSharingStream); - for (const track of this.localScreenSharingStream.getTracks()) { + this.deleteFeedByStream(this.localScreensharingStream); + for (const track of this.localScreensharingStream.getTracks()) { track.stop(); } return false; @@ -887,8 +887,8 @@ export class MatrixCall extends EventEmitter { }); sender.replaceTrack(track); - this.deleteFeedByStream(this.localScreenSharingStream); - for (const track of this.localScreenSharingStream.getTracks()) { + this.deleteFeedByStream(this.localScreensharingStream); + for (const track of this.localScreensharingStream.getTracks()) { track.stop(); } From f4e985b5cc6ad7c756daba52086e188e207b0da6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Mon, 2 Aug 2021 21:38:05 +0200 Subject: [PATCH 099/118] Fix typos MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/webrtc/call.ts | 62 +++++++++++++++++++++++----------------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/src/webrtc/call.ts b/src/webrtc/call.ts index 9c0947232..05a1c476e 100644 --- a/src/webrtc/call.ts +++ b/src/webrtc/call.ts @@ -141,7 +141,7 @@ export enum CallErrorCode { UnknownDevices = 'unknown_devices', /** - * Error code usewd when we fail to send the invite + * Error code used when we fail to send the invite * for some reason other than there being unknown devices */ SendInvite = 'send_invite', @@ -152,7 +152,7 @@ export enum CallErrorCode { CreateAnswer = 'create_answer', /** - * Error code usewd when we fail to send the answer + * Error code used when we fail to send the answer * for some reason other than there being unknown devices */ SendAnswer = 'send_answer', @@ -238,7 +238,7 @@ export class CallError extends Error { code: string; constructor(code: CallErrorCode, msg: string, err: Error) { - // Stil ldon't think there's any way to have proper nested errors + // Still don't think there's any way to have proper nested errors super(msg + ": " + err); this.code = code; @@ -641,7 +641,7 @@ export class MatrixCall extends EventEmitter { const remoteStream = this.feeds.find((feed) => !feed.isLocal())?.stream; // According to previous comments in this file, firefox at some point did not - // add streams until media started ariving on them. Testing latest firefox + // add streams until media started arriving on them. Testing latest firefox // (81 at time of writing), this is no longer a problem, so let's do it the correct way. if (!remoteStream || remoteStream.getTracks().length === 0) { logger.error("No remote stream or no tracks after setting remote description!"); @@ -952,11 +952,11 @@ export class MatrixCall extends EventEmitter { if (this.isRemoteOnHold() === onHold) return; this.remoteOnHold = onHold; - for (const tranceiver of this.peerConn.getTransceivers()) { + for (const transceiver of this.peerConn.getTransceivers()) { // We don't send hold music or anything so we're not actually // sending anything, but sendrecv is fairly standard for hold and // it makes it a lot easier to figure out who's put who on hold. - tranceiver.direction = onHold ? 'sendonly' : 'sendrecv'; + transceiver.direction = onHold ? 'sendonly' : 'sendrecv'; } this.updateMuteStatus(); @@ -975,8 +975,8 @@ export class MatrixCall extends EventEmitter { // We consider a call to be on hold only if *all* the tracks are on hold // (is this the right thing to do?) - for (const tranceiver of this.peerConn.getTransceivers()) { - const trackOnHold = ['inactive', 'recvonly'].includes(tranceiver.currentDirection); + for (const transceiver of this.peerConn.getTransceivers()) { + const trackOnHold = ['inactive', 'recvonly'].includes(transceiver.currentDirection); if (!trackOnHold) callOnHold = false; } @@ -1049,7 +1049,7 @@ export class MatrixCall extends EventEmitter { 'm.call.dtmf': false, }; - // We have just taken the local description from the peerconnection which will + // We have just taken the local description from the peerConn which will // contain all the local candidates added so far, so we can discard any candidates // we had queued up because they'll be in the answer. logger.info(`Discarding ${this.candidateSendQueue.length} candidates that will be sent in answer`); @@ -1165,8 +1165,8 @@ export class MatrixCall extends EventEmitter { return; } - const cands = ev.getContent().candidates; - if (!cands) { + const candidates = ev.getContent().candidates; + if (!candidates) { logger.info("Ignoring candidates event with no candidates!"); return; } @@ -1175,10 +1175,10 @@ export class MatrixCall extends EventEmitter { if (this.opponentPartyId === undefined) { // we haven't picked an opponent yet so save the candidates - logger.info(`Bufferring ${cands.length} candidates until we pick an opponent`); - const bufferedCands = this.remoteCandidateBuffer.get(fromPartyId) || []; - bufferedCands.push(...cands); - this.remoteCandidateBuffer.set(fromPartyId, bufferedCands); + logger.info(`Buffering ${candidates.length} candidates until we pick an opponent`); + const bufferedCandidates = this.remoteCandidateBuffer.get(fromPartyId) || []; + bufferedCandidates.push(...candidates); + this.remoteCandidateBuffer.set(fromPartyId, bufferedCandidates); return; } @@ -1191,7 +1191,7 @@ export class MatrixCall extends EventEmitter { return; } - await this.addIceCandidates(cands); + await this.addIceCandidates(candidates); } /** @@ -1393,7 +1393,7 @@ export class MatrixCall extends EventEmitter { lifetime: CALL_TIMEOUT_MS, } as MCallOfferNegotiate; - // clunky because TypeScript can't folow the types through if we use an expression as the key + // clunky because TypeScript can't follow the types through if we use an expression as the key if (this.state === CallState.CreateOffer) { content.offer = this.peerConn.localDescription; } else { @@ -1558,7 +1558,7 @@ export class MatrixCall extends EventEmitter { } onNegotiationNeeded = async () => { - logger.info("Negotation is needed!"); + logger.info("Negotiation is needed!"); if (this.state !== CallState.CreateOffer && this.opponentVersion === 0) { logger.info("Opponent does not support renegotiation: ignoring negotiationneeded event"); @@ -1646,12 +1646,12 @@ export class MatrixCall extends EventEmitter { // Don't send the ICE candidates yet if the call is in the ringing state: this // means we tried to pick (ie. started generating candidates) and then failed to // send the answer and went back to the ringing state. Queue up the candidates - // to send if we sucessfully send the answer. + // to send if we successfully send the answer. // Equally don't send if we haven't yet sent the answer because we can send the // first batch of candidates along with the answer if (this.state === CallState.Ringing || !this.inviteOrAnswerSent) return; - // MSC2746 reccomends these values (can be quite long when calling because the + // MSC2746 recommends these values (can be quite long when calling because the // callee will need a while to answer the call) const delay = this.direction === CallDirection.Inbound ? 500 : 2000; @@ -1667,7 +1667,7 @@ export class MatrixCall extends EventEmitter { */ async transfer(targetUserId: string) { // Fetch the target user's global profile info: their room avatar / displayname - // could be different in whatever room we shae with them. + // could be different in whatever room we share with them. const profileInfo = await this.client.getProfileInfo(targetUserId); const replacementId = genCallID(); @@ -1776,13 +1776,13 @@ export class MatrixCall extends EventEmitter { return; } - const cands = this.candidateSendQueue; + const candidates = this.candidateSendQueue; this.candidateSendQueue = []; ++this.candidateSendTries; const content = { - candidates: cands, + candidates: candidates, }; - logger.debug("Attempting to send " + cands.length + " candidates"); + logger.debug("Attempting to send " + candidates.length + " candidates"); try { await this.sendVoipEvent(EventType.CallCandidates, content); } catch (error) { @@ -1791,7 +1791,7 @@ export class MatrixCall extends EventEmitter { if (error.event) this.client.cancelPendingEvent(error.event); // put all the candidates we failed to send back in the queue - this.candidateSendQueue.push(...cands); + this.candidateSendQueue.push(...candidates); if (this.candidateSendTries > 5) { logger.debug( @@ -1895,16 +1895,16 @@ export class MatrixCall extends EventEmitter { } private async addBufferedIceCandidates() { - const bufferedCands = this.remoteCandidateBuffer.get(this.opponentPartyId); - if (bufferedCands) { - logger.info(`Adding ${bufferedCands.length} buffered candidates for opponent ${this.opponentPartyId}`); - await this.addIceCandidates(bufferedCands); + const bufferedCandidates = this.remoteCandidateBuffer.get(this.opponentPartyId); + if (bufferedCandidates) { + logger.info(`Adding ${bufferedCandidates.length} buffered candidates for opponent ${this.opponentPartyId}`); + await this.addIceCandidates(bufferedCandidates); } this.remoteCandidateBuffer = null; } - private async addIceCandidates(cands: RTCIceCandidate[]) { - for (const cand of cands) { + private async addIceCandidates(candidates: RTCIceCandidate[]) { + for (const cand of candidates) { if ( (cand.sdpMid === null || cand.sdpMid === undefined) && (cand.sdpMLineIndex === null || cand.sdpMLineIndex === undefined) From 7e0aeb425daa926014bbf996921b613dd51bb696 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Tue, 3 Aug 2021 09:31:06 +0200 Subject: [PATCH 100/118] Move things we can into a switch statement MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/webrtc/callEventHandler.ts | 77 +++++++++++++--------------------- 1 file changed, 29 insertions(+), 48 deletions(-) diff --git a/src/webrtc/callEventHandler.ts b/src/webrtc/callEventHandler.ts index 96b233e0f..913a6d6cd 100644 --- a/src/webrtc/callEventHandler.ts +++ b/src/webrtc/callEventHandler.ts @@ -220,17 +220,6 @@ export class CallEventHandler { } else { this.client.emit("Call.incoming", call); } - } else if (event.getType() === EventType.CallAnswer) { - if (!call) { - return; - } - if (event.getSender() === this.client.credentials.userId) { - if (call.state === CallState.Ringing) { - call.onAnsweredElsewhere(content); - } - } else { - call.onAnswerReceived(event); - } } else if (event.getType() === EventType.CallCandidates) { if (event.getSender() === this.client.credentials.userId) { return; @@ -267,48 +256,40 @@ export class CallEventHandler { this.calls.delete(content.call_id); } } - } else if (event.getType() === EventType.CallSelectAnswer) { - if (!call) return; + } - if (event.getContent().party_id === call.ourPartyId) { - // Ignore remote echo - return; - } + // The following events need a call + if (!call) return; + // Ignore remote echo + if (event.getContent().party_id === call.ourPartyId) return; - call.onSelectAnswerReceived(event); - } else if (event.getType() === EventType.CallNegotiate) { - if (!call) return; + switch (event.getType()) { + case EventType.CallAnswer: + if (event.getSender() === this.client.credentials.userId) { + if (call.state === CallState.Ringing) { + call.onAnsweredElsewhere(content); + } + } else { + call.onAnswerReceived(event); + } + break; + case EventType.CallSelectAnswer: + call.onSelectAnswerReceived(event); + break; - if (event.getContent().party_id === call.ourPartyId) { - // Ignore remote echo - return; - } + case EventType.CallNegotiate: + call.onNegotiateReceived(event); + break; - call.onNegotiateReceived(event); - } else if ( - event.getType() === EventType.CallAssertedIdentity || - event.getType() === EventType.CallAssertedIdentityPrefix - ) { - if (!call) return; + case EventType.CallAssertedIdentity: + case EventType.CallAssertedIdentityPrefix: + call.onAssertedIdentityReceived(event); + break; - if (event.getContent().party_id === call.ourPartyId) { - // Ignore remote echo (not that we send asserted identity, but still...) - return; - } - - call.onAssertedIdentityReceived(event); - } else if ( - event.getType() === EventType.CallSDPStreamMetadataChanged || - event.getType() === EventType.CallSDPStreamMetadataChangedPrefix - ) { - if (!call) return; - - if (event.getContent().party_id === call.ourPartyId) { - // Ignore remote echo - return; - } - - call.onSDPStreamMetadataChangedReceived(event); + case EventType.CallSDPStreamMetadataChanged: + case EventType.CallSDPStreamMetadataChangedPrefix: + call.onSDPStreamMetadataChangedReceived(event); + break; } } } From 904071ebe6858f5d47be2181ed1ad3640d3f1d09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Tue, 3 Aug 2021 09:33:40 +0200 Subject: [PATCH 101/118] Extract type into a variable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/webrtc/callEventHandler.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/webrtc/callEventHandler.ts b/src/webrtc/callEventHandler.ts index 913a6d6cd..ac42ae77e 100644 --- a/src/webrtc/callEventHandler.ts +++ b/src/webrtc/callEventHandler.ts @@ -130,10 +130,11 @@ export class CallEventHandler { private handleCallEvent(event: MatrixEvent) { const content = event.getContent(); + const type = event.getType() as EventType; let call = content.call_id ? this.calls.get(content.call_id) : undefined; - //console.info("RECV %s content=%s", event.getType(), JSON.stringify(content)); + //console.info("RECV %s content=%s", type, JSON.stringify(content)); - if (event.getType() === EventType.CallInvite) { + if (type === EventType.CallInvite) { if (event.getSender() === this.client.credentials.userId) { return; // ignore invites you send } @@ -220,7 +221,7 @@ export class CallEventHandler { } else { this.client.emit("Call.incoming", call); } - } else if (event.getType() === EventType.CallCandidates) { + } else if (type === EventType.CallCandidates) { if (event.getSender() === this.client.credentials.userId) { return; } @@ -233,7 +234,7 @@ export class CallEventHandler { } else { call.onRemoteIceCandidatesReceived(event); } - } else if ([EventType.CallHangup, EventType.CallReject].includes(event.getType() as EventType)) { + } else if ([EventType.CallHangup, EventType.CallReject].includes(type)) { // Note that we also observe our own hangups here so we can see // if we've already rejected a call that would otherwise be valid if (!call) { @@ -248,7 +249,7 @@ export class CallEventHandler { } } else { if (call.state !== CallState.Ended) { - if (event.getType() === EventType.CallHangup) { + if (type === EventType.CallHangup) { call.onHangupReceived(content); } else { call.onRejectReceived(content); @@ -263,7 +264,7 @@ export class CallEventHandler { // Ignore remote echo if (event.getContent().party_id === call.ourPartyId) return; - switch (event.getType()) { + switch (type) { case EventType.CallAnswer: if (event.getSender() === this.client.credentials.userId) { if (call.state === CallState.Ringing) { From dd3c53edace917b06c72f8c6f2b58795fbb24284 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Tue, 3 Aug 2021 09:37:41 +0200 Subject: [PATCH 102/118] Extract weSentTheEvent into a variable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/webrtc/callEventHandler.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/webrtc/callEventHandler.ts b/src/webrtc/callEventHandler.ts index ac42ae77e..873fd8193 100644 --- a/src/webrtc/callEventHandler.ts +++ b/src/webrtc/callEventHandler.ts @@ -131,11 +131,12 @@ export class CallEventHandler { private handleCallEvent(event: MatrixEvent) { const content = event.getContent(); const type = event.getType() as EventType; + const weSentTheEvent = event.getSender() === this.client.credentials.userId; let call = content.call_id ? this.calls.get(content.call_id) : undefined; //console.info("RECV %s content=%s", type, JSON.stringify(content)); if (type === EventType.CallInvite) { - if (event.getSender() === this.client.credentials.userId) { + if (weSentTheEvent) { return; // ignore invites you send } @@ -222,7 +223,7 @@ export class CallEventHandler { this.client.emit("Call.incoming", call); } } else if (type === EventType.CallCandidates) { - if (event.getSender() === this.client.credentials.userId) { + if (weSentTheEvent) { return; } if (!call) { @@ -266,7 +267,7 @@ export class CallEventHandler { switch (type) { case EventType.CallAnswer: - if (event.getSender() === this.client.credentials.userId) { + if (weSentTheEvent) { if (call.state === CallState.Ringing) { call.onAnsweredElsewhere(content); } From 5b13785115caf8e18e43e83d277831e1b540ba94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Tue, 3 Aug 2021 09:40:04 +0200 Subject: [PATCH 103/118] Make a few things into one liners MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/webrtc/callEventHandler.ts | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/src/webrtc/callEventHandler.ts b/src/webrtc/callEventHandler.ts index 873fd8193..27f3be9b1 100644 --- a/src/webrtc/callEventHandler.ts +++ b/src/webrtc/callEventHandler.ts @@ -136,17 +136,13 @@ export class CallEventHandler { //console.info("RECV %s content=%s", type, JSON.stringify(content)); if (type === EventType.CallInvite) { - if (weSentTheEvent) { - return; // ignore invites you send - } + // ignore invites you send + if (weSentTheEvent) return; + // expired call + if (event.getLocalAge() > content.lifetime - RING_GRACE_PERIOD) return; + // stale/old invite event + if (call && call.state === CallState.Ended) return; - if (event.getLocalAge() > content.lifetime - RING_GRACE_PERIOD) { - return; // expired call - } - - if (call && call.state === CallState.Ended) { - return; // stale/old invite event - } if (call) { logger.log( `WARN: Already have a MatrixCall with id ${content.call_id} but got an ` + @@ -223,9 +219,8 @@ export class CallEventHandler { this.client.emit("Call.incoming", call); } } else if (type === EventType.CallCandidates) { - if (weSentTheEvent) { - return; - } + if (weSentTheEvent) return; + if (!call) { // store the candidates; we may get a call eventually. if (!this.candidateEventsByCall.has(content.call_id)) { From ac2c09108f021caaebb8215660023cd88f7c428d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Tue, 3 Aug 2021 09:42:20 +0200 Subject: [PATCH 104/118] Improve styling a bit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/webrtc/callEventHandler.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/webrtc/callEventHandler.ts b/src/webrtc/callEventHandler.ts index 27f3be9b1..3e080c0eb 100644 --- a/src/webrtc/callEventHandler.ts +++ b/src/webrtc/callEventHandler.ts @@ -152,9 +152,11 @@ export class CallEventHandler { const timeUntilTurnCresExpire = this.client.getTurnServersExpiry() - Date.now(); logger.info("Current turn creds expire in " + timeUntilTurnCresExpire + " ms"); - call = createNewMatrixCall(this.client, event.getRoomId(), { - forceTURN: this.client.forceTURN, - }); + call = createNewMatrixCall( + this.client, + event.getRoomId(), + { forceTURN: this.client.forceTURN }, + ); if (!call) { logger.log( "Incoming call ID " + content.call_id + " but this client " + From fee6a963501c06e6d6eea2ce1b52e66ac55659d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Tue, 3 Aug 2021 09:44:44 +0200 Subject: [PATCH 105/118] Rename for consistency MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/webrtc/callEventTypes.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/webrtc/callEventTypes.ts b/src/webrtc/callEventTypes.ts index 7adf640e8..d070be000 100644 --- a/src/webrtc/callEventTypes.ts +++ b/src/webrtc/callEventTypes.ts @@ -47,7 +47,7 @@ export interface MCallSDPStreamMetadataChanged { [SDPStreamMetadataKey]: SDPStreamMetadata; } -export interface MCallReplacesTarget { +export interface CallReplacesTarget { id: string; display_name: string; avatar_url: string; @@ -55,7 +55,7 @@ export interface MCallReplacesTarget { export interface MCallReplacesEvent { replacement_id: string; - target_user: MCallReplacesTarget; + target_user: CallReplacesTarget; create_call: string; await_call: string; target_room: string; From b93e79274f15377659d0b1d32527ee7d264476e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Tue, 3 Aug 2021 09:45:27 +0200 Subject: [PATCH 106/118] Move for better organization MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/webrtc/callEventTypes.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/webrtc/callEventTypes.ts b/src/webrtc/callEventTypes.ts index d070be000..a02664743 100644 --- a/src/webrtc/callEventTypes.ts +++ b/src/webrtc/callEventTypes.ts @@ -29,6 +29,12 @@ export interface CallCapabilities { 'm.call.dtmf': boolean; } +export interface CallReplacesTarget { + id: string; + display_name: string; + avatar_url: string; +} + export interface MCallAnswer { answer: CallOfferAnswer; capabilities: CallCapabilities; @@ -47,12 +53,6 @@ export interface MCallSDPStreamMetadataChanged { [SDPStreamMetadataKey]: SDPStreamMetadata; } -export interface CallReplacesTarget { - id: string; - display_name: string; - avatar_url: string; -} - export interface MCallReplacesEvent { replacement_id: string; target_user: CallReplacesTarget; From 7484e3c0327ce0898d34b203945f022b2201ac0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Tue, 3 Aug 2021 09:48:23 +0200 Subject: [PATCH 107/118] Improve wording MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/webrtc/callEventTypes.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/webrtc/callEventTypes.ts b/src/webrtc/callEventTypes.ts index a02664743..009df5ed8 100644 --- a/src/webrtc/callEventTypes.ts +++ b/src/webrtc/callEventTypes.ts @@ -1,4 +1,4 @@ -// allow camelcase as these are events type that go onto the wire +// allow non-camelcase as these are events type that go onto the wire /* eslint-disable camelcase */ // TODO: Change to "sdp_stream_metadata" when MSC3077 is merged From 775c3465bba8c8f217c7449ea9a25a2a92ad6809 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Tue, 3 Aug 2021 10:05:05 +0200 Subject: [PATCH 108/118] Rename so that VS Code spell checker shutsup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/webrtc/call.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/webrtc/call.ts b/src/webrtc/call.ts index 05a1c476e..dd0043d25 100644 --- a/src/webrtc/call.ts +++ b/src/webrtc/call.ts @@ -1904,17 +1904,19 @@ export class MatrixCall extends EventEmitter { } private async addIceCandidates(candidates: RTCIceCandidate[]) { - for (const cand of candidates) { + for (const candidate of candidates) { if ( - (cand.sdpMid === null || cand.sdpMid === undefined) && - (cand.sdpMLineIndex === null || cand.sdpMLineIndex === undefined) + (candidate.sdpMid === null || candidate.sdpMid === undefined) && + (candidate.sdpMLineIndex === null || candidate.sdpMLineIndex === undefined) ) { logger.debug("Ignoring remote ICE candidate with no sdpMid or sdpMLineIndex"); continue; } - logger.debug("Call " + this.callId + " got remote ICE " + cand.sdpMid + " candidate: " + cand.candidate); + logger.debug( + "Call " + this.callId + " got remote ICE " + candidate.sdpMid + " candidate: " + candidate.candidate, + ); try { - await this.peerConn.addIceCandidate(cand); + await this.peerConn.addIceCandidate(candidate); } catch (err) { if (!this.ignoreOffer) { logger.info("Failed to add remote ICE candidate", err); From 3ca84cfc491b0987eec1f13f13cae58d2032bf54 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 3 Aug 2021 17:15:25 +0100 Subject: [PATCH 109/118] Update eslint plugin & fix silly indenting As per matrix-org/eslint-plugin-matrix-org#15 this caused a bunch of silly indenting to creep in, so this fixes it back to the previous style. --- package.json | 2 +- src/matrix.ts | 2 +- yarn.lock | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 470271444..e6b6e2405 100644 --- a/package.json +++ b/package.json @@ -88,7 +88,7 @@ "docdash": "^1.2.0", "eslint": "7.18.0", "eslint-config-google": "^0.14.0", - "eslint-plugin-matrix-org": "github:matrix-org/eslint-plugin-matrix-org#main", + "eslint-plugin-matrix-org": "github:matrix-org/eslint-plugin-matrix-org#2306b3d4da4eba908b256014b979f1d3d43d2945", "exorcist": "^1.0.1", "fake-indexeddb": "^3.1.2", "jest": "^26.6.3", diff --git a/src/matrix.ts b/src/matrix.ts index 78906a716..97709e314 100644 --- a/src/matrix.ts +++ b/src/matrix.ts @@ -154,7 +154,7 @@ export function createClient(opts: ICreateClientOpts | string) { } opts.request = opts.request || requestInstance; opts.store = opts.store || new MemoryStore({ - localStorage: global.localStorage, + localStorage: global.localStorage, }); opts.scheduler = opts.scheduler || new MatrixScheduler(); opts.cryptoStore = opts.cryptoStore || cryptoStoreFactory(); diff --git a/yarn.lock b/yarn.lock index 097f71cb5..d04bf8ea1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3016,9 +3016,9 @@ eslint-config-google@^0.14.0: resolved "https://registry.yarnpkg.com/eslint-config-google/-/eslint-config-google-0.14.0.tgz#4f5f8759ba6e11b424294a219dbfa18c508bcc1a" integrity sha512-WsbX4WbjuMvTdeVL6+J3rK1RGhCTqjsFjX7UMSMgZiyxxaNLkoJENbrGExzERFeoTpGw3F3FypTiWAP9ZXzkEw== -"eslint-plugin-matrix-org@github:matrix-org/eslint-plugin-matrix-org#main": - version "0.3.4" - resolved "https://codeload.github.com/matrix-org/eslint-plugin-matrix-org/tar.gz/45f6937539192e3820edcafc4d6d4d4187e85a6a" +"eslint-plugin-matrix-org@github:matrix-org/eslint-plugin-matrix-org#2306b3d4da4eba908b256014b979f1d3d43d2945": + version "0.3.5" + resolved "https://codeload.github.com/matrix-org/eslint-plugin-matrix-org/tar.gz/2306b3d4da4eba908b256014b979f1d3d43d2945" eslint-rule-composer@^0.3.0: version "0.3.0" From 08821e499c3228a40b2015848463704b6f79a40a Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 3 Aug 2021 17:51:42 +0100 Subject: [PATCH 110/118] Switch to new changelog generator --- package.json | 1 + release.sh | 15 +-- yarn.lock | 289 ++++++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 290 insertions(+), 15 deletions(-) diff --git a/package.json b/package.json index 470271444..cd18f986c 100644 --- a/package.json +++ b/package.json @@ -81,6 +81,7 @@ "@types/request": "^2.48.5", "@typescript-eslint/eslint-plugin": "^4.17.0", "@typescript-eslint/parser": "^4.17.0", + "allchange": "github:matrix-org/allchange", "babel-jest": "^26.6.3", "babelify": "^10.0.0", "better-docs": "^2.4.0-beta.9", diff --git a/release.sh b/release.sh index e2ef78263..af42fe886 100755 --- a/release.sh +++ b/release.sh @@ -102,11 +102,6 @@ yarn cache clean # Ensure all dependencies are updated yarn install --ignore-scripts --pure-lockfile -if [ -z "$skip_changelog" ]; then - # update_changelog doesn't have a --version flag - update_changelog -h > /dev/null || (echo "github-changelog-generator is required: please install it"; exit) -fi - # Login and publish continues to use `npm`, as it seems to have more clearly # defined options and semantics than `yarn` for writing to the registry. if [ -z "$skip_npm" ]; then @@ -133,14 +128,6 @@ if [ $prerelease -eq 1 ]; then echo Making a PRE-RELEASE fi -if [ -z "$skip_changelog" ]; then - if ! command -v update_changelog >/dev/null 2>&1; then - echo "release.sh requires github-changelog-generator. Try:" >&2 - echo " pip install git+https://github.com/matrix-org/github-changelog-generator.git" >&2 - exit 1 - fi -fi - # we might already be on the release branch, in which case, yay # If we're on any branch starting with 'release', we don't create # a separate release branch (this allows us to use the same @@ -156,7 +143,7 @@ fi if [ -z "$skip_changelog" ]; then echo "Generating changelog" - update_changelog -f "$changelog_file" "$release" + yarn run allchange "$release" read -p "Edit $changelog_file manually, or press enter to continue " REPLY if [ -n "$(git ls-files --modified $changelog_file)" ]; then diff --git a/yarn.lock b/yarn.lock index 097f71cb5..63bd7316d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1228,6 +1228,107 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" +"@octokit/auth-token@^2.4.4": + version "2.4.5" + resolved "https://registry.yarnpkg.com/@octokit/auth-token/-/auth-token-2.4.5.tgz#568ccfb8cb46f36441fac094ce34f7a875b197f3" + integrity sha512-BpGYsPgJt05M7/L/5FoE1PiAbdxXFZkX/3kDYcsvd1v6UhlnE5e96dTDr0ezX/EFwciQxf3cNV0loipsURU+WA== + dependencies: + "@octokit/types" "^6.0.3" + +"@octokit/core@^3.5.0": + version "3.5.1" + resolved "https://registry.yarnpkg.com/@octokit/core/-/core-3.5.1.tgz#8601ceeb1ec0e1b1b8217b960a413ed8e947809b" + integrity sha512-omncwpLVxMP+GLpLPgeGJBF6IWJFjXDS5flY5VbppePYX9XehevbDykRH9PdCdvqt9TS5AOTiDide7h0qrkHjw== + dependencies: + "@octokit/auth-token" "^2.4.4" + "@octokit/graphql" "^4.5.8" + "@octokit/request" "^5.6.0" + "@octokit/request-error" "^2.0.5" + "@octokit/types" "^6.0.3" + before-after-hook "^2.2.0" + universal-user-agent "^6.0.0" + +"@octokit/endpoint@^6.0.1": + version "6.0.12" + resolved "https://registry.yarnpkg.com/@octokit/endpoint/-/endpoint-6.0.12.tgz#3b4d47a4b0e79b1027fb8d75d4221928b2d05658" + integrity sha512-lF3puPwkQWGfkMClXb4k/eUT/nZKQfxinRWJrdZaJO85Dqwo/G0yOC434Jr2ojwafWJMYqFGFa5ms4jJUgujdA== + dependencies: + "@octokit/types" "^6.0.3" + is-plain-object "^5.0.0" + universal-user-agent "^6.0.0" + +"@octokit/graphql@^4.5.8": + version "4.6.4" + resolved "https://registry.yarnpkg.com/@octokit/graphql/-/graphql-4.6.4.tgz#0c3f5bed440822182e972317122acb65d311a5ed" + integrity sha512-SWTdXsVheRmlotWNjKzPOb6Js6tjSqA2a8z9+glDJng0Aqjzti8MEWOtuT8ZSu6wHnci7LZNuarE87+WJBG4vg== + dependencies: + "@octokit/request" "^5.6.0" + "@octokit/types" "^6.0.3" + universal-user-agent "^6.0.0" + +"@octokit/openapi-types@^9.3.0": + version "9.3.0" + resolved "https://registry.yarnpkg.com/@octokit/openapi-types/-/openapi-types-9.3.0.tgz#160347858d727527901c6aae7f7d5c2414cc1f2e" + integrity sha512-oz60hhL+mDsiOWhEwrj5aWXTOMVtQgcvP+sRzX4C3cH7WOK9QSAoEtjWh0HdOf6V3qpdgAmUMxnQPluzDWR7Fw== + +"@octokit/plugin-paginate-rest@^2.6.2": + version "2.15.0" + resolved "https://registry.yarnpkg.com/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.15.0.tgz#9c956c3710b2bd786eb3814eaf5a2b17392c150d" + integrity sha512-/vjcb0w6ggVRtsb8OJBcRR9oEm+fpdo8RJk45khaWw/W0c8rlB2TLCLyZt/knmC17NkX7T9XdyQeEY7OHLSV1g== + dependencies: + "@octokit/types" "^6.23.0" + +"@octokit/plugin-request-log@^1.0.2": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@octokit/plugin-request-log/-/plugin-request-log-1.0.4.tgz#5e50ed7083a613816b1e4a28aeec5fb7f1462e85" + integrity sha512-mLUsMkgP7K/cnFEw07kWqXGF5LKrOkD+lhCrKvPHXWDywAwuDUeDwWBpc69XK3pNX0uKiVt8g5z96PJ6z9xCFA== + +"@octokit/plugin-rest-endpoint-methods@5.6.0": + version "5.6.0" + resolved "https://registry.yarnpkg.com/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.6.0.tgz#c28833b88d0f07bf94093405d02d43d73c7de99b" + integrity sha512-2G7lIPwjG9XnTlNhe/TRnpI8yS9K2l68W4RP/ki3wqw2+sVeTK8hItPxkqEI30VeH0UwnzpuksMU/yHxiVVctw== + dependencies: + "@octokit/types" "^6.23.0" + deprecation "^2.3.1" + +"@octokit/request-error@^2.0.5", "@octokit/request-error@^2.1.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@octokit/request-error/-/request-error-2.1.0.tgz#9e150357831bfc788d13a4fd4b1913d60c74d677" + integrity sha512-1VIvgXxs9WHSjicsRwq8PlR2LR2x6DwsJAaFgzdi0JfJoGSO8mYI/cHJQ+9FbN21aa+DrgNLnwObmyeSC8Rmpg== + dependencies: + "@octokit/types" "^6.0.3" + deprecation "^2.0.0" + once "^1.4.0" + +"@octokit/request@^5.6.0": + version "5.6.0" + resolved "https://registry.yarnpkg.com/@octokit/request/-/request-5.6.0.tgz#6084861b6e4fa21dc40c8e2a739ec5eff597e672" + integrity sha512-4cPp/N+NqmaGQwbh3vUsYqokQIzt7VjsgTYVXiwpUP2pxd5YiZB2XuTedbb0SPtv9XS7nzAKjAuQxmY8/aZkiA== + dependencies: + "@octokit/endpoint" "^6.0.1" + "@octokit/request-error" "^2.1.0" + "@octokit/types" "^6.16.1" + is-plain-object "^5.0.0" + node-fetch "^2.6.1" + universal-user-agent "^6.0.0" + +"@octokit/rest@^18.6.7": + version "18.8.0" + resolved "https://registry.yarnpkg.com/@octokit/rest/-/rest-18.8.0.tgz#ba24f7ba554f015a7ae2b7cc2aecef5386ddfea5" + integrity sha512-lsuNRhgzGnLMn/NmQTNCit/6jplFWiTUlPXhqN0zCMLwf2/9pseHzsnTW+Cjlp4bLMEJJNPa5JOzSLbSCOahKw== + dependencies: + "@octokit/core" "^3.5.0" + "@octokit/plugin-paginate-rest" "^2.6.2" + "@octokit/plugin-request-log" "^1.0.2" + "@octokit/plugin-rest-endpoint-methods" "5.6.0" + +"@octokit/types@^6.0.3", "@octokit/types@^6.16.1", "@octokit/types@^6.23.0": + version "6.23.0" + resolved "https://registry.yarnpkg.com/@octokit/types/-/types-6.23.0.tgz#b39f242b20036e89fa8f34f7962b4e9b7ff8f65b" + integrity sha512-eG3clC31GSS7K3oBK6C6o7wyXPrkP+mu++eus8CSZdpRytJ5PNszYxudOQ0spWZQ3S9KAtoTG6v1WK5prJcJrA== + dependencies: + "@octokit/openapi-types" "^9.3.0" + "@sinonjs/commons@^1.7.0": version "1.8.3" resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.3.tgz#3802ddd21a50a949b6721ddd72da36e67e7f1b2d" @@ -1572,6 +1673,17 @@ align-text@^0.1.1, align-text@^0.1.3: longest "^1.0.1" repeat-string "^1.5.2" +"allchange@github:matrix-org/allchange": + version "0.0.1" + resolved "https://codeload.github.com/matrix-org/allchange/tar.gz/56b37b06339a3ac3fe771f3ec3d0bff798df8dab" + dependencies: + "@octokit/rest" "^18.6.7" + cli-color "^2.0.0" + js-yaml "^4.1.0" + loglevel "^1.7.1" + semver "^7.3.5" + yargs "^17.0.1" + another-json@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/another-json/-/another-json-0.2.0.tgz#b5f4019c973b6dd5c6506a2d93469cb6d32aeedc" @@ -1589,6 +1701,11 @@ ansi-escapes@^4.2.1: dependencies: type-fest "^0.21.3" +ansi-regex@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" + integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= + ansi-regex@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75" @@ -1636,6 +1753,11 @@ argparse@^1.0.7: dependencies: sprintf-js "~1.0.2" +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + arr-diff@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" @@ -1910,6 +2032,11 @@ bcrypt-pbkdf@^1.0.0: dependencies: tweetnacl "^0.14.3" +before-after-hook@^2.2.0: + version "2.2.2" + resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-2.2.2.tgz#a6e8ca41028d90ee2c24222f201c90956091613e" + integrity sha512-3pZEU3NT5BFUo/AD5ERPWOgQOCZITni6iavr5AUw5AUwQjMlI0kzu5btnyD39AF0gUEsDPwJT+oY1ORBJijPjQ== + better-docs@^2.4.0-beta.9: version "2.4.0-beta.9" resolved "https://registry.yarnpkg.com/better-docs/-/better-docs-2.4.0-beta.9.tgz#9ffa25b90b9a0fe4eb97528faedf53c04c416cfe" @@ -2374,6 +2501,18 @@ clean-css@^4.1.11: dependencies: source-map "~0.6.0" +cli-color@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/cli-color/-/cli-color-2.0.0.tgz#11ecfb58a79278cf6035a60c54e338f9d837897c" + integrity sha512-a0VZ8LeraW0jTuCkuAGMNufareGHhyZU9z8OGsW0gXd1hZGi1SRuNRXdbGkraBBKnhyUhyebFWnRbp+dIn0f0A== + dependencies: + ansi-regex "^2.1.1" + d "^1.0.1" + es5-ext "^0.10.51" + es6-iterator "^2.0.3" + memoizee "^0.4.14" + timers-ext "^0.1.7" + cliui@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/cliui/-/cliui-2.1.0.tgz#4b475760ff80264c762c3a1719032e91c7fea0d1" @@ -2664,6 +2803,14 @@ cssstyle@^2.3.0: dependencies: cssom "~0.3.6" +d@1, d@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/d/-/d-1.0.1.tgz#8698095372d58dbee346ffd0c7093f99f8f9eb5a" + integrity sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA== + dependencies: + es5-ext "^0.10.50" + type "^1.0.1" + dash-ast@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/dash-ast/-/dash-ast-1.0.0.tgz#12029ba5fb2f8aa6f0a861795b23c1b4b6c27d37" @@ -2768,6 +2915,11 @@ delayed-stream@~1.0.0: resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= +deprecation@^2.0.0, deprecation@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/deprecation/-/deprecation-2.3.1.tgz#6368cbdb40abf3373b525ac87e4a260c3a700919" + integrity sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ== + deps-sort@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/deps-sort/-/deps-sort-2.0.1.tgz#9dfdc876d2bcec3386b6829ac52162cda9fa208d" @@ -2984,6 +3136,42 @@ es-to-primitive@^1.2.1: is-date-object "^1.0.1" is-symbol "^1.0.2" +es5-ext@^0.10.35, es5-ext@^0.10.46, es5-ext@^0.10.50, es5-ext@^0.10.51, es5-ext@^0.10.53, es5-ext@~0.10.14, es5-ext@~0.10.2, es5-ext@~0.10.46: + version "0.10.53" + resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.53.tgz#93c5a3acfdbef275220ad72644ad02ee18368de1" + integrity sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q== + dependencies: + es6-iterator "~2.0.3" + es6-symbol "~3.1.3" + next-tick "~1.0.0" + +es6-iterator@^2.0.3, es6-iterator@~2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7" + integrity sha1-p96IkUGgWpSwhUQDstCg+/qY87c= + dependencies: + d "1" + es5-ext "^0.10.35" + es6-symbol "^3.1.1" + +es6-symbol@^3.1.1, es6-symbol@~3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.3.tgz#bad5d3c1bcdac28269f4cb331e431c78ac705d18" + integrity sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA== + dependencies: + d "^1.0.1" + ext "^1.1.2" + +es6-weak-map@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/es6-weak-map/-/es6-weak-map-2.0.3.tgz#b6da1f16cc2cc0d9be43e6bdbfc5e7dfcdf31d53" + integrity sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA== + dependencies: + d "1" + es5-ext "^0.10.46" + es6-iterator "^2.0.3" + es6-symbol "^3.1.1" + escalade@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" @@ -3152,6 +3340,14 @@ esutils@^2.0.2: resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== +event-emitter@^0.3.5: + version "0.3.5" + resolved "https://registry.yarnpkg.com/event-emitter/-/event-emitter-0.3.5.tgz#df8c69eef1647923c7157b9ce83840610b02cc39" + integrity sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk= + dependencies: + d "1" + es5-ext "~0.10.14" + events@^3.0.0: version "3.3.0" resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" @@ -3251,6 +3447,13 @@ expect@^26.6.2: jest-message-util "^26.6.2" jest-regex-util "^26.0.0" +ext@^1.1.2: + version "1.4.0" + resolved "https://registry.yarnpkg.com/ext/-/ext-1.4.0.tgz#89ae7a07158f79d35517882904324077e4379244" + integrity sha512-Key5NIsUxdqKg3vIsdw9dSuXpPCQ297y6wBjL30edxwPgt2E44WcWBZey/ZvUc6sERLTxKdyCu4gZFmUbk1Q7A== + dependencies: + type "^2.0.0" + extend-shallow@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" @@ -4112,12 +4315,17 @@ is-plain-object@^2.0.3, is-plain-object@^2.0.4: dependencies: isobject "^3.0.1" +is-plain-object@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-5.0.0.tgz#4427f50ab3429e9025ea7d52e9043a9ef4159344" + integrity sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q== + is-potential-custom-element-name@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz#171ed6f19e3ac554394edf78caa05784a45bebb5" integrity sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ== -is-promise@^2.0.0: +is-promise@^2.0.0, is-promise@^2.2.2: version "2.2.2" resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.2.2.tgz#39ab959ccbf9a774cf079f7b40c7a26f763135f1" integrity sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ== @@ -4676,6 +4884,13 @@ js-yaml@^3.13.1: argparse "^1.0.7" esprima "^4.0.0" +js-yaml@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + js2xmlparser@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/js2xmlparser/-/js2xmlparser-4.0.1.tgz#670ef71bc5661f089cc90481b99a05a1227ae3bd" @@ -4982,6 +5197,13 @@ lru-cache@^6.0.0: dependencies: yallist "^4.0.0" +lru-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/lru-queue/-/lru-queue-0.1.0.tgz#2738bd9f0d3cf4f84490c5736c48699ac632cda3" + integrity sha1-Jzi9nw089PhEkMVzbEhpmsYyzaM= + dependencies: + es5-ext "~0.10.2" + magic-string@^0.25.1: version "0.25.7" resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.7.tgz#3f497d6fd34c669c6798dcb821f2ef31f5445051" @@ -5071,6 +5293,20 @@ mdurl@^1.0.1: resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e" integrity sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4= +memoizee@^0.4.14: + version "0.4.15" + resolved "https://registry.yarnpkg.com/memoizee/-/memoizee-0.4.15.tgz#e6f3d2da863f318d02225391829a6c5956555b72" + integrity sha512-UBWmJpLZd5STPm7PMUlOw/TSy972M+z8gcyQ5veOnSDRREz/0bmpyTfKt3/51DhEBqCZQn1udM/5flcSPYhkdQ== + dependencies: + d "^1.0.1" + es5-ext "^0.10.53" + es6-weak-map "^2.0.3" + event-emitter "^0.3.5" + is-promise "^2.2.2" + lru-queue "^0.1.0" + next-tick "^1.1.0" + timers-ext "^0.1.7" + merge-stream@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" @@ -5256,6 +5492,16 @@ neo-async@^2.6.1: resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== +next-tick@1, next-tick@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.1.0.tgz#1836ee30ad56d67ef281b22bd199f709449b35eb" + integrity sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ== + +next-tick@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.0.0.tgz#ca86d1fe8828169b0120208e3dc8424b9db8342c" + integrity sha1-yobR/ogoFpsBICCOPchCS524NCw= + nice-try@^1.0.4: version "1.0.5" resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" @@ -5268,6 +5514,11 @@ node-dir@^0.1.10: dependencies: minimatch "^3.0.2" +node-fetch@^2.6.1: + version "2.6.1" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" + integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw== + node-int64@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" @@ -6871,6 +7122,14 @@ timers-browserify@^1.0.1: dependencies: process "~0.11.0" +timers-ext@^0.1.7: + version "0.1.7" + resolved "https://registry.yarnpkg.com/timers-ext/-/timers-ext-0.1.7.tgz#6f57ad8578e07a3fb9f91d9387d65647555e25c6" + integrity sha512-b85NUNzTSdodShTIbky6ZF02e8STtVVfD+fu4aXXShEELpozH+bCpJLYMPZbsABN2wDH7fJpqIoXxJpzbf0NqQ== + dependencies: + es5-ext "~0.10.46" + next-tick "1" + tmatch@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/tmatch/-/tmatch-2.0.1.tgz#0c56246f33f30da1b8d3d72895abaf16660f38cf" @@ -7047,6 +7306,16 @@ type-fest@^0.8.1: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== +type@^1.0.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/type/-/type-1.2.0.tgz#848dd7698dafa3e54a6c479e759c4bc3f18847a0" + integrity sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg== + +type@^2.0.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/type/-/type-2.5.0.tgz#0a2e78c2e77907b252abe5f298c1b01c63f0db3d" + integrity sha512-180WMDQaIMm3+7hGXWf12GtdniDEy7nYcyFMKJn/eZz/6tSLXrUN9V0wKSbMjej0I1WHWbpREDEKHtqPQa9NNw== + typedarray-to-buffer@^3.1.5: version "3.1.5" resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" @@ -7184,6 +7453,11 @@ union-value@^1.0.0: is-extendable "^0.1.1" set-value "^2.0.1" +universal-user-agent@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/universal-user-agent/-/universal-user-agent-6.0.0.tgz#3381f8503b251c0d9cd21bc1de939ec9df5480ee" + integrity sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w== + universalify@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" @@ -7618,6 +7892,19 @@ yargs@^16.2.0: y18n "^5.0.5" yargs-parser "^20.2.2" +yargs@^17.0.1: + version "17.0.1" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.0.1.tgz#6a1ced4ed5ee0b388010ba9fd67af83b9362e0bb" + integrity sha512-xBBulfCc8Y6gLFcrPvtqKz9hz8SO0l1Ni8GgDekvBX2ro0HRQImDGnikfc33cgzcYUSncapnNcZDjVFIH3f6KQ== + dependencies: + cliui "^7.0.2" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.0" + y18n "^5.0.5" + yargs-parser "^20.2.2" + yargs@~3.10.0: version "3.10.0" resolved "https://registry.yarnpkg.com/yargs/-/yargs-3.10.0.tgz#f7ee7bd857dd7c1d2d38c0e74efbd681d1431fd1" From 8800c6d2e0f1f37f6e50b718aed538545608af6f Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 4 Aug 2021 13:16:15 +0100 Subject: [PATCH 111/118] Add CODEOWNERS This should make github auto review request from the element-web team --- .github/CODEOWNERS | 1 + 1 file changed, 1 insertion(+) create mode 100644 .github/CODEOWNERS diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 000000000..2c068fff3 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @matrix-org/element-web From c45bc9138990c5891fa13d0d44c1f7a70ef42e4b Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Thu, 5 Aug 2021 11:16:28 +0200 Subject: [PATCH 112/118] Include all TypeScript @types listed in project dependency Fixes vector-im/element-web#17743 The package @types/jest was not discovered because if types is specified, only packages listed will be included in the global scope. --- tsconfig.json | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/tsconfig.json b/tsconfig.json index 548bbe7fb..896c4c3b3 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -9,12 +9,10 @@ "noImplicitAny": false, "sourceMap": true, "outDir": "./lib", - "declaration": true, - "types": [ - "node" - ] + "declaration": true }, "include": [ - "./src/**/*.ts" + "./src/**/*.ts", + "./spec/**/*.ts", ] } From 026260502b1359e6f125fc52acb6543d1a0ef4a4 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Thu, 5 Aug 2021 11:37:27 +0200 Subject: [PATCH 113/118] Fix linting issues in TypeScript test files --- spec/unit/models/MSC3089Branch.spec.ts | 7 +++---- spec/unit/models/MSC3089TreeSpace.spec.ts | 10 +++++----- spec/unit/relations.spec.ts | 10 +++++----- src/models/room-state.ts | 6 +++--- 4 files changed, 16 insertions(+), 17 deletions(-) diff --git a/spec/unit/models/MSC3089Branch.spec.ts b/spec/unit/models/MSC3089Branch.spec.ts index fc8b35815..72454cc54 100644 --- a/spec/unit/models/MSC3089Branch.spec.ts +++ b/spec/unit/models/MSC3089Branch.spec.ts @@ -16,7 +16,6 @@ limitations under the License. import { MatrixClient } from "../../../src"; import { Room } from "../../../src/models/room"; -import { MatrixEvent } from "../../../src/models/event"; import { UNSTABLE_MSC3089_BRANCH } from "../../../src/@types/event"; import { EventTimelineSet } from "../../../src/models/event-timeline-set"; import { EventTimeline } from "../../../src/models/event-timeline"; @@ -25,7 +24,7 @@ import { MSC3089Branch } from "../../../src/models/MSC3089Branch"; describe("MSC3089Branch", () => { let client: MatrixClient; // @ts-ignore - TS doesn't know that this is a type - let indexEvent: MatrixEvent; + let indexEvent: any; let branch: MSC3089Branch; const branchRoomId = "!room:example.org"; @@ -47,10 +46,10 @@ describe("MSC3089Branch", () => { } }, }; - indexEvent = { + indexEvent = ({ getRoomId: () => branchRoomId, getStateKey: () => fileEventId, - }; + }); branch = new MSC3089Branch(client, indexEvent); }); diff --git a/spec/unit/models/MSC3089TreeSpace.spec.ts b/spec/unit/models/MSC3089TreeSpace.spec.ts index 951ab4c0e..ae9652e7d 100644 --- a/spec/unit/models/MSC3089TreeSpace.spec.ts +++ b/spec/unit/models/MSC3089TreeSpace.spec.ts @@ -29,7 +29,7 @@ import { MatrixError } from "../../../src/http-api"; describe("MSC3089TreeSpace", () => { let client: MatrixClient; - let room: Room; + let room: any; let tree: MSC3089TreeSpace; const roomId = "!tree:localhost"; const targetUser = "@target:example.org"; @@ -170,7 +170,7 @@ describe("MSC3089TreeSpace", () => { expect(userIds).toMatchObject([target]); return Promise.resolve(); }); - client.invite = () => Promise.resolve(); // we're not testing this here - see other tests + client.invite = () => Promise.resolve({}); // we're not testing this here - see other tests client.sendSharedHistoryKeys = sendKeysFn; // Mock the history check as best as possible @@ -198,7 +198,7 @@ describe("MSC3089TreeSpace", () => { expect(userIds).toMatchObject([target]); return Promise.resolve(); }); - client.invite = () => Promise.resolve(); // we're not testing this here - see other tests + client.invite = () => Promise.resolve({}); // we're not testing this here - see other tests client.sendSharedHistoryKeys = sendKeysFn; const historyVis = "joined"; // NOTE: Changed. @@ -446,9 +446,9 @@ describe("MSC3089TreeSpace", () => { // Danger: these are partial implementations for testing purposes only // @ts-ignore - "MatrixEvent is a value but used as a type", which is true but not important - let childState: { [roomId: string]: MatrixEvent[] } = {}; + let childState: { [roomId: string]: any[] } = {}; // @ts-ignore - "MatrixEvent is a value but used as a type", which is true but not important - let parentState: MatrixEvent[] = []; + let parentState: any[] = []; let parentRoom: Room; let childTrees: MSC3089TreeSpace[]; let rooms: { [roomId: string]: Room }; diff --git a/spec/unit/relations.spec.ts b/spec/unit/relations.spec.ts index 45c20c00c..27370fba0 100644 --- a/spec/unit/relations.spec.ts +++ b/spec/unit/relations.spec.ts @@ -16,11 +16,13 @@ limitations under the License. import { EventTimelineSet } from "../../src/models/event-timeline-set"; import { MatrixEvent } from "../../src/models/event"; +import { Room } from "../../src/models/room"; import { Relations } from "../../src/models/relations"; describe("Relations", function() { it("should deduplicate annotations", function() { - const relations = new Relations("m.annotation", "m.reaction"); + const room = new Room("room123", null, null); + const relations = new Relations("m.annotation", "m.reaction", room); // Create an instance of an annotation const eventData = { @@ -95,10 +97,8 @@ describe("Relations", function() { }); // Stub the room - const room = { - getPendingEvent() { return null; }, - getUnfilteredTimelineSet() { return null; }, - }; + + const room = new Room("room123", null, null); // Add the target event first, then the relation event { diff --git a/src/models/room-state.ts b/src/models/room-state.ts index 2b67ee79e..79ad48f8e 100644 --- a/src/models/room-state.ts +++ b/src/models/room-state.ts @@ -204,9 +204,9 @@ export class RoomState extends EventEmitter { * @return {MatrixEvent[]|MatrixEvent} A list of events if state_key was * undefined, else a single event (or null if no match found). */ - public getStateEvents(eventType: string): MatrixEvent[]; - public getStateEvents(eventType: string, stateKey: string): MatrixEvent; - public getStateEvents(eventType: string, stateKey?: string) { + public getStateEvents(eventType: EventType | string): MatrixEvent[]; + public getStateEvents(eventType: EventType | string, stateKey: string): MatrixEvent; + public getStateEvents(eventType: EventType | string, stateKey?: string) { if (!this.events.has(eventType)) { // no match return stateKey === undefined ? [] : null; From b63af00579b65049d5fc473ace1f9c38ffa23cca Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Thu, 5 Aug 2021 11:56:26 +0200 Subject: [PATCH 114/118] Exclude tests when building browserify package --- package.json | 2 +- tsconfig-build.json | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 tsconfig-build.json diff --git a/package.json b/package.json index 607b21edb..63c9e731e 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "build:dev": "yarn clean && git rev-parse HEAD > git-revision.txt && yarn build:compile && yarn build:types", "build:types": "tsc --emitDeclarationOnly", "build:compile": "babel -d lib --verbose --extensions \".ts,.js\" src", - "build:compile-browser": "mkdirp dist && browserify -d src/browser-index.js -p [ tsify -p ./tsconfig.json ] -t [ babelify --sourceMaps=inline --presets [ @babel/preset-env @babel/preset-typescript ] ] | exorcist dist/browser-matrix.js.map > dist/browser-matrix.js", + "build:compile-browser": "mkdirp dist && browserify -d src/browser-index.js -p [ tsify -p ./tsconfig-build.json ] -t [ babelify --sourceMaps=inline --presets [ @babel/preset-env @babel/preset-typescript ] ] | exorcist dist/browser-matrix.js.map > dist/browser-matrix.js", "build:minify-browser": "terser dist/browser-matrix.js --compress --mangle --source-map --output dist/browser-matrix.min.js", "gendoc": "jsdoc -c jsdoc.json -P package.json", "lint": "yarn lint:types && yarn lint:js", diff --git a/tsconfig-build.json b/tsconfig-build.json new file mode 100644 index 000000000..50f5536d9 --- /dev/null +++ b/tsconfig-build.json @@ -0,0 +1,6 @@ +{ + "extends": "./tsconfig.json", + "exclude": [ + "./spec/**/*.ts" + ] +} From 2bdb570f3cdcdd9b6f615987b8192e8d1355423f Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 6 Aug 2021 09:38:50 +0100 Subject: [PATCH 115/118] Fix type documentation on MatrixClient::setRoomMutePushRule --- src/client.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/client.ts b/src/client.ts index ac7647479..2bcda09e4 100644 --- a/src/client.ts +++ b/src/client.ts @@ -5184,11 +5184,11 @@ export class MatrixClient extends EventEmitter { * The operation also updates MatrixClient.pushRules at the end. * @param {string} scope "global" or device-specific. * @param {string} roomId the id of the room. - * @param {string} mute the mute state. + * @param {boolean} mute the mute state. * @return {Promise} Resolves: result object * @return {module:http-api.MatrixError} Rejects: with an error response. */ - public setRoomMutePushRule(scope: string, roomId: string, mute: string): Promise | void { + public setRoomMutePushRule(scope: string, roomId: string, mute: boolean): Promise | void { let deferred; let hasDontNotifyRule; From 5482a19979d08304dd98fb3c5a9cadd92927578e Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 6 Aug 2021 09:09:03 -0600 Subject: [PATCH 116/118] Expose a getFileEvent() function off a MSC3089Branch --- src/models/MSC3089Branch.ts | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/models/MSC3089Branch.ts b/src/models/MSC3089Branch.ts index 7bb8c356e..875f9bd7d 100644 --- a/src/models/MSC3089Branch.ts +++ b/src/models/MSC3089Branch.ts @@ -82,6 +82,19 @@ export class MSC3089Branch { * @returns {Promise<{info: IEncryptedFile, httpUrl: string}>} Information about the file. */ public async getFileInfo(): Promise<{ info: IEncryptedFile, httpUrl: string }> { + const event = await this.getFileEvent(); + + const file = event.getContent()['file']; + const httpUrl = this.client.mxcUrlToHttp(file['url']); + + return { info: file, httpUrl: httpUrl }; + } + + /** + * Gets the event the file points to. + * @returns {Promise} Resolves to the file's event. + */ + public async getFileEvent(): Promise { const room = this.client.getRoom(this.roomId); if (!room) throw new Error("Unknown room"); @@ -94,9 +107,6 @@ export class MSC3089Branch { // Sometimes the event context doesn't decrypt for us, so do that. await this.client.decryptEventIfNeeded(event, { emit: false, isRetry: false }); - const file = event.getContent()['file']; - const httpUrl = this.client.mxcUrlToHttp(file['url']); - - return { info: file, httpUrl: httpUrl }; + return event; } } From ea9459d330543215495fef9b8b91b2589604ff99 Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 6 Aug 2021 23:05:00 +0100 Subject: [PATCH 117/118] Add changelog preview action --- .github/workflows/preview_changelog.yaml | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 .github/workflows/preview_changelog.yaml diff --git a/.github/workflows/preview_changelog.yaml b/.github/workflows/preview_changelog.yaml new file mode 100644 index 000000000..d68d19361 --- /dev/null +++ b/.github/workflows/preview_changelog.yaml @@ -0,0 +1,12 @@ +name: Preview Changelog +on: + pull_request_target: + types: [ opened, edited, labeled ] +jobs: + changelog: + runs-on: ubuntu-latest + steps: + - name: Preview Changelog + uses: matrix-org/allchange@main + with: + ghToken: ${{ secrets.GITHUB_TOKEN }} From b61312feca7b9f395da6d9a56bd0886e84f31cbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sun, 8 Aug 2021 11:08:36 +0200 Subject: [PATCH 118/118] Fix screensharing crash MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We have to first stop the track and only then delete the feed as stopping the tracks depends on the feed being defined Signed-off-by: Šimon Brandner --- src/webrtc/call.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/webrtc/call.ts b/src/webrtc/call.ts index ed687a69e..da529c08a 100644 --- a/src/webrtc/call.ts +++ b/src/webrtc/call.ts @@ -837,10 +837,10 @@ export class MatrixCall extends EventEmitter { for (const sender of this.screensharingSenders) { this.peerConn.removeTrack(sender); } - this.deleteFeedByStream(this.localScreensharingStream); for (const track of this.localScreensharingStream.getTracks()) { track.stop(); } + this.deleteFeedByStream(this.localScreensharingStream); return false; } } @@ -888,10 +888,10 @@ export class MatrixCall extends EventEmitter { }); sender.replaceTrack(track); - this.deleteFeedByStream(this.localScreensharingStream); for (const track of this.localScreensharingStream.getTracks()) { track.stop(); } + this.deleteFeedByStream(this.localScreensharingStream); return false; }