Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 26 additions & 26 deletions JitsiConference.ts
Original file line number Diff line number Diff line change
Expand Up @@ -835,32 +835,6 @@ export default class JitsiConference extends Listenable {
}));
}

/**
* Restarts all active media sessions.
*
* @returns {void}
*/
private _restartMediaSessions(): void {
if (this.p2pJingleSession) {
this._stopP2PSession({
reasonDescription: 'restart',
requestRestart: true
});
}

if (this.jvbJingleSession) {
this._stopJvbSession({
reason: 'success',
reasonDescription: 'restart required',
requestRestart: true,
sendSessionTerminate: true
});
}

this._maybeStartOrStopP2P(false);
}


/**
* Fires TRACK_AUDIO_LEVEL_CHANGED change conference event (for local tracks).
* @param {number} audioLevel - The audio level.
Expand Down Expand Up @@ -2427,6 +2401,32 @@ export default class JitsiConference extends Listenable {
return process;
}

/**
* Restarts all active media sessions.
*
* @returns {void}
* @internal
*/
_restartMediaSessions(): void {
if (this.p2pJingleSession) {
this._stopP2PSession({
reasonDescription: 'restart',
requestRestart: true
});
}

if (this.jvbJingleSession) {
this._stopJvbSession({
reason: 'success',
reasonDescription: 'restart required',
requestRestart: true,
sendSessionTerminate: true
});
}

this._maybeStartOrStopP2P(false);
}

