livekit-client
Version:
JavaScript/TypeScript client SDK for LiveKit
122 lines (103 loc) • 3.29 kB
text/typescript
import type { Throws } from '@livekit/throws-transformer/throws';
import type { BaseE2EEManager } from '../../../e2ee/E2eeManager';
import { LoggerNames, getLogger } from '../../../logger';
import DataTrackDepacketizer, { DataTrackDepacketizerDropError } from '../depacketizer';
import type { DataTrackFrameInternal } from '../frame';
import { DataTrackPacket } from '../packet';
import { type DataTrackInfo } from '../types';
const log = getLogger(LoggerNames.DataTracks);
/**
* Options for creating a {@link IncomingDataTrackPipeline}.
*/
type Options = {
info: DataTrackInfo;
publisherIdentity: string;
e2eeManager: BaseE2EEManager | null;
};
/**
* Pipeline for an individual data track subscription.
*/
export default class IncomingDataTrackPipeline {
private publisherIdentity: string;
private e2eeManager: BaseE2EEManager | null;
private depacketizer: DataTrackDepacketizer;
/**
* Creates a new pipeline with the given options.
*/
constructor(options: Options) {
const hasProvider = options.e2eeManager !== null;
if (options.info.usesE2ee !== hasProvider) {
// @throws-transformer ignore - this should be treated as a "panic" and not be caught
throw new Error(
'IncomingDataTrackPipeline: DataTrackInfo.usesE2ee must match presence of decryptionProvider',
);
}
const depacketizer = new DataTrackDepacketizer();
this.publisherIdentity = options.publisherIdentity;
this.e2eeManager = options.e2eeManager ?? null;
this.depacketizer = depacketizer;
}
updateE2eeManager(e2eeManager: BaseE2EEManager | null) {
this.e2eeManager = e2eeManager;
}
async processPacket(
packet: DataTrackPacket,
): Promise<Throws<DataTrackFrameInternal | null, DataTrackDepacketizerDropError>> {
const frame = this.depacketize(packet);
if (!frame) {
return null;
}
const decrypted = await this.decryptIfNeeded(frame);
if (!decrypted) {
return null;
}
return decrypted;
}
/**
* Depacketize the given frame, log if a drop occurs.
*/
private depacketize(
packet: DataTrackPacket,
): Throws<DataTrackFrameInternal | null, DataTrackDepacketizerDropError> {
let frame: DataTrackFrameInternal | null;
try {
frame = this.depacketizer.push(packet);
} catch (err) {
// In a future version, use this to maintain drop statistics.
// FIXME: is this a good idea?
log.warn(`Data frame depacketize error: ${err}`);
return null;
}
return frame;
}
/**
* Decrypt the frame's payload if E2EE is enabled for this track.
*/
private async decryptIfNeeded(
frame: DataTrackFrameInternal,
): Promise<DataTrackFrameInternal | null> {
const e2eeManager = this.e2eeManager;
if (!e2eeManager) {
return frame;
}
const e2ee = frame.extensions?.e2ee ?? null;
if (!e2ee) {
log.error('Missing E2EE meta');
return null;
}
let result;
try {
result = await e2eeManager.handleEncryptedData(
frame.payload,
e2ee.iv,
this.publisherIdentity,
e2ee.keyIndex,
);
} catch (err) {
log.error(`Error decrypting packet: ${err}`);
return null;
}
frame.payload = result.payload;
return frame;
}
}