// import dependencies
import {fetch} from '../utils/contextuals'
import Environment from './Environment';
import defaults from './defaults';
import {Environments} from '../constants';
/**
* Loads the available environments from the CDN, providing an array of {@link Environment} instances to interface with.
* The EnvironmentList uses a singleton instance to interface with the environments, unlike a {@link VersionList}.
*/
class EnvironmentList {
constructor () {
this._environments = defaults.map(env => {
return new Environment(env)
})
// load asynchronously
this._loaded = false
}
async _loadRemoteEnvironments ({cacheBust}) {
try {
const remoteEnvironments = await fetch(`https://cdn.medal.tv/public/environments.json${cacheBust ? `?t=${Date.now()}` : ''}`);
const remoteEnvironmentsJson = await remoteEnvironments.json();
this._environments = remoteEnvironmentsJson.map(env => {
return new Environment(env)
})
} catch (err) {
if (err.toString().includes('ENOTFOUND')) {
throw new Error(`Failed to retrieve environments list`)
} else {
throw err;
}
}
// map back to original format, but now sorted
return this._environments;
}
/**
* Load the remote environments list from the CDN if not already
* loaded and not forcing a reload.
* @returns {Promise<EnvironmentList>}
*/
async load (force = false, cacheBust = false) {
if (this._loaded && !force) {
return this;
}
await this._loadRemoteEnvironments({cacheBust});
this._loaded = true;
return this;
}
/**
* Get an {@link Environment} by value, e.g. 'production' or 'staging'.
* @param environmentValue
* @returns {Environment}
*/
getEnvironment (environmentValue) {
return this._environments.find(env => env.value === environmentValue)
}
/**
* Get the {@link Environment} by value, or fallback to the appropriate one
* by inferring the intended fallback based on the environment value.
*
* Any environments with 'sandbox' in the string will fallback to sandbox.
* Any environments with 'beta' in the string will fallback to early access.
* All other environments that no longer exist will fallback to production.
*
* If the 'requireModule' option is set to 'true', the environment will fallback
* if the target environment exists, but it doesn't include any builds for the
* specified module.
*
* @param environmentValue
* @param requireModule
* @returns {Environment}
*/
getEnvironmentOrFallback (environmentValue, {requireModule} = {}) {
let environment = this.getEnvironment(environmentValue);
if (!environment || (!!requireModule && !environment.includesModule(requireModule))) {
return this.getFallback(environmentValue);
}
return environment;
}
/**
* Get the fallback environment, this assumes that it is always a beta or dynamic
* environment that is falling back. Static environments won't have a fallback.
* @param environmentValue
* @returns {Environment}
*/
getFallback (environmentValue) {
const inferIsBeta = environmentValue.includes('-beta');
if (inferIsBeta || environmentValue === Environments.EARLY_ACCESS_CANDIDATE) {
return this.getEnvironment(Environments.EARLY_ACCESS);
} else if (environmentValue === Environments.PRODUCTION_CANDIDATE) {
return this.getEnvironment(Environments.PRODUCTION);
}
return this.getEnvironment(Environments.SANDBOX);
}
/**
* Return the {@link Environment} for the specified origin, if it exists.
* @param origin
* @returns {Environment}
*/
getEnvironmentByOrigin (origin) {
return this._environments.find(env => env.origin === origin);
}
/**
* Get the latest array of {@link Environment} instances (including any modifications).
* @returns {Environment[]}
*/
getEnvironments () {
if (!this._loaded) {
console.warn('EnvironmentList not loaded yet!');
}
return this._environments;
}
/**
* Return only public {@link Environment} instances.
* @returns {Environment[]}
*/
getPublicEnvironments () {
return this._environments.filter(env => env.public === true);
}
/**
* Return only static {@link Environment} instances.
* @returns {Environment[]}
*/
getStaticEnvironments () {
return this._environments.filter(env => env.static === true);
}
/**
* Return true if the environment list contains the specified environment (by string or by {@link Environment}
* @param {string|Environment} environment
* @returns {boolean}
*/
includes (environment) {
if (environment instanceof Environment) {
environment = environment.getValue();
}
return !!this._environments.find(env => env.value === environment);
}
/**
* Add a new {@link Environment} to the {@link EnvironmentList}.
* @param {Environment} environment
* @returns {EnvironmentList}
*/
add (environment) {
if (!this._loaded) {
throw new Error('List not loaded yet!')
}
if (this._environments.find(env => env.value === environment.value)) {
throw new Error('Environments list already contains environment: ' + environment.value)
}
// create a new array from the remote versions
this._environments.push(environment);
return this
}
/**
* Remove an {@link Environment} from the {@link EnvironmentList}.
* @param {Environment} environment
* @returns {EnvironmentList}
*/
remove (environment) {
if (environment.isStatic() && !environment.isExperimental()) {
throw new Error(`Cannot remove static non-experimental environment: ${environment.getValue()}`)
}
this._environments = this._environments.filter(env => env.getValue() !== environment.getValue());
return this;
}
/**
* Convert the {@link EnvironmentList} and it's {@link Environment} instances to a serializable object.
*/
toJson () {
return this._environments.map(environment => environment.toJson())
}
}
// we can instantiate this once to keep a singleton instance of environments
const environmentList = new EnvironmentList();
// compatibility with browser context
module.exports = environmentList
export default environmentList