/**
* Check if joined to the conference.
* @returns {boolean} True if joined, false otherwise.
Expand Down
6 changes: 6 additions & 0 deletions globals.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ declare global {
writable: WritableStream<RTCEncodedAudioFrame | RTCEncodedVideoFrame>;
}
}
interface RTCRtpSender {
createEncodedStreams?: () => {
readable: ReadableStream<RTCEncodedAudioFrame | RTCEncodedVideoFrame>;
writable: WritableStream<RTCEncodedAudioFrame | RTCEncodedVideoFrame>;
}
}
interface MediaStream {
oninactive?: ((this: MediaStream, ev: Event) => void) | ((this: MediaStreamTrack, ev: Event) => void) | null;
}
Expand Down
19 changes: 11 additions & 8 deletions modules/e2ee/E2EEContext.js → modules/e2ee/E2EEContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,18 @@ const kJitsiE2EE = Symbol('kJitsiE2EE');
* - allow for the key to be rotated frequently.
*/
export default class E2EEcontext {
private _worker: Worker;

/**
* Build a new E2EE context instance, which will be used in a given conference.
* @param {Object} [options] - The options object.
* @param {boolean} [options.sharedKey] - whether there is a uniques key shared amoung all participants.
*/
constructor({ sharedKey } = {}) {
constructor({ sharedKey }: { sharedKey?: boolean; } = {}) {
// Determine the URL for the worker script. Relative URLs are relative to
// the entry point, not the script that launches the worker.
let baseUrl = '';
const ljm = document.querySelector('script[src*="lib-jitsi-meet"]');
const ljm = document.querySelector('script[src*="lib-jitsi-meet"]') as HTMLScriptElement;

if (ljm) {
const idx = ljm.src.lastIndexOf('/');
Expand Down Expand Up @@ -67,7 +70,7 @@ export default class E2EEcontext {
*
* @param {string} participantId - The participant that just left.
*/
cleanup(participantId) {
public cleanup(participantId: string): void {
this._worker.postMessage({
operation: 'cleanup',
participantId
Expand All @@ -78,7 +81,7 @@ export default class E2EEcontext {
* Cleans up all state associated with all participants in the conference. This is needed when disabling e2ee.
*
*/
cleanupAll() {
public cleanupAll(): void {
this._worker.postMessage({
operation: 'cleanupAll'
});
Expand All @@ -92,7 +95,7 @@ export default class E2EEcontext {
* @param {string} kind - The kind of track this receiver belongs to.
* @param {string} participantId - The participant id that this receiver belongs to.
*/
handleReceiver(receiver, kind, participantId) {
public handleReceiver(receiver: RTCRtpReceiver, kind: string, participantId: string): void {
if (receiver[kJitsiE2EE]) {
return;
}
Expand Down Expand Up @@ -125,7 +128,7 @@ export default class E2EEcontext {
* @param {string} kind - The kind of track this sender belongs to.
* @param {string} participantId - The participant id that this sender belongs to.
*/
handleSender(sender, kind, participantId) {
public handleSender(sender: RTCRtpSender, kind: string, participantId: string): void {
if (sender[kJitsiE2EE]) {
return;
}
Expand Down Expand Up @@ -155,7 +158,7 @@ export default class E2EEcontext {
*
* @param {boolean} enabled - whether E2EE is enabled or not.
*/
setEnabled(enabled) {
public setEnabled(enabled: boolean): void {
this._worker.postMessage({
enabled,
operation: 'setEnabled'
Expand All @@ -169,7 +172,7 @@ export default class E2EEcontext {
* @param {Uint8Array | boolean} key - they key for the given participant.
* @param {Number} keyIndex - the key index.
*/
setKey(participantId, key, keyIndex) {
public setKey(participantId: string, key: Uint8Array | boolean, keyIndex: number): void {
this._worker.postMessage({
key,
keyIndex,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import JitsiConference from '../../JitsiConference';

import { KeyHandler } from './KeyHandler';

/**
Expand All @@ -8,7 +10,7 @@ export class ExternallyManagedKeyHandler extends KeyHandler {
* Build a new ExternallyManagedKeyHandler instance, which will be used in a given conference.
* @param conference - the current conference.
*/
constructor(conference) {
constructor(conference: JitsiConference) {
super(conference, { sharedKey: true });
}

Expand All @@ -19,7 +21,9 @@ export class ExternallyManagedKeyHandler extends KeyHandler {
* @param {Number} [keyInfo.index] - the index of the encryption key.
* @returns {void}
*/
setKey(keyInfo) {
this.e2eeCtx.setKey(undefined, { encryptionKey: keyInfo.encryptionKey }, keyInfo.index);
public async setKey(keyInfo: { encryptionKey: CryptoKey; index: number; }) {
const keyData = await crypto.subtle.exportKey('raw', keyInfo.encryptionKey);

this.e2eeCtx.setKey(undefined, new Uint8Array(keyData), keyInfo.index);
}
}
125 changes: 73 additions & 52 deletions modules/e2ee/KeyHandler.js → modules/e2ee/KeyHandler.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,45 @@
import { getLogger } from '@jitsi/logger';

import JitsiConference from '../../JitsiConference';
import { JitsiConferenceEvents } from '../../JitsiConferenceEvents';
import { RTCEvents } from '../../service/RTC/RTCEvents';
import JitsiLocalTrack from '../RTC/JitsiLocalTrack';
import JitsiRemoteTrack from '../RTC/JitsiRemoteTrack';
import TraceablePeerConnection from '../RTC/TraceablePeerConnection';
import browser from '../browser';
import Deferred from '../util/Deferred';
import Listenable from '../util/Listenable';
import JingleSessionPC from '../xmpp/JingleSessionPC';

import E2EEContext from './E2EEContext';

const logger = getLogger('e2ee:KeyHandler');

/**
* Options for the KeyHandler constructor.
*/
export interface IKeyHandlerOptions {
sharedKey?: boolean;
}


/**
* Abstract class that integrates {@link E2EEContext} with a key management system.
*/
export class KeyHandler extends Listenable {
protected conference: JitsiConference;
protected e2eeCtx: E2EEContext;
protected enabled: boolean;
protected _enabling?: Deferred<void>;
protected _firstEnable: boolean;
protected _setEnabled?: (enabled: boolean) => Promise<void>;

/**
* Build a new KeyHandler instance, which will be used in a given conference.
* @param {JitsiConference} conference - the current conference.
* @param {object} options - the options passed to {E2EEContext}, see implemention.
*/
constructor(conference, options = {}) {
constructor(conference: JitsiConference, options: IKeyHandlerOptions = {}) {
super();

this.conference = conference;
Expand All @@ -39,63 +59,21 @@ export class KeyHandler extends Listenable {
this._onMediaSessionStarted.bind(this));
this.conference.on(
JitsiConferenceEvents.TRACK_ADDED,
track => track.isLocal() && this._onLocalTrackAdded(track));
(track: JitsiLocalTrack | JitsiRemoteTrack) => track.isLocal() && this._onLocalTrackAdded(track as JitsiLocalTrack));
this.conference.rtc.on(
RTCEvents.REMOTE_TRACK_ADDED,
(track, tpc) => this._setupReceiverE2EEForTrack(tpc, track));
(track: JitsiRemoteTrack, tpc: TraceablePeerConnection) => this._setupReceiverE2EEForTrack(tpc, track));
this.conference.on(
JitsiConferenceEvents.TRACK_MUTE_CHANGED,
this._trackMuteChanged.bind(this));
}

/**
* Indicates whether E2EE is currently enabled or not.
*
* @returns {boolean}
*/
isEnabled() {
return this.enabled;
}

/**
* Enables / disables End-To-End encryption.
*
* @param {boolean} enabled - whether E2EE should be enabled or not.
* @returns {void}
*/
async setEnabled(enabled) {
this._enabling && await this._enabling;

if (enabled === this.enabled) {
return;
}

this._enabling = new Deferred();

this.enabled = enabled;

this._setEnabled && await this._setEnabled(enabled);

this.conference.setLocalParticipantProperty('e2ee.enabled', enabled);

// Only restart media sessions if E2EE is enabled. If it's later disabled
// we'll continue to use the existing media sessions with an empty transform.
if (!this._firstEnable && enabled) {
this._firstEnable = true;
this.conference._restartMediaSessions();
}

this.e2eeCtx.setEnabled(enabled);

this._enabling.resolve();
}

/**
* Setup E2EE on the new track that has been added to the conference, apply it on all the open peerconnections.
* @param {JitsiLocalTrack} track - the new track that's being added to the conference.
* @private
*/
_onLocalTrackAdded(track) {
private _onLocalTrackAdded(track: JitsiLocalTrack): void {
for (const session of this.conference.getMediaSessions()) {
this._setupSenderE2EEForTrack(session, track);
}
Expand All @@ -106,7 +84,7 @@ export class KeyHandler extends Listenable {
* @param {JingleSessionPC} session - the new media session.
* @private
*/
_onMediaSessionStarted(session) {
private _onMediaSessionStarted(session: JingleSessionPC): void {
const localTracks = this.conference.getLocalTracks();

for (const track of localTracks) {
Expand All @@ -119,7 +97,7 @@ export class KeyHandler extends Listenable {
*
* @private
*/
_setupReceiverE2EEForTrack(tpc, track) {
private _setupReceiverE2EEForTrack(tpc: TraceablePeerConnection, track: JitsiRemoteTrack): void {
if (!this.enabled && !this._firstEnable) {
return;
}
Expand All @@ -140,13 +118,13 @@ export class KeyHandler extends Listenable {
* @param {JitsiLocalTrack} track - the local track for which e2e encoder will be configured.
* @private
*/
_setupSenderE2EEForTrack(session, track) {
private _setupSenderE2EEForTrack(session: JingleSessionPC, track: JitsiLocalTrack): void {
if (!this.enabled && !this._firstEnable) {
return;
}

const pc = session.peerconnection;
const sender = pc && pc.findSenderForTrack(track.track);
const sender = pc?.findSenderForTrack(track.track);

if (sender) {
this.e2eeCtx.handleSender(sender, track.getType(), track.getParticipantId());
Expand All @@ -160,11 +138,54 @@ export class KeyHandler extends Listenable {
* @param {JitsiLocalTrack} track - the track for which muted status has changed.
* @private
*/
_trackMuteChanged(track) {
private _trackMuteChanged(track: JitsiLocalTrack): void {
if (browser.doesVideoMuteByStreamRemove() && track.isLocal() && track.isVideoTrack() && !track.isMuted()) {
for (const session of this.conference.getMediaSessions()) {
this._setupSenderE2EEForTrack(session, track);
this._setupSenderE2EEForTrack(session, track as JitsiLocalTrack);
}
}
}

/**
* Indicates whether E2EE is currently enabled or not.
*
* @returns {boolean}
*/
public isEnabled(): boolean {
return this.enabled;
}

/**
* Enables / disables End-To-End encryption.
*
* @param {boolean} enabled - whether E2EE should be enabled or not.
* @returns {void}
*/
public async setEnabled(enabled: boolean): Promise<void> {
this._enabling && await this._enabling;

if (enabled === this.enabled) {
return;
}

this._enabling = new Deferred<void>();

this.enabled = enabled;

this._setEnabled && await this._setEnabled(enabled);

this.conference.setLocalParticipantProperty('e2ee.enabled', enabled.toString());

// Only restart media sessions if E2EE is enabled. If it's later disabled
// we'll continue to use the existing media sessions with an empty transform.
if (!this._firstEnable && enabled) {
this._firstEnable = true;
this.conference._restartMediaSessions();
}

this.e2eeCtx.setEnabled(enabled);

this._enabling.resolve();
}

}
Loading