UNPKG

react-gpt-for-prebid

Version:

A react display ad component using Google Publisher Tag & Prebid

587 lines (514 loc) 22.4 kB
Object.defineProperty(exports, "__esModule", { value: true }); exports.AdManager = exports.APIToCallBeforeServiceEnabled = exports.pubadsAPI = undefined; var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); exports.createManager = createManager; var _eventemitter = require("eventemitter3"); var _eventemitter2 = _interopRequireDefault(_eventemitter); var _throttleDebounce = require("throttle-debounce"); var _invariant = require("invariant"); var _invariant2 = _interopRequireDefault(_invariant); var _exenv = require("exenv"); var _Events = require("./Events"); var _Events2 = _interopRequireDefault(_Events); var _isInViewport2 = require("./utils/isInViewport"); var _isInViewport3 = _interopRequireDefault(_isInViewport2); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } // based on https://developers.google.com/doubleclick-gpt/reference?hl=en var pubadsAPI = exports.pubadsAPI = ["enableAsyncRendering", "enableSingleRequest", "enableSyncRendering", "disableInitialLoad", "collapseEmptyDivs", "enableVideoAds", "set", "get", "getAttributeKeys", "setTargeting", "clearTargeting", "setCategoryExclusion", "clearCategoryExclusions", "setCentering", "setCookieOptions", "setLocation", "setPublisherProvidedId", "setTagForChildDirectedTreatment", "clearTagForChildDirectedTreatment", "setVideoContent", "setForceSafeFrame"]; var APIToCallBeforeServiceEnabled = exports.APIToCallBeforeServiceEnabled = ["enableAsyncRendering", "enableSingleRequest", "enableSyncRendering", "disableInitialLoad", "collapseEmptyDivs", "setCentering"]; var AdManager = exports.AdManager = function (_EventEmitter) { _inherits(AdManager, _EventEmitter); function AdManager() { var config = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; _classCallCheck(this, AdManager); var _this = _possibleConstructorReturn(this, (AdManager.__proto__ || Object.getPrototypeOf(AdManager)).call(this, config)); _this._adCnt = 0; _this._initialRender = true; _this._syncCorrelator = false; _this._testMode = false; _this._foldCheck = (0, _throttleDebounce.throttle)(20, function (event) { var instances = _this.getMountedInstances(); instances.forEach(function (instance) { if (instance.getRenderWhenViewable()) { instance.foldCheck(event); } }); if (_this.testMode) { _this._getTimer(); } }); _this._handleMediaQueryChange = function (event) { if (_this._syncCorrelator) { _this.refresh(); return; } // IE returns `event.media` value differently, need to use regex to evaluate. // eslint-disable-next-line wrap-regex var res = /min-width:\s?(\d+)px/.exec(event.media); var viewportWidth = res && res[1]; if (viewportWidth && _this._mqls[viewportWidth]) { _this._mqls[viewportWidth].listeners.forEach(function (instance) { instance.refresh(); if (instance.props.onMediaQueryChange) { instance.props.onMediaQueryChange(event); } }); } }; _this.render = (0, _throttleDebounce.debounce)(4, function () { if (!_this._initialRender) { return; } var checkPubadsReady = function checkPubadsReady(cb) { if (_this.pubadsReady) { cb(); } else { setTimeout(checkPubadsReady, 50, cb); } }; var instances = _this.getMountedInstances(); var hasPubAdsService = false; var dummyAdSlot = void 0; // Define all the slots instances.forEach(function (instance) { if (!instance.notInViewport()) { instance.defineSlot(); var adSlot = instance.adSlot; if (adSlot && adSlot.hasOwnProperty("getServices")) { var services = adSlot.getServices(); if (!hasPubAdsService) { hasPubAdsService = services.filter(function (service) { return !!service.enableAsyncRendering; }).length > 0; } } } }); // if none of the ad slots uses pubads service, create dummy slot to use pubads service. if (!hasPubAdsService) { dummyAdSlot = _this.googletag.defineSlot("/", []); dummyAdSlot.addService(_this.googletag.pubads()); } // Call pubads API which needs to be called before service is enabled. _this._processPubadsQueue(); // Enable service _this.googletag.enableServices(); // After the service is enabled, check periodically until `pubadsReady` flag returns true before proceeding the rest. checkPubadsReady(function () { // destroy dummy ad slot if exists. if (dummyAdSlot) { _this.googletag.destroySlots([dummyAdSlot]); } // Call the rest of the pubads API that's in the queue. _this._processPubadsQueue(); // listen for GPT events _this._listen(); // client should be able to set any page-level setting within the event handler. _this._isReady = true; _this.emit(_Events2.default.READY, _this.googletag); // Call display instances.forEach(function (instance) { if (!instance.notInViewport()) { instance.display(); } }); _this.emit(_Events2.default.RENDER, _this.googletag); _this._initialRender = false; }); }); _this.renderAll = (0, _throttleDebounce.debounce)(4, function () { if (!_this.apiReady) { return false; } // first instance updates correlator value and re-render each ad var instances = _this.getMountedInstances(); instances.forEach(function (instance, i) { if (i === 0) { _this.updateCorrelator(); } instance.forceUpdate(); }); return true; }); if (config.test) { _this.testMode = config; } return _this; } _createClass(AdManager, [{ key: "_processPubadsQueue", value: function _processPubadsQueue() { var _this2 = this; if (this._pubadsProxyQueue) { Object.keys(this._pubadsProxyQueue).forEach(function (method) { if (_this2.googletag && !_this2.googletag.pubadsReady && APIToCallBeforeServiceEnabled.indexOf(method) > -1 || _this2.pubadsReady) { _this2._pubadsProxyQueue[method].forEach(function (params) { return _this2.pubadsProxy(params); }); delete _this2._pubadsProxyQueue[method]; } }); if (!Object.keys(this._pubadsProxyQueue).length) { this._pubadsProxyQueue = null; } } } }, { key: "_callPubads", value: function _callPubads(_ref) { var method = _ref.method, args = _ref.args, resolve = _ref.resolve, reject = _ref.reject; if (typeof this.googletag.pubads()[method] !== "function") { reject(new Error("googletag.pubads does not support " + method + ", please update pubadsAPI")); } else { try { var _googletag$pubads; var result = (_googletag$pubads = this.googletag.pubads())[method].apply(_googletag$pubads, _toConsumableArray(args)); resolve(result); } catch (err) { reject(err); } } } }, { key: "_toggleListener", value: function _toggleListener(add) { var _this3 = this; ["scroll", "resize"].forEach(function (eventName) { window[add ? "addEventListener" : "removeEventListener"](eventName, _this3._foldCheck); }); } }, { key: "_getTimer", value: function _getTimer() { return Date.now(); } }, { key: "_listen", value: function _listen() { var _this4 = this; if (!this._listening) { [_Events2.default.SLOT_RENDER_ENDED, _Events2.default.IMPRESSION_VIEWABLE, _Events2.default.SLOT_VISIBILITY_CHANGED, _Events2.default.SLOT_LOADED].forEach(function (eventType) { ["pubads", "content", "companionAds"].forEach(function (service) { // there is no API to remove listeners. _this4.googletag[service]().addEventListener(eventType, _this4._onEvent.bind(_this4, eventType)); }); }); this._listening = true; } } }, { key: "_onEvent", value: function _onEvent(eventType, event) { // fire to the global listeners if (this.listeners(eventType, true)) { this.emit(eventType, event); } // call event handler props var instances = this.getMountedInstances(); var slot = event.slot; var funcName = "on" + eventType.charAt(0).toUpperCase() + eventType.substr(1); var instance = instances.filter(function (inst) { return slot === inst.adSlot; })[0]; if (instance && instance.props[funcName]) { instance.props[funcName](event); } } }, { key: "syncCorrelator", value: function syncCorrelator() { var value = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true; this._syncCorrelator = value; } }, { key: "generateDivId", value: function generateDivId() { return "bling-" + ++this._adCnt; } }, { key: "getMountedInstances", value: function getMountedInstances() { if (!this.mountedInstances) { this.mountedInstances = []; } return this.mountedInstances; } }, { key: "addInstance", value: function addInstance(instance) { var instances = this.getMountedInstances(); var index = instances.indexOf(instance); if (index === -1) { // The first instance starts listening for the event. if (instances.length === 0) { this._toggleListener(true); } this.addMQListener(instance, instance.props); instances.push(instance); } } }, { key: "removeInstance", value: function removeInstance(instance) { var instances = this.getMountedInstances(); var index = instances.indexOf(instance); if (index >= 0) { instances.splice(index, 1); // The last instance removes listening for the event. if (instances.length === 0) { this._toggleListener(false); } this.removeMQListener(instance, instance.props); } } }, { key: "addMQListener", value: function addMQListener(instance, _ref2) { var _this5 = this; var sizeMapping = _ref2.sizeMapping; if (!sizeMapping || !Array.isArray(sizeMapping)) { return; } sizeMapping.forEach(function (size) { var viewportWidth = size.viewport && size.viewport[0]; if (viewportWidth !== undefined) { if (!_this5._mqls) { _this5._mqls = {}; } if (!_this5._mqls[viewportWidth]) { var mql = window.matchMedia("(min-width: " + viewportWidth + "px)"); mql.addListener(_this5._handleMediaQueryChange); _this5._mqls[viewportWidth] = { mql: mql, listeners: [] }; } if (_this5._mqls[viewportWidth].listeners.indexOf(instance) === -1) { _this5._mqls[viewportWidth].listeners.push(instance); } } }); } }, { key: "removeMQListener", value: function removeMQListener(instance) { var _this6 = this; if (!this._mqls) { return; } Object.keys(this._mqls).forEach(function (key) { var index = _this6._mqls[key].listeners.indexOf(instance); if (index > -1) { _this6._mqls[key].listeners.splice(index, 1); } if (_this6._mqls[key].listeners.length === 0) { _this6._mqls[key].mql.removeListener(_this6._handleMediaQueryChange); delete _this6._mqls[key]; } }); } }, { key: "isInViewport", value: function isInViewport() { return _isInViewport3.default.apply(undefined, arguments); } /** * Refreshes all the ads in the page with a new correlator value. * * @param {Array} slots An array of ad slots. * @param {Object} options You can pass `changeCorrelator` flag. * @static */ }, { key: "refresh", value: function refresh(slots, options) { if (!this.pubadsReady) { return false; } // gpt already debounces refresh this.googletag.pubads().refresh(slots, options); return true; } }, { key: "clear", value: function clear(slots) { if (!this.pubadsReady) { return false; } this.googletag.pubads().clear(slots); return true; } /** * Re-render(not refresh) all the ads in the page and the first ad will update the correlator value. * Updating correlator value ensures competitive exclusion. * * @method renderAll * @static */ }, { key: "getGPTVersion", value: function getGPTVersion() { if (!this.apiReady) { return false; } return this.googletag.getVersion(); } }, { key: "getPubadsVersion", value: function getPubadsVersion() { if (!this.pubadsReady) { return false; } return this.googletag.pubads().getVersion(); } }, { key: "updateCorrelator", value: function updateCorrelator() { if (!this.pubadsReady) { return false; } this.googletag.pubads().updateCorrelator(); return true; } }, { key: "load", value: function load(url) { var _this7 = this; return this._loadPromise || (this._loadPromise = new Promise(function (resolve, reject) { // test mode can't be enabled in production mode if (_this7.testMode) { resolve(_this7.googletag); return; } if (!_exenv.canUseDOM) { reject(new Error("DOM not available")); return; } if (!url) { reject(new Error("url is missing")); return; } var onLoad = function onLoad() { if (window.googletag) { _this7._googletag = window.googletag; // make sure API is ready for use. _this7.googletag.cmd.push(function () { _this7._isLoaded = true; resolve(_this7.googletag); }); } else { reject(new Error("window.googletag is not available")); } }; //console.log("GPT Status : " + window.googletag) if (window.googletag && window.googletag.apiReady) { //console.log("Already loaded") onLoad(); } else { var script = document.createElement("script"); script.async = true; script.onload = onLoad; script.onerror = function () { reject(new Error("failed to load script")); }; script.src = url; document.head.appendChild(script); } })); } }, { key: "pubadsProxy", value: function pubadsProxy(_ref3) { var _this8 = this; var method = _ref3.method, _ref3$args = _ref3.args, args = _ref3$args === undefined ? [] : _ref3$args, resolve = _ref3.resolve, reject = _ref3.reject; if (!resolve) { // there are couple pubads API which doesn't provide getter methods for later use, // so remember them here. if (APIToCallBeforeServiceEnabled.indexOf(method) > -1) { this["_" + method] = args && args.length && args[0] || true; } return new Promise(function (resolve2, reject2) { var params = { method: method, args: args, resolve: resolve2, reject: reject2 }; if (!_this8.pubadsReady) { if (!_this8._pubadsProxyQueue) { _this8._pubadsProxyQueue = {}; } if (!_this8._pubadsProxyQueue[method]) { _this8._pubadsProxyQueue[method] = []; } _this8._pubadsProxyQueue[method].push(params); } else { _this8._callPubads(params); } }); } this._callPubads({ method: method, args: args, resolve: resolve, reject: reject }); return Promise.resolve(); } }, { key: "googletag", get: function get() { return this._googletag; } }, { key: "isLoaded", get: function get() { return !!this._isLoaded; } }, { key: "isReady", get: function get() { return !!this._isReady; } }, { key: "apiReady", get: function get() { return this.googletag && this.googletag.apiReady; } }, { key: "pubadsReady", get: function get() { return this.googletag && this.googletag.pubadsReady; } }, { key: "testMode", get: function get() { return this._testMode; }, set: function set(config) { if (process.env.NODE_ENV === "production") { return; } var test = config.test, GPTMock = config.GPTMock; this._isLoaded = true; this._testMode = !!test; if (test) { (0, _invariant2.default)(test && GPTMock, "Must provide GPTMock to enable testMode. config{GPTMock}"); this._googletag = new GPTMock(config); } } }]); return AdManager; }(_eventemitter2.default); function createManager(config) { return new AdManager(config); }