UNPKG

livekit-client

Version:

JavaScript/TypeScript client SDK for LiveKit

117 lines (103 loc) 3.89 kB
import { EventEmitter } from 'events'; import type TypedEventEmitter from 'typed-emitter'; import log from '../logger'; import { KEY_PROVIDER_DEFAULTS } from './constants'; import { type KeyProviderCallbacks, KeyProviderEvent } from './events'; import type { KeyInfo, KeyProviderOptions, RatchetResult } from './types'; import { createKeyMaterialFromBuffer, createKeyMaterialFromString } from './utils'; /** * @experimental */ export class BaseKeyProvider extends (EventEmitter as new () => TypedEventEmitter<KeyProviderCallbacks>) { private keyInfoMap: Map<string, KeyInfo>; private readonly options: KeyProviderOptions; private latestManuallySetKeyIndex = 0; constructor(options: Partial<KeyProviderOptions> = {}) { super(); this.keyInfoMap = new Map(); this.options = { ...KEY_PROVIDER_DEFAULTS, ...options }; this.on(KeyProviderEvent.KeyRatcheted, this.onKeyRatcheted); } /** * callback to invoke once a key has been set for a participant * @param key * @param participantIdentity * @param keyIndex */ protected onSetEncryptionKey(key: CryptoKey, participantIdentity?: string, keyIndex?: number) { const keyInfo: KeyInfo = { key, participantIdentity, keyIndex }; if (!this.options.sharedKey && !participantIdentity) { throw new Error( 'participant identity needs to be passed for encryption key if sharedKey option is false', ); } this.keyInfoMap.set(`${participantIdentity ?? 'shared'}-${keyIndex ?? 0}`, keyInfo); if (keyIndex !== undefined) { this.latestManuallySetKeyIndex = keyIndex; } this.emit(KeyProviderEvent.SetKey, keyInfo, keyIndex !== undefined); } /** * Callback being invoked after a key has been ratcheted. * Can happen when: * - A decryption failure occurs and the key is auto-ratcheted * - A ratchet request is sent (see {@link ratchetKey()}) * @param ratchetResult Contains the ratcheted chain key (exportable to other participants) and the derived new key material. * @param participantId * @param keyIndex */ protected onKeyRatcheted = ( ratchetResult: RatchetResult, participantId?: string, keyIndex?: number, ) => { log.debug('key ratcheted event received', { ratchetResult, participantId, keyIndex }); }; getKeys() { return Array.from(this.keyInfoMap.values()); } getLatestManuallySetKeyIndex() { return this.latestManuallySetKeyIndex; } getOptions() { return this.options; } ratchetKey(participantIdentity?: string, keyIndex?: number) { this.emit(KeyProviderEvent.RatchetRequest, participantIdentity, keyIndex); } } /** * A basic KeyProvider implementation intended for a single shared * passphrase between all participants * @experimental */ export class ExternalE2EEKeyProvider extends BaseKeyProvider { ratchetInterval: number | undefined; constructor(options: Partial<Omit<KeyProviderOptions, 'sharedKey'>> = {}) { const opts: Partial<KeyProviderOptions> = { ...options, sharedKey: true, // for a shared key provider failing to decrypt for a specific participant // should not mark the key as invalid, so we accept wrong keys forever // and won't try to auto-ratchet ratchetWindowSize: 0, failureTolerance: -1, }; super(opts); } /** * Accepts a passphrase that's used to create the crypto keys. * When passing in a string, PBKDF2 is used. (recommended for maximum compatibility across SDKs) * When passing in an ArrayBuffer of cryptographically random numbers, HKDF is used. * * Note: Not all client SDKS support HKDF. * @param key */ async setKey(key: string | ArrayBuffer) { const derivedKey = typeof key === 'string' ? await createKeyMaterialFromString(key) : await createKeyMaterialFromBuffer(key); this.onSetEncryptionKey(derivedKey); } }