@financial-times/o-ads
Version:
This package contains the core functionality used by the FT in providing ads across all of its sites. This includes ft.com, howtospendit.com, ftadviser.com and other specialist titles.
583 lines (456 loc) • 16.8 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.extend = extend;
exports.inSample = inSample;
exports.default = exports.getVersion = exports.cookie = exports.buildObjectFromArray = exports.iframeToSlotName = exports.getTimestamp = exports.getQueryParamByName = exports.getQueryString = exports.getLocation = exports.parseAttributeName = exports.dehyphenise = exports.getReferrer = exports.attach = exports.hash = exports.isElement = exports.isNonEmptyString = exports.isPlainObject = exports.isWindow = exports.isObject = exports.isStorage = exports.isFunction = exports.isString = exports.isArray = void 0;
var _events = require("./events.js");
var _metrics = require("./metrics.js");
var _messenger = _interopRequireDefault(require("./messenger.js"));
var _responsive = _interopRequireWildcard(require("./responsive.js"));
var _log = _interopRequireWildcard(require("./log.js"));
var _version = _interopRequireDefault(require("../version.js"));
function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function _getRequireWildcardCache() { return cache; }; return cache; }
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
* Utility methods for the advertising library.
* @author Origami Advertising, origami.advertising@ft.com
* @module utils
*/
var hop = Object.prototype.hasOwnProperty;
_responsive.default.getCurrent = _responsive.getCurrent;
_log.default.start = _log.start;
_log.default.end = _log.end;
_log.default.isOn = _log.isOn;
_log.default.info = _log.info;
_log.default.warn = _log.warn;
_log.default.error = _log.error;
_log.default.table = _log.table;
_log.default.attributeTable = _log.attributeTable;
/**
* Uses object prototype toString method to get at the type of object we are dealing,
* IE returns [object Object] for null and undefined so we need to filter those
* http://es5.github.com/#x15.2.4.2
* @private
* @param {object} Any javascript object
* @returns The type of the object e.g Array, String, Object
*/
function is(object) {
var type = Object.prototype.toString.call(object).match(/^\[object\s(.*)\]$/)[1];
if (object === null) {
return 'Null';
} else if (object === undefined) {
return 'Undefined';
} else {
return type;
}
}
/**
* Test if an object is an Array
* @param {object} obj The object to be tested
* @returns {boolean} true if the object is of type Array, otherwise false
*/
var isArray = function isArray(obj) {
return is(obj) === 'Array';
};
/**
* Test if an object is a String
* @param {object} obj The object to be tested
* @returns {boolean} true if the object is of type String, otherwise false
*/
exports.isArray = isArray;
var isString = function isString(obj) {
return is(obj) === 'String';
};
/**
* Test if an object is a Function
* @param {object} obj The object to be tested
* @returns {boolean} true if the object is of type Function, otherwise false
*/
exports.isString = isString;
var isFunction = function isFunction(obj) {
return is(obj) === 'Function';
};
/**
* Test if an object is a Storage object
* @param {object} obj The object to be tested
* @returns {boolean} true if the object is of type Storage, otherwise false
*/
exports.isFunction = isFunction;
var isStorage = function isStorage(obj) {
return is(obj) === 'Storage';
};
/**
* Test if an object is an Object
* @param {object} obj The object to be tested
* @returns {boolean} true if the object is of type Object, otherwise false
*/
exports.isStorage = isStorage;
var isObject = function isObject(obj) {
return is(obj) === 'Object';
};
/**
* Test if an object is the global window object
* @param {object} obj The object to be tested
* @returns {boolean} true if the object is the window obj, otherwise false
*/
exports.isObject = isObject;
var isWindow = function isWindow(obj) {
return obj && obj !== null && obj === window;
};
/**
* Test if an object inherits from any other objects, used in extend
* to protect against deep copies running out of memory and constructors
* losing there prototypes when cloned
* @param {object} obj The object to be tested
* @returns {boolean} true if the object is plain false otherwise
*/
exports.isWindow = isWindow;
var isPlainObject = function isPlainObject(obj) {
var hop = Object.prototype.hasOwnProperty; // Must be an Object.
// Because of IE, we also have to check the presence of the constructor property.
// Make sure that DOM nodes and window objects don't pass through, as well
if (!obj || !isObject(obj) || obj.nodeType || isWindow(obj)) {
return false;
}
try {
// Not own constructor property must be Object
if (obj.constructor && !hop.call(obj, 'constructor') && !hop.call(obj.constructor.prototype, 'isPrototypeOf')) {
return false;
}
} catch (e) {
/* istanbul ignore next */
// IE8,9 Will throw exceptions on certain host objects
return false;
} // Own properties are enumerated firstly, so to speed up,
// if last one is own, then all properties are own.
var key;
for (key in obj) {// empty
}
return key === undefined || hop.call(obj, key);
};
/**
* Test if an object is a string with a length
* @param {object} str The object to be tested
* @returns {boolean} true if the object is a string with a length greater than 0
*/
exports.isPlainObject = isPlainObject;
var isNonEmptyString = function isNonEmptyString(str) {
return isString(str) && Boolean(str.length);
};
exports.isNonEmptyString = isNonEmptyString;
var isElement = function isElement(element) {
return element && element.nodeType === 1 && element.tagName || false;
};
exports.isElement = isElement;
function extend() {
/* jshint forin: false */
/* when doing a deep copy we want to copy prototype properties */
var options;
var src;
var copy;
var copyIsArray;
var clone;
var target = arguments[0] || {};
var length = arguments.length;
var deep = false;
var i = 1; // Handle a deep copy situation
if (typeof target === 'boolean') {
deep = target;
target = arguments[1] || {}; // skip the boolean and the target
i = 2;
} // Handle case when target is a string or something (possible in deep copy)
/* istanbul ignore else */
if (typeof target !== 'object' && !isFunction(target)) {
target = {};
} // do nothing if only one argument is passed (or 2 for a deep copy)
/* istanbul ignore else */
if (length === i) {
return target;
}
for (; i < length; i++) {
// Only deal with non-null/undefined values
if ((options = arguments[i]) !== null) {
// Extend the base object
for (var name in options) {
/* istanbul ignore next */
if (options.hasOwnProperty(name)) {
src = target[name];
copy = options[name]; // Prevent never-ending loop
if (target === copy) {
continue;
} // Recurse if we're merging arrays
if (deep && copy && (isPlainObject(copy) || isArray(copy))) {
copyIsArray = isArray(copy);
if (copyIsArray) {
copyIsArray = false;
clone = src && isArray(src) ? src : [];
} else {
clone = src && isObject(src) ? src : {};
} // Never move original objects, clone them
target[name] = extend(deep, clone, copy); // Don't bring in undefined values
} else if (copy !== undefined) {
target[name] = copy;
}
}
}
}
} // Return the modified object
return target;
}
/**
* Create an object hash from a delimited string
* Beware all properties on the resulting object will have string values.
* @param {string} str The string to transform
* @param {string|regexp} delimiter The character that delimits each name/value pair
* @param {string} pairing The character that separates the name from the value
* @return {object}
*
*/
var hash = function hash(str, delimiter, pairing) {
var pair;
var value;
var hashObj = {};
if (str && str.split) {
str = str.split(delimiter);
for (var idx = 0, l = str.length; idx < l; idx += 1) {
value = str[idx];
pair = value.split(pairing);
if (pair.length > 1) {
hashObj[pair[0].trim()] = pair.slice(1).join(pairing);
}
}
}
return hashObj;
};
/**
* Takes a script URL as a string value, creates a new script element, sets the src and attaches to the page
* The async value of the script can be set by the seccond parameter, which is a boolean
* Note, we should use protocol-relative URL paths to ensure we don't run into http/https issues
* @param {string} scriptUrl The url to the script file to be added
* @param {boolean} async Set the async attribute on the script tag
* @param {function} callback A function to run when the script loads
* @param {function} errorcb A function to run if the script fails to load
* @returns {HTMLElement} the created script tag
*/
exports.hash = hash;
var attach = function attach(scriptUrl, async, callback, errorcb, autoRemove) {
var tag = document.createElement('script');
var node = document.getElementsByTagName('script')[0];
var hasRun = false;
function processCallback(callback) {
/* istanbul ignore else */
if (!hasRun) {
callback.call();
hasRun = true;
/* istanbul ignore else */
if (autoRemove) {
tag.parentElement.removeChild(tag);
}
}
}
tag.setAttribute('src', scriptUrl);
tag.setAttribute('o-ads', '');
/* istanbul ignore else */
if (async) {
tag.async = 'true';
}
/* istanbul ignore else */
if (isFunction(callback)) {
/* istanbul ignore if - legacy IE code, won't test */
if (hop.call(tag, 'onreadystatechange')) {
tag.onreadystatechange = function () {
if (tag.readyState === 'loaded') {
processCallback(callback);
}
};
} else {
tag.onload = function () {
processCallback(callback);
};
}
}
/* istanbul ignore else */
if (isFunction(errorcb)) {
tag.onerror = function () {
processCallback(errorcb);
};
} // Use insert before, append child has issues with script tags in some browsers.
node.parentNode.insertBefore(tag, node);
return tag;
};
/**
* return the current documents referrer or an empty string if non exists
* This method enables us to mock the referrer in our tests reliably and doesn't really serve any other purpose
* @returns {string} document.referrer
*/
/* istanbul ignore next - cannot reliably test value of referer */
exports.attach = attach;
var getReferrer = function getReferrer() {
return document.referrer || '';
};
/**
* Remove hyphens from a string and upper case the following letter
* @param {string} string the string to parse
* @returns {string}
*/
exports.getReferrer = getReferrer;
var dehyphenise = function dehyphenise(string) {
return string.replace(/(-)([a-z])/g, function (match, hyphen, letter) {
return letter.toUpperCase();
});
};
/**
* remove prefixes from o-ads data attributes and dehyphenise the name
* @param {string|} name the name of the attribute to parse
* @returns {string}
*/
exports.dehyphenise = dehyphenise;
var parseAttributeName = function parseAttributeName(attribute) {
var name = isString(attribute) ? attribute : attribute.name;
return dehyphenise(name.replace(/(data-)?o-ads-/, ''));
};
/**
* return the current documents url or an empty string if non exists
* This method enables us to mock the document location string in our tests reliably and doesn't really serve any other purpose
* @returns {string}
*/
/* istanbul ignore next - cannot reliably test value of location */
exports.parseAttributeName = parseAttributeName;
var getLocation = function getLocation() {
return document.location.href || '';
};
/**
* return the current documents search or an empty string if non exists
* also strips the initial ? from the search string for easier parsing
* This method enables us to mock the search string in our tests reliably and doesn't really serve any other purpose
* @returns {string}
*/
exports.getLocation = getLocation;
var getQueryString = function getQueryString() {
return document.location.search.substring(1) || '';
};
/**
* Get a query string parameter by name from a url
* @param name
* @param url
* @returns {string | null}
*/
exports.getQueryString = getQueryString;
var getQueryParamByName = function getQueryParamByName(name, url) {
url = url || window.location.href;
name = name.replace(/[[\]]/g, '\\$&');
var regex = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)');
var results = regex.exec(url);
if (!results) {
return null;
}
if (!results[2]) {
return '';
}
return decodeURIComponent(results[2].replace(/\+/g, ' '));
};
/**
* returns a timestamp of the current date/time in the format YYYYMMDDHHMMSS
* @returns {string}
*/
exports.getQueryParamByName = getQueryParamByName;
var getTimestamp = function getTimestamp() {
var now = new Date();
return [now.getFullYear(), "0".concat(now.getMonth() + 1).slice(-2), "0".concat(now.getDate()).slice(-2), "0".concat(now.getHours()).slice(-2), "0".concat(now.getMinutes()).slice(-2), "0".concat(now.getSeconds()).slice(-2)].join('');
};
/**
* Given the window object of an iframe this method returns the o-ads slot name
* that rendered the iframe, if the iframe was not rendered by o-ads this will
* return false
* @param {window} a window object
* @returns {String|Boolean}
*/
exports.getTimestamp = getTimestamp;
var iframeToSlotName = function iframeToSlotName(iframeWindow) {
// capture all iframes in the page in a live node list
var iframes = document.getElementsByTagName('iframe');
var slotName;
var node;
var i = iframes.length; // Figure out which iframe DOM node we have the window for
while (i--) {
/* istanbul ignore else */
if (iframes[i].contentWindow === iframeWindow) {
node = iframes[i];
break;
}
}
/* istanbul ignore else */
if (node) {
// find the closest parent with a data-o-ads-name attribute, that's our slot name
while (node.parentNode) {
slotName = node.getAttribute('data-o-ads-name');
/* istanbul ignore else */
if (slotName) {
return slotName;
}
node = node.parentNode;
}
}
return false;
};
exports.iframeToSlotName = iframeToSlotName;
var buildObjectFromArray = function buildObjectFromArray(targetObject) {
return targetObject.reduce((prev, data) => {
prev[data.key] = data.value;
return prev;
}, {});
};
exports.buildObjectFromArray = buildObjectFromArray;
var cookie = function cookie(name) {
var val = document.cookie.match("(^|;)\\s*".concat(name, "\\s*=\\s*([^;]+)"));
return val ? val.pop() : null;
};
exports.cookie = cookie;
var getVersion = () => _version.default || 'UNKNOWN';
exports.getVersion = getVersion;
var metricsSampleThreshold = Math.random();
function inSample(sampleSize) {
return typeof sampleSize === 'undefined' || sampleSize > metricsSampleThreshold;
}
var _default = {
on: _events.on,
off: _events.off,
once: _events.once,
broadcast: _events.broadcast,
messenger: _messenger.default,
responsive: _responsive.default,
log: _log.default,
isArray,
isString,
isFunction,
isStorage,
isObject,
isWindow,
isPlainObject,
isNonEmptyString,
isElement,
extend,
hash,
attach,
getReferrer,
dehyphenise,
parseAttributeName,
getLocation,
getQueryString,
getQueryParamByName,
getTimestamp,
iframeToSlotName,
buildObjectFromArray,
cookie,
getVersion,
setupMetrics: _metrics.setupMetrics,
clearPerfMarks: _metrics.clearPerfMarks,
markPageChange: _metrics.markPageChange,
inSample,
perfMark: _events.perfMark,
buildPerfmarkSuffix: _events.buildPerfmarkSuffix
};
exports.default = _default;