UNPKG

@fakes/media-devices

Version:

A interactive fake implementation of MediaDevices interface in the browser for testing

186 lines (159 loc) 5.8 kB
export { MediaDeviceDescription } from './MediaDeviceDescription' export { anyMicrophone, anyCamera, anyDevice, anySpeaker } from './DeviceMother' export { PermissionPrompt, PermissionPromptAction, RequestedMediaInput } from './UserConsentTracker' import { Context } from './context' import { MediaDeviceDescription } from './MediaDeviceDescription' import { MediaDevicesFake } from './MediaDevicesFake' import { NotImplemented, ThrowingNotImplemented } from './not-implemented' import { OpenMediaTracks } from './OpenMediaTracks' import { PermissionsFake } from './permissions/PermissionsFake' import { DefaultReporter, NoopReporter, Reporter } from './reporter' import { allConstraintsFalse, existingDevice, noDeviceWithDeviceId, passUndefined, requestedDeviceTypeNotAttached, scenarios as all, } from './Scenarios' import { PermissionPrompt, UserConsent, UserConsentTracker } from './UserConsentTracker' export type LogLevel = 'off' | 'all' export type PermissionSetup = Partial<UserConsent> export type InitialSetup = PermissionSetup & { attachedDevices?: MediaDeviceDescription[] logLevel?: LogLevel } export const stillHaveToAskForDeviceAccess = (additional: PermissionSetup = {}): PermissionSetup => { return { microphone: 'prompt', camera: 'prompt', ...additional, } } export const allAccessGranted = (additional: PermissionSetup = {}): PermissionSetup => { return { microphone: 'granted', camera: 'granted', ...additional, } } export const allAccessDenied = (additional: PermissionSetup = {}): PermissionSetup => { return { microphone: 'denied', camera: 'denied', ...additional, } } export interface MediaDevicesControl { readonly mediaDevices: MediaDevices readonly permissions: Permissions installInto(target: Window): void uninstall(): void attach(...toAdd: MediaDeviceDescription[] | [MediaDeviceDescription[]]): void remove(toRemove: MediaDeviceDescription | 'all'): void deviceAccessPrompt(): Promise<PermissionPrompt> setPermissionFor( ...permissionSetup: [type: 'camera' | 'microphone', state: PermissionState] | [PermissionSetup] ): void } export const forgeMediaDevices = (initial: InitialSetup = {}): MediaDevicesControl => { const camera = initial.camera ?? 'prompt' const microphone = initial.microphone ?? 'prompt' const logLevel = initial.logLevel ?? 'off' const reporter: Reporter = logLevel === 'off' ? new NoopReporter() : new DefaultReporter() const notImplemented: NotImplemented = new ThrowingNotImplemented(reporter) const context: Context = { notImplemented, reporter, } const consentTracker = new UserConsentTracker(context, { camera, microphone }) const openMediaTracks = new OpenMediaTracks() const mediaDevicesFake = new MediaDevicesFake(context, consentTracker, openMediaTracks) const permissionsFake = new PermissionsFake(context, consentTracker) const attachedDevices = initial.attachedDevices ?? [] attachedDevices.forEach((device) => mediaDevicesFake.attach(device)) const _setPermissionFor = (type: 'camera' | 'microphone', state: PermissionState): void => { consentTracker.setPermissionFor(type, state) if (state === 'granted') { return } openMediaTracks.allFor(type).forEach((fake) => { fake.permissionRevoked() }) } return new (class implements MediaDevicesControl { private _target: Window | null = null private _mediaDevicesBackup: MediaDevices | null = null private _permissionBackup: Permissions | null = null get mediaDevices(): MediaDevices { return mediaDevicesFake } get permissions(): Permissions { return permissionsFake } installInto(target: Window) { this._target = target this._mediaDevicesBackup = target.navigator.mediaDevices this._permissionBackup = target.navigator.permissions Object.assign(this._target.navigator, { mediaDevices: mediaDevicesFake, permissions: permissionsFake }) } uninstall() { if (this._target === null) { // nothing to restore return } Object.assign(this._target.navigator, { mediaDevices: this._mediaDevicesBackup, permissions: this._permissionBackup, }) this._target = null this._mediaDevicesBackup = null this._permissionBackup = null } attach(...toAdd: MediaDeviceDescription[] | [MediaDeviceDescription[]]): void { let array: MediaDeviceDescription[] = toAdd as MediaDeviceDescription[] if (toAdd.length === 1) { const singleElement = toAdd[0] if (Array.isArray(singleElement)) { array = singleElement } } array.forEach((it) => mediaDevicesFake.attach(it)) } remove(toRemove: MediaDeviceDescription | 'all'): void { if ('all' === toRemove) { mediaDevicesFake.noDevicesAttached() return } mediaDevicesFake.remove(toRemove) } deviceAccessPrompt(): Promise<PermissionPrompt> { return consentTracker.deviceAccessPrompt() } setPermissionFor( ...permissionSetup: [type: 'camera' | 'microphone', state: PermissionState] | [PermissionSetup] ): void { if (permissionSetup.length === 2) { _setPermissionFor(...permissionSetup) return } const [{ camera, microphone }] = permissionSetup if (camera) { _setPermissionFor('camera', camera) } if (microphone) { _setPermissionFor('microphone', microphone) } } })() } // todo testrig should be moved here export { MediaStreamCheckResult, Scenario } from './Scenarios' export const scenarios = { all, passUndefined, existingDevice, allConstraintsFalse, requestedDeviceTypeNotAttached, noDeviceWithDeviceId, }