@ampproject/toolbox-runtime-version
Advanced tools
Comparing version 2.2.0 to 2.3.0
@@ -22,29 +22,48 @@ /** | ||
const RUNTIME_METADATA_PATH = '/rtv/metadata'; | ||
const VERSION_TXT_PATH = '/version.txt'; | ||
/** | ||
* Queries https://cdn.ampproject.org/rtv/metadata for the latest AMP runtime version. Uses a | ||
* @typedef {number} ReleaseType | ||
*/ | ||
/** | ||
* Release type "enumeration" | ||
* | ||
* @enum {ReleaseType} | ||
*/ | ||
const ReleaseType = { | ||
canary: 0, | ||
prod: 1, | ||
lts: 2, | ||
}; | ||
/** | ||
* Queries <host>/rtv/metadata for the latest AMP runtime version. If version is not available from | ||
* this endpoint, falls back to <host>/version.txt and manually prepends config code. Uses a | ||
* stale-while-revalidate caching strategy to avoid refreshing the version. | ||
* | ||
* More details: https://cdn.ampproject.org/rtv/metadata returns the following metadata: | ||
* /rtv/metadata endpoint details: | ||
* | ||
* <pre> | ||
* { | ||
* "ampRuntimeVersion": "CURRENT_PROD", | ||
* "ampCssUrl": "https://cdn.ampproject.org/rtv/CURRENT_PROD/v0.css", | ||
* "canaryPercentage": "0.1", | ||
* "diversions": [ | ||
* "CURRENT_OPTIN", | ||
* "CURRENT_1%", | ||
* "CURRENT_CONTROL" | ||
* ] | ||
* } | ||
* </pre> | ||
* "ampRuntimeVersion": "CURRENT_PROD", | ||
* "ampCssUrl": "https://cdn.ampproject.org/rtv/CURRENT_PROD/v0.css", | ||
* "canaryPercentage": "0.1", | ||
* "diversions": [ | ||
* "CURRENT_OPTIN", | ||
* "CURRENT_1%", | ||
* "CURRENT_CONTROL" | ||
* ], | ||
* "ltsRuntimeVersion": "CURRENT_LTS", | ||
* "ltsCssUrl": "https://cdn.ampproject.org/rtv/CURRENT_LTS/v0.css" | ||
* } | ||
* </pre> | ||
* | ||
* where: | ||
* where: | ||
* | ||
* <ul> | ||
* <li> CURRENT_OPTIN: is when you go to https://cdn.ampproject.org/experiments.html and toggle "dev-channel". It's the earliest possible time to get new code.</li> | ||
* <li> CURRENT_1%: 1% is the same code as opt-in that we're now comfortable releasing to 1% of the population.</li> | ||
* <li> CURRENT_CONTROL is the same thing as production, but with a different URL. This is to compare experiments against, since prod's immutable caching would affect metrics.</li> | ||
* </ul> | ||
* <ul> | ||
* <li> CURRENT_OPTIN: is when you go to https://cdn.ampproject.org/experiments.html and toggle "dev-channel". It's the earliest possible time to get new code.</li> | ||
* <li> CURRENT_1%: 1% is the same code as opt-in that we're now comfortable releasing to 1% of the population.</li> | ||
* <li> CURRENT_CONTROL is the same thing as production, but with a different URL. This is to compare experiments against, since prod's immutable caching would affect metrics.</li> | ||
* </ul> | ||
*/ | ||
@@ -60,4 +79,4 @@ class RuntimeVersion { | ||
* | ||
* @param {Object} options - the options. | ||
* @param {bool} options.canary - true if canary should be returned. | ||
* @param {object} options - the options. | ||
* @param {boolean} options.canary - true if canary should be returned. | ||
* @param {string} options.ampUrlPrefix - the domain & path to an AMP runtime. | ||
@@ -67,39 +86,149 @@ * @returns {Promise<string>} a promise containing the current version. | ||
async currentVersion(options = {}) { | ||
let runtimeMetaUrl = AMP_CACHE_HOST + RUNTIME_METADATA_PATH; | ||
if (options.ampUrlPrefix) { | ||
const customMetaUrl = options.ampUrlPrefix.replace(/\/$/, '') + RUNTIME_METADATA_PATH; | ||
// Check whether ampUrlPrefix is absolute since relative paths are allowed | ||
// by optimizer | ||
if (this.isAbsoluteUrl_(customMetaUrl)) { | ||
runtimeMetaUrl = customMetaUrl; | ||
} else { | ||
log.warn( | ||
'ampUrlPrefix is not an absolute URL. Falling back to https://cdn.ampproject.org.' | ||
); | ||
} | ||
if (options.ampUrlPrefix && !this.isAbsoluteUrl_(options.ampUrlPrefix)) { | ||
throw new Error('host must be an absolute URL'); | ||
} | ||
const response = await this.fetch_(runtimeMetaUrl); | ||
const data = await response.json(); | ||
let version; | ||
if (options.canary && options.lts) { | ||
throw new Error('lts flag is not compatible with canary flag'); | ||
} | ||
let releaseType = ReleaseType.prod; | ||
if (options.canary) { | ||
version = data.diversions[0]; | ||
log.debug('canary version', version); | ||
} else { | ||
version = data.ampRuntimeVersion; | ||
log.debug('prod version', version); | ||
releaseType = ReleaseType.canary; | ||
} else if (options.lts) { | ||
releaseType = ReleaseType.lts; | ||
} | ||
return this.padVersionString_(version); | ||
const host = options.ampUrlPrefix ? options.ampUrlPrefix.replace(/\/$/, '') : AMP_CACHE_HOST; | ||
let rtv = await this.getVersionFromRuntimeMetadata_(host, releaseType); | ||
if (!rtv && releaseType === ReleaseType.prod) { | ||
rtv = await this.getVersionFromVersionTxt_(host, releaseType); | ||
} | ||
return rtv; | ||
} | ||
/* PRIVATE */ | ||
padVersionString_(version) { | ||
return this.pad_(version, 15, 0); | ||
/** | ||
* Get runtime version from <host>/rtv/metadata | ||
* | ||
* @param {string} host - runtime host. | ||
* @param {ReleaseType} releaseType - release type. | ||
* @returns {Promise<string>} a promise containing the runtime version. | ||
*/ | ||
async getVersionFromRuntimeMetadata_(host, releaseType) { | ||
const runtimeMetaUrl = host + RUNTIME_METADATA_PATH; | ||
log.debug(`Fetching version from ${runtimeMetaUrl}`); | ||
let response; | ||
try { | ||
response = await this.fetch_(runtimeMetaUrl); | ||
} catch (ex) { | ||
// Avoid exception to give fallback mechanism getVersionFromVersionTxt_() | ||
// a chance to lookup version, and to gracefully return 'undefined' if no | ||
// version is ultimately found. | ||
} | ||
if (!response || !response.ok) { | ||
log.debug('RTV metadata endpoint did not respond with a successful status code'); | ||
return; | ||
} | ||
let data; | ||
try { | ||
data = await response.json(); | ||
} catch (ex) { | ||
log.debug('RTV metadata JSON malformed'); | ||
return; | ||
} | ||
let rtv; | ||
if (releaseType === ReleaseType.canary) { | ||
if ( | ||
Array.isArray(data.diversions) && | ||
data.diversions[0] && | ||
data.diversions[0].startsWith(this.getRtvConfigCode_(releaseType)) | ||
) { | ||
rtv = data.diversions[0]; | ||
} | ||
if (!rtv) { | ||
log.debug('RTV metadata JSON malformed, canary version not in diversions array'); | ||
} | ||
} else if (releaseType === ReleaseType.lts) { | ||
rtv = data.ltsRuntimeVersion; | ||
if (!rtv) { | ||
log.debug('RTV metadata JSON malformed, lts version not in ltsRuntimeVersion'); | ||
} | ||
} else if (releaseType === ReleaseType.prod) { | ||
rtv = data.ampRuntimeVersion; | ||
if (!rtv) { | ||
log.debug('RTV metadata JSON malformed, production version not in ampRuntimeVersion'); | ||
} | ||
} | ||
return rtv; | ||
} | ||
pad_(n, width, z) { | ||
z = z || '0'; | ||
n = String(n); | ||
return n.length >= width ? n : new Array(width - n.length + 1).join(z) + n; | ||
/** | ||
* Get runtime version from <host>/version.txt, manually prepending | ||
* production release code '01'. This method cannot be used to detect | ||
* canary or lts releases. | ||
* | ||
* @param {string} host - runtime host. | ||
* @param {ReleaseType} releaseType - release type. | ||
* @returns {Promise<string>} a promise containing the runtime version. | ||
*/ | ||
async getVersionFromVersionTxt_(host, releaseType) { | ||
if (releaseType !== ReleaseType.prod) { | ||
log.debug(`version.txt lookup only supported for prod releases`); | ||
return; | ||
} | ||
let versionTxtUrl = host + VERSION_TXT_PATH; | ||
log.debug(`Falling back to ${versionTxtUrl}`); | ||
let response; | ||
try { | ||
response = await this.fetch_(versionTxtUrl); | ||
} catch (ex) { | ||
// Prefer gracefully returning 'undefined' version to throwing. | ||
} | ||
if (!response || !response.ok) { | ||
log.debug('version.txt endpoint did not respond with a successful status code'); | ||
return; | ||
} | ||
let version; | ||
try { | ||
version = (await response.text()).trim(); | ||
if (version !== encodeURIComponent(version)) { | ||
throw new Error(); | ||
} | ||
} catch (ex) { | ||
log.debug('Version string malformed, not URL compatible'); | ||
return; | ||
} | ||
return this.getRtvConfigCode_(releaseType) + version; | ||
} | ||
/** | ||
* Get config code corresponding to release type | ||
* | ||
* @param {ReleaseType} releaseType - release type. | ||
* @returns {string} | ||
*/ | ||
getRtvConfigCode_(releaseType) { | ||
if (releaseType === ReleaseType.canary) { | ||
return '00'; | ||
} | ||
return '01'; | ||
} | ||
/** | ||
* Determine whether a URL is absolute. | ||
* | ||
* @param {string} url - URL to test. | ||
* @returns {boolean} | ||
*/ | ||
isAbsoluteUrl_(url) { | ||
@@ -106,0 +235,0 @@ try { |
{ | ||
"name": "@ampproject/toolbox-runtime-version", | ||
"version": "2.2.0", | ||
"version": "2.3.0", | ||
"description": "AMP Runtime versions", | ||
@@ -21,3 +21,3 @@ "main": "index.js", | ||
"dependencies": { | ||
"@ampproject/toolbox-core": "^2.2.0" | ||
"@ampproject/toolbox-core": "^2.3.0" | ||
}, | ||
@@ -28,3 +28,3 @@ "bugs": { | ||
"homepage": "https://github.com/ampproject/amp-toolbox/tree/master/packages/runtime-version", | ||
"gitHead": "e2bea7ac7d4cb6a57d196e124ee8a5f818123a02" | ||
"gitHead": "bb7eaa6c720044e84f01c6406f5f0805dc637923" | ||
} |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
21444
235
2