video-ad-sdk
Version:
VAST/VPAID SDK that allows video ads to be played on top of any player
133 lines (132 loc) • 5.2 kB
JavaScript
import { parseXml } from '../xml';
import { isVastErrorCode, ErrorCode } from '../tracker';
import { getWrapperOptions, getFirstAd, getVASTAdTagURI, hasAdPod, isInline, isWrapper } from '../vastSelectors';
import { fetch } from './helpers/fetch';
import { VastError } from './helpers/vastError';
import { markAdAsRequested } from './helpers/adUtils';
const validateChain = (vastChain, { wrapperLimit = 5 }) => {
if (vastChain.length > wrapperLimit) {
const error = new VastError('Wrapper Limit reached');
error.code = ErrorCode.VAST_TOO_MANY_REDIRECTS;
throw error;
}
};
const fetchAdXML = async (adTag, options) => {
var _a;
let response;
try {
response = await fetch(adTag, options);
const XML = await response.text();
return { response, XML };
}
catch (error) {
error.code = ErrorCode.VAST_NONLINEAR_LOADING_FAILED;
(_a = error.response) !== null && _a !== void 0 ? _a : (error.response = response);
throw error;
}
};
const parseVastXml = (xml) => {
try {
return parseXml(xml);
}
catch (error) {
error.code = ErrorCode.VAST_XML_PARSING_ERROR;
throw error;
}
};
const getAd = (parsedXML) => {
try {
const ad = getFirstAd(parsedXML);
if (ad) {
markAdAsRequested(ad);
return ad;
}
throw new Error('No Ad');
}
catch (error) {
error.code = ErrorCode.VAST_NO_ADS_AFTER_WRAPPER;
throw error;
}
};
const validateResponse = ({ ad, parsedXML }, { allowMultipleAds = true, followAdditionalWrappers = true }) => {
if (!isWrapper(ad) && !isInline(ad)) {
const error = new VastError('Invalid VAST, ad contains neither Wrapper nor Inline');
error.code = ErrorCode.VAST_SCHEMA_VALIDATION_ERROR;
throw error;
}
if (hasAdPod(parsedXML) && !allowMultipleAds) {
const error = new VastError('Multiple ads are not allowed');
error.code = ErrorCode.VAST_UNEXPECTED_MEDIA_FILE;
throw error;
}
if (isWrapper(ad) && !followAdditionalWrappers) {
const error = new VastError('To follow additional wrappers is not allowed');
error.code = ErrorCode.VAST_UNEXPECTED_AD_TYPE;
throw error;
}
};
const getOptions = (vastChain, options) => {
const [parentAd] = vastChain;
const parentAdIsWrapper = Boolean(parentAd) && parentAd.ad && isWrapper(parentAd.ad);
const wrapperOptions = parentAdIsWrapper && parentAd.ad ? getWrapperOptions(parentAd.ad) : {};
return Object.assign(Object.assign({}, wrapperOptions), options);
};
/**
* Request the ad using the passed ad tag and returns an array with the {@link VastResponse} needed to get an inline ad.
*
* @param adTag The VAST ad tag request url.
* @param options Options Map. The allowed properties are:
* @param vastChain Optional vastChain with the previous VAST responses.
* @returns Returns a Promise that will resolve with a VastChain with the newest VAST response at the beginning of the array.
* If the {@link VastChain} had an error. The first VAST response of the array will contain an error and an errorCode entry.
*/
export const requestAd = async (adTag, options, vastChain = []) => {
var _a;
const vastAdResponse = {
requestTag: adTag
};
let epoch;
let timeout;
try {
const resultOptions = getOptions(vastChain, options);
validateChain(vastChain, resultOptions);
let fetchPromise = fetchAdXML(adTag, resultOptions);
if (typeof resultOptions.timeout === 'number') {
timeout = resultOptions.timeout;
epoch = Date.now();
fetchPromise = Promise.race([
fetchPromise,
new Promise((_resolve, reject) => {
setTimeout(() => {
const error = new VastError('RequestAd timeout');
error.code = ErrorCode.VAST_LOAD_TIMEOUT;
reject(error);
}, timeout);
})
]);
}
const { response, XML } = await fetchPromise;
vastAdResponse.response = response;
vastAdResponse.XML = XML;
vastAdResponse.parsedXML = parseVastXml(vastAdResponse.XML);
vastAdResponse.ad = getAd(vastAdResponse.parsedXML);
validateResponse(vastAdResponse, resultOptions);
if (isWrapper(vastAdResponse.ad)) {
if (epoch && timeout) {
timeout -= Date.now() - epoch;
}
return requestAd(getVASTAdTagURI(vastAdResponse.ad), Object.assign(Object.assign({}, resultOptions), { timeout }), [vastAdResponse, ...vastChain]);
}
return [vastAdResponse, ...vastChain];
}
catch (error) {
/* istanbul ignore if */
if (!isVastErrorCode(error.code)) {
error.code = ErrorCode.UNKNOWN_ERROR;
}
vastAdResponse.errorCode = error.code;
vastAdResponse.error = error;
(_a = vastAdResponse.response) !== null && _a !== void 0 ? _a : (vastAdResponse.response = error.response);
return [vastAdResponse, ...vastChain];
}
};