eme-encryption-scheme-polyfill
Version:
A polyfill for the encryptionScheme field in EME
754 lines (675 loc) • 27.4 kB
JavaScript
/*!
* @license
* EME Encryption Scheme Polyfill
* Copyright 2019 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
// This special header is retained in minified bundle, and only adds ~120 bytes.
/**
* A polyfill to add support for EncryptionScheme queries in EME.
*
* Because this polyfill can't know what schemes the UA or CDM actually support,
* it assumes support for the historically-supported schemes of each well-known
* key system.
*
* In source form, this is compatible with the Closure Compiler, CommonJS, and
* AMD module formats. It can also be directly included via a script tag.
*
* The minified bundle is a standalone module compatible with the CommonJS and
* AMD module formats, and can also be directly included via a script tag.
*
* @see https://wicg.github.io/encrypted-media-encryption-scheme/
* @see https://github.com/w3c/encrypted-media/pull/457
* @export
*/
class EmeEncryptionSchemePolyfill {
/**
* Installs the polyfill. To avoid the possibility of extra user prompts,
* this will shim EME so long as it exists, without checking support for
* encryptionScheme upfront. The support check will happen on-demand the
* first time EME is used.
*
* @export
*/
static install() {
if (EmeEncryptionSchemePolyfill.originalRMKSA_ ||
navigator['emeEncryptionSchemePolyfilled']) {
console.debug('EmeEncryptionSchemePolyfill: Already installed.');
return;
}
if (!navigator.requestMediaKeySystemAccess ||
!MediaKeySystemAccess.prototype.getConfiguration) {
console.debug('EmeEncryptionSchemePolyfill: EME not found');
// No EME.
return;
}
// Save the original.
EmeEncryptionSchemePolyfill.originalRMKSA_ =
navigator.requestMediaKeySystemAccess;
// Patch in a method which will check for support on the first call.
console.debug('EmeEncryptionSchemePolyfill: ' +
'Waiting to detect encryptionScheme support.');
navigator.requestMediaKeySystemAccess =
EmeEncryptionSchemePolyfill.probeRMKSA_;
// Mark EME as polyfilled. This keeps us from running into conflicts
// between multiple versions of this (compiled Shaka lib vs
// uncompiled source).
navigator['emeEncryptionSchemePolyfilled'] = true;
}
/**
* A shim for navigator.requestMediaKeySystemAccess to check for
* encryptionScheme support. Only used until we know if the browser has
* native support for the encryptionScheme field.
*
* @this {Navigator}
* @param {string} keySystem The key system ID.
* @param {!Array.<!MediaKeySystemConfiguration>} supportedConfigurations An
* array of supported configurations the application can use.
* @return {!Promise.<!MediaKeySystemAccess>} A Promise to a
* MediaKeySystemAccess instance.
* @private
*/
static async probeRMKSA_(keySystem, supportedConfigurations) {
console.assert(this == navigator,
'bad "this" for requestMediaKeySystemAccess');
// Call the original version. If the call succeeds, we look at the result
// to decide if the encryptionScheme field is supported or not.
const mediaKeySystemAccess =
await EmeEncryptionSchemePolyfill.originalRMKSA_.call(
this, keySystem, supportedConfigurations);
if (hasEncryptionScheme(mediaKeySystemAccess)) {
// The browser supports the encryptionScheme field!
// No need for a patch. Revert back to the original implementation.
console.debug('EmeEncryptionSchemePolyfill: ' +
'Native encryptionScheme support found.');
// eslint-disable-next-line require-atomic-updates
navigator.requestMediaKeySystemAccess =
EmeEncryptionSchemePolyfill.originalRMKSA_;
// Return the results, which are completely valid.
return mediaKeySystemAccess;
}
// If we land here, the browser does _not_ support the encryptionScheme
// field. So we install another patch to check the encryptionScheme field
// in future calls.
console.debug('EmeEncryptionSchemePolyfill: ' +
'No native encryptionScheme support found. '+
'Patching encryptionScheme support.');
// eslint-disable-next-line require-atomic-updates
navigator.requestMediaKeySystemAccess =
EmeEncryptionSchemePolyfill.polyfillRMKSA_;
// The results we have may not be valid. Run the query again through our
// polyfill.
return EmeEncryptionSchemePolyfill.polyfillRMKSA_.call(
this, keySystem, supportedConfigurations);
}
/**
* A polyfill for navigator.requestMediaKeySystemAccess to handle the
* encryptionScheme field in browsers that don't support it. It uses the
* user-agent string to guess what encryption schemes are supported, then
* those guesses are used to filter videoCapabilities and audioCapabilities
* and reject unsupported schemes.
*
* @this {Navigator}
* @param {string} keySystem The key system ID.
* @param {!Array.<!MediaKeySystemConfiguration>} supportedConfigurations An
* array of supported configurations the application can use.
* @return {!Promise.<!MediaKeySystemAccess>} A Promise to a
* MediaKeySystemAccess instance.
* @private
*/
static async polyfillRMKSA_(keySystem, supportedConfigurations) {
console.assert(this == navigator,
'bad "this" for requestMediaKeySystemAccess');
const supportedScheme = guessSupportedScheme(keySystem);
// Filter the application's configurations based on our guess of what
// encryption scheme is supported.
const filteredSupportedConfigurations = [];
for (const configuration of supportedConfigurations) {
const filteredVideoCapabilities =
EmeEncryptionSchemePolyfill.filterCapabilities_(
configuration.videoCapabilities, supportedScheme);
const filteredAudioCapabilities =
EmeEncryptionSchemePolyfill.filterCapabilities_(
configuration.audioCapabilities, supportedScheme);
if (configuration.videoCapabilities &&
configuration.videoCapabilities.length &&
!filteredVideoCapabilities.length) {
// We eliminated all of the video capabilities, so this configuration
// is unusable.
} else if (configuration.audioCapabilities &&
configuration.audioCapabilities.length &&
!filteredAudioCapabilities.length) {
// We eliminated all of the audio capabilities, so this configuration
// is unusable.
} else {
// Recreate a clone of the configuration and modify that. This way, we
// don't modify the application-provided config objects.
/** @type {!MediaKeySystemConfiguration} */
const clonedConfiguration = Object.assign({}, configuration);
clonedConfiguration.videoCapabilities = filteredVideoCapabilities;
clonedConfiguration.audioCapabilities = filteredAudioCapabilities;
filteredSupportedConfigurations.push(clonedConfiguration);
}
}
if (!filteredSupportedConfigurations.length) {
// None of the application's configurations passed our encryptionScheme
// filters, so this request fails.
// As spec'd, this should be a DOMException, but there is not a public
// constructor for this in all browsers. This should be close enough for
// most applications.
const unsupportedError = new Error(
'Unsupported keySystem or supportedConfigurations.');
unsupportedError.name = 'NotSupportedError';
unsupportedError['code'] = DOMException.NOT_SUPPORTED_ERR;
throw unsupportedError;
}
// At this point, we have some filtered configurations that we think could
// work. Pass this subset to the native version of RMKSA.
const mediaKeySystemAccess =
await EmeEncryptionSchemePolyfill.originalRMKSA_.call(
this, keySystem, filteredSupportedConfigurations);
// Wrap the MKSA object in ours to provide the missing field in the
// returned configuration.
let videoScheme = null;
let audioScheme = null;
if (filteredSupportedConfigurations[0]) {
if (filteredSupportedConfigurations[0].videoCapabilities) {
videoScheme = filteredSupportedConfigurations[0]
.videoCapabilities[0].encryptionScheme;
}
if (filteredSupportedConfigurations[0].audioCapabilities) {
audioScheme = filteredSupportedConfigurations[0]
.audioCapabilities[0].encryptionScheme;
}
}
return new EmeEncryptionSchemePolyfillMediaKeySystemAccess(
mediaKeySystemAccess, videoScheme, audioScheme);
}
/**
* Filters out capabilities that don't match the supported encryption scheme.
*
* @param {!Array.<!MediaKeySystemMediaCapability>|undefined} capabilities
* An array of capabilities, or null or undefined.
* @param {?string} supportedScheme The encryption scheme that we think is
* supported by the key system.
* @return {!Array.<!MediaKeySystemMediaCapability>|undefined} A filtered
* array of capabilities based on |supportedScheme|. May be undefined if
* the input was undefined.
* @private
*/
static filterCapabilities_(capabilities, supportedScheme) {
if (!capabilities) {
return capabilities;
}
return capabilities.filter((capability) => {
return checkSupportedScheme(
capability['encryptionScheme'], supportedScheme);
});
}
}
/**
* A polyfill to add support for EncryptionScheme queries in MediaCapabilities.
*
* Because this polyfill can't know what schemes the UA or CDM actually support,
* it assumes support for the historically-supported schemes of each well-known
* key system.
*
* In source form, this is compatible with the Closure Compiler, CommonJS, and
* AMD module formats. It can also be directly included via a script tag.
*
* The minified bundle is a standalone module compatible with the CommonJS and
* AMD module formats, and can also be directly included via a script tag.
*
* @see https://wicg.github.io/encrypted-media-encryption-scheme/
* @see https://github.com/w3c/encrypted-media/pull/457
* @export
*/
class McEncryptionSchemePolyfill {
/**
* Installs the polyfill. To avoid the possibility of extra user prompts,
* this will shim MC so long as it exists, without checking support for
* encryptionScheme upfront. The support check will happen on-demand the
* first time MC is used.
*
* @export
*/
static install() {
if (McEncryptionSchemePolyfill.originalDecodingInfo_ ||
navigator['mediaCapabilitiesEncryptionSchemePolyfilled']) {
console.debug('McEncryptionSchemePolyfill: Already installed.');
return;
}
if (!navigator.mediaCapabilities) {
console.debug('McEncryptionSchemePolyfill: MediaCapabilities not found');
// No MediaCapabilities.
return;
}
// Save the original.
McEncryptionSchemePolyfill.originalDecodingInfo_ =
navigator.mediaCapabilities.decodingInfo;
// Patch in a method which will check for support on the first call.
console.debug('McEncryptionSchemePolyfill: ' +
'Waiting to detect encryptionScheme support.');
navigator.mediaCapabilities.decodingInfo =
McEncryptionSchemePolyfill.probeDecodingInfo_;
// Mark MediaCapabilities as polyfilled. This keeps us from running into
// conflicts between multiple versions of this (compiled Shaka lib vs
// uncompiled source).
navigator['mediaCapabilitiesEncryptionSchemePolyfilled'] = true;
}
/**
* A shim for mediaCapabilities.decodingInfo to check for encryptionScheme
* support. Only used until we know if the browser has native support for the
* encryptionScheme field.
*
* @this {MediaCapabilities}
* @param {!MediaDecodingConfiguration} requestedConfiguration The requested
* decoding configuration.
* @return {!Promise.<!MediaCapabilitiesDecodingInfo>} A Promise to a result
* describing the capabilities of the browser in the request configuration.
* @private
*/
static async probeDecodingInfo_(requestedConfiguration) {
console.assert(this == navigator.mediaCapabilities,
'bad "this" for decodingInfo');
// Call the original version. If the call succeeds, we look at the result
// to decide if the encryptionScheme field is supported or not.
const capabilities =
await McEncryptionSchemePolyfill.originalDecodingInfo_.call(
this, requestedConfiguration);
// If the config is not supported, we don't need to try anything else.
if (!capabilities.supported) {
return capabilities;
}
if (!requestedConfiguration.keySystemConfiguration) {
// This was not a query regarding encrypted content. The results are
// valid, but won't tell us anything about native support for
// encryptionScheme. Just return the results.
return capabilities;
}
const mediaKeySystemAccess = capabilities.keySystemAccess;
if (mediaKeySystemAccess && hasEncryptionScheme(mediaKeySystemAccess)) {
// The browser supports the encryptionScheme field!
// No need for a patch. Revert back to the original implementation.
console.debug('McEncryptionSchemePolyfill: ' +
'Native encryptionScheme support found.');
// eslint-disable-next-line require-atomic-updates
navigator.mediaCapabilities.decodingInfo =
McEncryptionSchemePolyfill.originalDecodingInfo_;
// Return the results, which are completely valid.
return capabilities;
}
// If we land here, either the browser does not support the
// encryptionScheme field, or the browser does not support EME-related
// fields in MCap _at all_.
// First, install a patch to check the mediaKeySystemAccess or
// encryptionScheme field in future calls.
console.debug('McEncryptionSchemePolyfill: ' +
'No native encryptionScheme support found. '+
'Patching encryptionScheme support.');
// eslint-disable-next-line require-atomic-updates
navigator.mediaCapabilities.decodingInfo =
McEncryptionSchemePolyfill.polyfillDecodingInfo_;
// Second, if _none_ of the EME-related fields of MCap are supported, fill
// them in now before returning the results.
if (!mediaKeySystemAccess) {
capabilities.keySystemAccess =
await McEncryptionSchemePolyfill.getMediaKeySystemAccess_(
requestedConfiguration);
return capabilities;
}
// If we land here, it's only the encryption scheme field that is missing.
// The results we have may not be valid, since they didn't account for
// encryption scheme. Run the query again through our polyfill.
return McEncryptionSchemePolyfill.polyfillDecodingInfo_.call(
this, requestedConfiguration);
}
/**
* A polyfill for mediaCapabilities.decodingInfo to handle the
* encryptionScheme field in browsers that don't support it. It uses the
* user-agent string to guess what encryption schemes are supported, then
* those guesses are used to reject unsupported schemes.
*
* @this {MediaCapabilities}
* @param {!MediaDecodingConfiguration} requestedConfiguration The requested
* decoding configuration.
* @return {!Promise.<!MediaCapabilitiesDecodingInfo>} A Promise to a result
* describing the capabilities of the browser in the request configuration.
* @private
*/
static async polyfillDecodingInfo_(requestedConfiguration) {
console.assert(this == navigator.mediaCapabilities,
'bad "this" for decodingInfo');
let videoScheme = null;
let audioScheme = null;
if (requestedConfiguration.keySystemConfiguration) {
const keySystemConfig = requestedConfiguration.keySystemConfiguration;
const keySystem = keySystemConfig.keySystem;
audioScheme = keySystemConfig.audio &&
keySystemConfig.audio.encryptionScheme;
videoScheme = keySystemConfig.video &&
keySystemConfig.video.encryptionScheme;
const supportedScheme = guessSupportedScheme(keySystem);
const notSupportedResult = {
powerEfficient: false,
smooth: false,
supported: false,
keySystemAccess: null,
configuration: requestedConfiguration,
};
if (!checkSupportedScheme(audioScheme, supportedScheme)) {
return notSupportedResult;
}
if (!checkSupportedScheme(videoScheme, supportedScheme)) {
return notSupportedResult;
}
}
// At this point, either it's unencrypted or we assume the encryption scheme
// is supported. So delegate to the original decodingInfo() method.
const capabilities =
await McEncryptionSchemePolyfill.originalDecodingInfo_.call(
this, requestedConfiguration);
if (capabilities.keySystemAccess) {
// If the result is supported and encrypted, this will be a
// MediaKeySystemAccess instance. Wrap the MKSA object in ours to provide
// the missing field in the returned configuration.
capabilities.keySystemAccess =
new EmeEncryptionSchemePolyfillMediaKeySystemAccess(
capabilities.keySystemAccess, videoScheme, audioScheme);
} else if (requestedConfiguration.keySystemConfiguration) {
// If the result is supported and the content is encrypted, we should have
// a MediaKeySystemAccess instance as part of the result. If we land
// here, the browser doesn't support the EME-related fields of MCap.
capabilities.keySystemAccess =
await McEncryptionSchemePolyfill.getMediaKeySystemAccess_(
requestedConfiguration);
}
return capabilities;
}
/**
* Call navigator.requestMediaKeySystemAccess to get the MediaKeySystemAccess
* information.
*
* @param {!MediaDecodingConfiguration} requestedConfiguration The requested
* decoding configuration.
* @return {!Promise.<!MediaKeySystemAccess>} A Promise to a
* MediaKeySystemAccess instance.
* @private
*/
static async getMediaKeySystemAccess_(requestedConfiguration) {
const mediaKeySystemConfig =
McEncryptionSchemePolyfill.convertToMediaKeySystemConfig_(
requestedConfiguration);
const keySystemAccess =
await navigator.requestMediaKeySystemAccess(
requestedConfiguration.keySystemConfiguration.keySystem,
[mediaKeySystemConfig]);
return keySystemAccess;
}
/**
* Convert the MediaDecodingConfiguration object to a
* MediaKeySystemConfiguration object.
*
* @param {!MediaDecodingConfiguration} decodingConfig The decoding
* configuration.
* @return {!MediaKeySystemConfiguration} The converted MediaKeys
* configuration.
*/
static convertToMediaKeySystemConfig_(decodingConfig) {
const mediaCapKeySystemConfig = decodingConfig.keySystemConfiguration;
const audioCapabilities = [];
const videoCapabilities = [];
if (mediaCapKeySystemConfig.audio) {
const capability = {
robustness: mediaCapKeySystemConfig.audio.robustness || '',
contentType: decodingConfig.audio.contentType,
encryptionScheme: mediaCapKeySystemConfig.audio.encryptionScheme,
};
audioCapabilities.push(capability);
}
if (mediaCapKeySystemConfig.video) {
const capability = {
robustness: mediaCapKeySystemConfig.video.robustness || '',
contentType: decodingConfig.video.contentType,
encryptionScheme: mediaCapKeySystemConfig.video.encryptionScheme,
};
videoCapabilities.push(capability);
}
const initDataTypes = mediaCapKeySystemConfig.initDataType ?
[mediaCapKeySystemConfig.initDataType] : [];
/** @type {!MediaKeySystemConfiguration} */
const mediaKeySystemConfig = {
initDataTypes: initDataTypes,
distinctiveIdentifier: mediaCapKeySystemConfig.distinctiveIdentifier,
persistentState: mediaCapKeySystemConfig.persistentState,
sessionTypes: mediaCapKeySystemConfig.sessionTypes,
};
// Only add the audio video capablities if they have valid data.
// Otherwise the query will fail.
if (audioCapabilities.length) {
mediaKeySystemConfig.audioCapabilities = audioCapabilities;
}
if (videoCapabilities.length) {
mediaKeySystemConfig.videoCapabilities = videoCapabilities;
}
return mediaKeySystemConfig;
}
}
/**
* A wrapper around MediaKeySystemAccess that adds encryptionScheme
* fields to the configuration, to emulate what a browser with native support
* for this field would do.
*
* @see https://github.com/w3c/encrypted-media/pull/457
* @see https://github.com/WICG/encrypted-media-encryption-scheme/issues/13
* @implements {MediaKeySystemAccess}
*/
class EmeEncryptionSchemePolyfillMediaKeySystemAccess {
/**
* @param {!MediaKeySystemAccess} mksa A native MediaKeySystemAccess instance
* to wrap.
* @param {?string|undefined} videoScheme The encryption scheme to add to the
* configuration for video.
* @param {?string|undefined} audioScheme The encryption scheme to add to the
* configuration for audio.
*/
constructor(mksa, videoScheme, audioScheme) {
/**
* @const {!MediaKeySystemAccess}
* @private
*/
this.mksa_ = mksa;
/**
* @const {?string}
* @private
*/
this.videoScheme_ = videoScheme || null;
/**
* @const {?string}
* @private
*/
this.audioScheme_ = audioScheme || null;
/** @const {string} */
this.keySystem = mksa.keySystem;
}
/**
* @override
* @return {!MediaKeySystemConfiguration} A MediaKeys config with
* encryptionScheme fields added
*/
getConfiguration() {
// A browser which supports the encryptionScheme field would always return
// that field in the resulting configuration. So here, we emulate that.
const configuration = this.mksa_.getConfiguration();
if (configuration.videoCapabilities) {
for (const capability of configuration.videoCapabilities) {
capability['encryptionScheme'] = this.videoScheme_;
}
}
if (configuration.audioCapabilities) {
for (const capability of configuration.audioCapabilities) {
capability['encryptionScheme'] = this.audioScheme_;
}
}
return configuration;
}
/**
* @override
* @return {!Promise<!MediaKeys>} A passthru of the native MediaKeys object
*/
createMediaKeys() {
return this.mksa_.createMediaKeys();
}
}
/**
* Guess the supported encryption scheme for the key system.
*
* @param {string} keySystem The key system ID.
* @return {?string} A guess at the encryption scheme this key system
* supports.
*/
function guessSupportedScheme(keySystem) {
if (keySystem.startsWith('com.widevine')) {
return 'cenc';
} else if (keySystem.startsWith('com.microsoft')) {
return 'cenc';
} else if (keySystem.startsWith('com.chromecast')) {
return 'cenc';
} else if (keySystem.startsWith('com.adobe')) {
return 'cenc';
} else if (keySystem.startsWith('org.w3')) {
return 'cenc';
} else if (keySystem.startsWith('com.apple')) {
return 'cbcs';
} else if (keySystem.startsWith('com.huawei')) {
return 'cenc';
}
// We don't have this key system in our map!
// Log a warning. The only way the request will succeed now is if the
// app doesn't specify an encryption scheme in their own configs.
// Use bracket notation to keep this from being stripped from the build.
console['warn']('EmeEncryptionSchemePolyfill: Unknown key system:',
keySystem, 'Please contribute!');
return null;
}
/**
* @param {?MediaKeySystemAccess} mediaKeySystemAccess A native
* MediaKeySystemAccess instance from the browser.
* @return {boolean} True if browser natively supports encryptionScheme.
*/
function hasEncryptionScheme(mediaKeySystemAccess) {
const configuration = mediaKeySystemAccess.getConfiguration();
// It doesn't matter which capability we look at. For this check, they
// should all produce the same result.
const firstVideoCapability =
configuration.videoCapabilities && configuration.videoCapabilities[0];
const firstAudioCapability =
configuration.audioCapabilities && configuration.audioCapabilities[0];
const firstCapability = firstVideoCapability || firstAudioCapability;
// If supported by the browser, the encryptionScheme field must appear in
// the returned configuration, regardless of whether or not it was
// specified in the supportedConfigurations given by the application.
if (firstCapability && firstCapability['encryptionScheme'] !== undefined) {
return true;
}
return false;
}
/**
* @param {(string|undefined|null)} scheme Encryption scheme to check
* @param {?string} supportedScheme A guess at the encryption scheme this
* supports.
* @return {boolean} True if the scheme is compatible.
*/
function checkSupportedScheme(scheme, supportedScheme) {
if (!scheme) {
// Not encrypted = always supported
return true;
}
if (scheme == supportedScheme) {
// The assumed-supported legacy scheme for this platform.
return true;
}
if (scheme == 'cbcs' || scheme == 'cbcs-1-9') {
if (EncryptionSchemePolyfills.isRecentFirefox ||
EncryptionSchemePolyfills.isRecentWebOS ||
EncryptionSchemePolyfills.isChromecast) {
// Firefox >= 100 supports CBCS, but doesn't support queries yet.
// WebOS >= 6 supports CBCS, but doesn't support queries yet.
// Older Chromecast devices are assumed to support CBCS as well.
return true;
}
}
return false;
}
/**
* The original requestMediaKeySystemAccess, before we patched it.
*
* @type {
* function(this:Navigator,
* string,
* !Array.<!MediaKeySystemConfiguration>
* ):!Promise.<!MediaKeySystemAccess>
* }
* @private
*/
EmeEncryptionSchemePolyfill.originalRMKSA_;
/**
* The original decodingInfo, before we patched it.
*
* @type {
* function(this:MediaCapabilities,
* !MediaDecodingConfiguration
* ):!Promise.<!MediaCapabilitiesDecodingInfo>
* }
* @private
*/
McEncryptionSchemePolyfill.originalDecodingInfo_;
/**
* A single entry point for both polyfills (EME & MC).
*
* @export
*/
class EncryptionSchemePolyfills {
/**
* Installs both polyfills (EME & MC).
*
* @export
*/
static install() {
EmeEncryptionSchemePolyfill.install();
McEncryptionSchemePolyfill.install();
}
}
/**
* @const {boolean}
*/
EncryptionSchemePolyfills.isChromecast =
navigator.userAgent.includes('CrKey');
/**
* @const {boolean}
*/
EncryptionSchemePolyfills.isRecentFirefox =
parseInt(navigator.userAgent.split('Firefox/').pop(), 10) >= 100;
/**
* @const {boolean}
*/
EncryptionSchemePolyfills.isRecentWebOS = (() => {
const userAgent = navigator.userAgent || '';
const isWebOS = userAgent.includes('Web0S');
if (!isWebOS) {
return false;
}
const chromeVersionMatch = userAgent.match(/Chrome\/(\d+)/);
if (!chromeVersionMatch) {
return false;
}
const chromeVersion = parseInt(chromeVersionMatch[1], 10);
return chromeVersion >= 79;
})();
// Support for CommonJS and AMD module formats.
/** @suppress {undefinedVars} */
(() => {
if (typeof module !== 'undefined' && module.exports) {
module.exports = EncryptionSchemePolyfills;
}
})();