diff --git a/dist/ethicalads.js b/dist/ethicalads.js index da18b22..df5fb70 100644 --- a/dist/ethicalads.js +++ b/dist/ethicalads.js @@ -95,7 +95,7 @@ var ethicalads = /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"Placement\", function() { return Placement; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"check_dependencies\", function() { return check_dependencies; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"load_placements\", function() { return load_placements; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"unload_placements\", function() { return unload_placements; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"set_verbosity\", function() { return set_verbosity; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"wait\", function() { return wait; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"load\", function() { return load; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"reload\", function() { return reload; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"uplifted\", function() { return uplifted; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"detectedKeywords\", function() { return detectedKeywords; });\n/* harmony import */ var verge__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! verge */ \"./node_modules/verge/verge.js\");\n/* harmony import */ var verge__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(verge__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var _styles_scss__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./styles.scss */ \"./styles.scss\");\n/* harmony import */ var _styles_scss__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_styles_scss__WEBPACK_IMPORTED_MODULE_1__);\nfunction _typeof(o) { \"@babel/helpers - typeof\"; return _typeof = \"function\" == typeof Symbol && \"symbol\" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && \"function\" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? \"symbol\" : typeof o; }, _typeof(o); }\nfunction _callSuper(t, o, e) { return o = _getPrototypeOf(o), _possibleConstructorReturn(t, _isNativeReflectConstruct() ? Reflect.construct(o, e || [], _getPrototypeOf(t).constructor) : o.apply(t, e)); }\nfunction _possibleConstructorReturn(t, e) { if (e && (\"object\" == _typeof(e) || \"function\" == typeof e)) return e; if (void 0 !== e) throw new TypeError(\"Derived constructors may only return object or undefined\"); return _assertThisInitialized(t); }\nfunction _assertThisInitialized(e) { if (void 0 === e) throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\"); return e; }\nfunction _inherits(t, e) { if (\"function\" != typeof e && null !== e) throw new TypeError(\"Super expression must either be null or a function\"); t.prototype = Object.create(e && e.prototype, { constructor: { value: t, writable: !0, configurable: !0 } }), Object.defineProperty(t, \"prototype\", { writable: !1 }), e && _setPrototypeOf(t, e); }\nfunction _wrapNativeSuper(t) { var r = \"function\" == typeof Map ? new Map() : void 0; return _wrapNativeSuper = function _wrapNativeSuper(t) { if (null === t || !_isNativeFunction(t)) return t; if (\"function\" != typeof t) throw new TypeError(\"Super expression must either be null or a function\"); if (void 0 !== r) { if (r.has(t)) return r.get(t); r.set(t, Wrapper); } function Wrapper() { return _construct(t, arguments, _getPrototypeOf(this).constructor); } return Wrapper.prototype = Object.create(t.prototype, { constructor: { value: Wrapper, enumerable: !1, writable: !0, configurable: !0 } }), _setPrototypeOf(Wrapper, t); }, _wrapNativeSuper(t); }\nfunction _construct(t, e, r) { if (_isNativeReflectConstruct()) return Reflect.construct.apply(null, arguments); var o = [null]; o.push.apply(o, e); var p = new (t.bind.apply(t, o))(); return r && _setPrototypeOf(p, r.prototype), p; }\nfunction _isNativeReflectConstruct() { try { var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); } catch (t) {} return (_isNativeReflectConstruct = function _isNativeReflectConstruct() { return !!t; })(); }\nfunction _isNativeFunction(t) { try { return -1 !== Function.toString.call(t).indexOf(\"[native code]\"); } catch (n) { return \"function\" == typeof t; } }\nfunction _setPrototypeOf(t, e) { return _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function (t, e) { return t.__proto__ = e, t; }, _setPrototypeOf(t, e); }\nfunction _getPrototypeOf(t) { return _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf.bind() : function (t) { return t.__proto__ || Object.getPrototypeOf(t); }, _getPrototypeOf(t); }\nfunction _classCallCheck(a, n) { if (!(a instanceof n)) throw new TypeError(\"Cannot call a class as a function\"); }\nfunction _defineProperties(e, r) { for (var t = 0; t < r.length; t++) { var o = r[t]; o.enumerable = o.enumerable || !1, o.configurable = !0, \"value\" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o); } }\nfunction _createClass(e, r, t) { return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, \"prototype\", { writable: !1 }), e; }\nfunction _toPropertyKey(t) { var i = _toPrimitive(t, \"string\"); return \"symbol\" == _typeof(i) ? i : i + \"\"; }\nfunction _toPrimitive(t, r) { if (\"object\" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || \"default\"); if (\"object\" != _typeof(i)) return i; throw new TypeError(\"@@toPrimitive must return a primitive value.\"); } return (\"string\" === r ? String : Number)(t); }\n/* Ethical ad publisher JavaScript client\n *\n * Loads placement from Ethical Ad decision API. Searches for elements with\n * `ethical-ad` data binding attributes and uses these attributes to query the\n * decision API.\n *\n * This is native JavaScript, no JQuery. It uses the API JSONP interface to get\n * around CORS and related issues. A script is added with a callback on\n * `window`. The promise is rejected if there are errors with the request or the\n * response doesn't look correct.\n *\n * Currently, only two parameters are supported with the ad placement: publisher\n * id and the place type. All of this is determined by the server and this\n * client so far only renders the API return HTML.\n *\n * This can be loaded async. CSS styles are preloaded via webpack `style-loader`.\n * There is some potential for problems if CSP rules disallow inline\n * stylesheets, but webpack does allow for a hardcoded nonce.\n *\n * Usage:\n *\n * \n *
\n */\n\n\n\nvar AD_CLIENT_VERSION = \"2.0.0\"; // Sent with the ad request\n\n// For local testing, set this\n// const AD_DECISION_URL = \"http://ethicaladserver:5000/api/v1/decision/\";\nvar AD_DECISION_URL = \"https://server.ethicalads.io/api/v1/decision/\";\nvar AD_TYPES_VERSION = 1; // Used with the ad type slugs\nvar ATTR_PREFIX = \"data-ea-\";\nvar ABP_DETECTION_PX = \"https://media.ethicalads.io/abp/px.gif\";\n\n// Verbosity and logging\n//\n// Set with:\n//\n//
\nvar VERBOSITY = {\n quiet: 0,\n // Errors only\n normal: 1,\n // Warnings only (default)\n verbose: 2 // Debug messages\n};\nvar logger = {\n verbosity: VERBOSITY[\"normal\"],\n // Default\n debug: function debug(message) {\n if (this.verbosity >= VERBOSITY[\"verbose\"]) {\n var _console;\n for (var _len = arguments.length, params = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {\n params[_key - 1] = arguments[_key];\n }\n (_console = console).debug.apply(_console, [message].concat(params));\n }\n },\n info: function info(message) {\n if (this.verbosity >= VERBOSITY[\"verbose\"]) {\n var _console2;\n for (var _len2 = arguments.length, params = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {\n params[_key2 - 1] = arguments[_key2];\n }\n (_console2 = console).info.apply(_console2, [message].concat(params));\n }\n },\n warn: function warn(message) {\n if (this.verbosity >= VERBOSITY[\"normal\"]) {\n var _console3;\n for (var _len3 = arguments.length, params = new Array(_len3 > 1 ? _len3 - 1 : 0), _key3 = 1; _key3 < _len3; _key3++) {\n params[_key3 - 1] = arguments[_key3];\n }\n (_console3 = console).warn.apply(_console3, [message].concat(params));\n }\n },\n error: function error(message) {\n if (this.verbosity >= VERBOSITY[\"quiet\"]) {\n var _console4;\n for (var _len4 = arguments.length, params = new Array(_len4 > 1 ? _len4 - 1 : 0), _key4 = 1; _key4 < _len4; _key4++) {\n params[_key4 - 1] = arguments[_key4];\n }\n (_console4 = console).error.apply(_console4, [message].concat(params));\n }\n }\n};\n\n// Keywords and topics\n//\n// This allows us to categorize pages simply and have better content targeting.\n// Additional categorization can be done on the server side for pages\n// that request ads commonly but this quick and easy categorization\n// works decently well most of the time.\nvar KEYWORDS = new Set([\"2fa\", \"ai\", \"airflow\", \"algolia\", \"android\", \"angular\", \"angularjs\", \"ansible\", \"api\", \"appengine\", \"app-engine\", \"arangodb\", \"artificial-intelligence\", \"asp-net\", \"auth0\", \"authentication\", \"authorization\", \"aws\", \"azure\", \"babel\", \"backend\", \"backend-web\", \"bayes\", \"bayesian\", \"billing\", \"bitcoin\", \"blender\", \"blockchain\", \"celery\", \"chartjs\", \"chatbot\", \"chatbots\", \"chatgpt\", \"chatgpt3\", \"chatgpt4\", \"ci\", \"cicd\", \"ci-cd\", \"classifier\", \"cloud\", \"cloudformation\", \"cloud-formation\", \"cloudfront\", \"clustering\", \"cockroachdb\", \"commonjs\", \"computer-vision\", \"container\", \"containers\", \"continuousdeployment\", \"continuous-deployment\", \"continuousintegration\", \"continuous-integration\", \"cordova\", \"cplusplus\", \"cryptocurrency\", \"cryptography\", \"csharp\", \"c-sharp\", \"css\", \"cssinjs\", \"cuda\", \"cve\", \"cyber-attack\", \"cybersecurity\", \"cyber-security\", \"d3js\", \"dalle\", \"dall-e\", \"dao\", \"dapp\", \"dataanalytics\", \"data-analytics\", \"database\", \"datadog\", \"datalake\", \"data-lake\", \"datamesh\", \"data-mesh\", \"datascience\", \"data-science\", \"datascientist\", \"data-scientist\", \"data-visualization\", \"data-warehouse\", \"decryption\", \"deeplearning\", \"deep-learning\", \"deepreinforcement\", \"deep-reinforcement\", \"defi\", \"devops\", \"django\", \"djangorestframework\", \"django-rest-framework\", \"dnssec\", \"docker\", \"dockerhub\", \"docker-hub\", \"dockerizing\", \"dogecoin\", \"dotnet\", \"duckdb\", \"elasticsearch\", \"elastic-search\", \"emberjs\", \"erlang\", \"es6\", \"eslint\", \"ethereum\", \"express\", \"facedetection\", \"face-detection\", \"fiddler\", \"firebase\", \"firewall\", \"flask\", \"frontend\", \"frontend-web\", \"fsharp\", \"full-stack\", \"game\", \"gamedev\", \"gatsbyjs\", \"gcp\", \"gitguardian\", \"godot\", \"golang\", \"google-cloud\", \"gpt\", \"grafana\", \"grails\", \"graphql\", \"hacking\", \"haskell\", \"heroku\", \"hyperledger\", \"indiegame\", \"indie-game\", \"influxdb\", \"infosec\", \"invoice\", \"ionic\", \"ios\", \"ipfs\", \"iphone\", \"java\", \"javascript\", \"jenkins\", \"jfrog\", \"jinja\", \"jquery\", \"julia\", \"jupyter\", \"jvm\", \"kafka\", \"k-means-clustering\", \"kotlin\", \"kubernetes\", \"laravel\", \"lint\", \"linux\", \"llm\", \"llms\", \"log4j\", \"lucene\", \"machinelearning\", \"machine-learning\", \"mariadb\", \"matlab\", \"matplotlib\", \"maven\", \"metabase\", \"mfa\", \"midjourney\", \"minecraft\", \"mkdocs\", \"ml\", \"mobile\", \"model-training\", \"mongodb\", \"monitoring\", \"montecarlo\", \"monte-carlo\", \"mysql\", \"naivebayes\", \"naive-bayes\", \"neo4j\", \"neuralnet\", \"neural-net\", \"neural-nets\", \"neuralnetworks\", \"neural-networks\", \"newrelic\", \"new-relic\", \"nft\", \"nginx\", \"nlp\", \"node\", \"nodejs\", \"nosql\", \"numpy\", \"nuxt\", \"nuxtjs\", \"oauth\", \"obj-c\", \"objectdetection\", \"object-detection\", \"openai\", \"opencv-python-library\", \"openid\", \"openid-connect\", \"openjdk\", \"openshift\", \"openssl\", \"otp\", \"overfitting\", \"owasp\", \"pandas\", \"payment\", \"payments\", \"paypal\", \"penetration-test\", \"pentest\", \"perl\", \"phishing\", \"phonegap\", \"php\", \"pip\", \"postcss\", \"postgres\", \"postgresql\", \"privacy\", \"psf\", \"pwa\", \"pydata\", \"pygame\", \"pylint\", \"pypi\", \"pytest\", \"python\", \"pytorch\", \"pytorch3d\", \"rabbitmq\", \"rails\", \"rdbms\", \"rds\", \"react\", \"reactjs\", \"react-native\", \"redis\", \"redux\", \"regression\", \"regressionmodel\", \"regression-model\", \"reinforcement-learning\", \"rollbar\", \"ruby\", \"rust\", \"saltstack\", \"scala\", \"scikitlearn\", \"scikit-learn\", \"scipy\", \"scss\", \"security\", \"securityvulnerabilities\", \"security-vulnerabilities\", \"selenium\", \"selinux\", \"sencha\", \"sentiment-analysis\", \"sentry\", \"serverless\", \"single-page-application\", \"sklearn\", \"smartphone\", \"sms\", \"snowflake\", \"snyk\", \"solana\", \"solidity\", \"solr\", \"spa\", \"spacy\", \"sphinx\", \"sphinx-doc\", \"spring\", \"sql\", \"sqlite\", \"sqlserver\", \"sql-server\", \"stripe\", \"struts\", \"subscriptions\", \"svelte\", \"sveltejs\", \"swift\", \"symfony\", \"tableau\", \"tailwind\", \"tailwindcss\", \"tailwind-css\", \"tdd\", \"technical-writing\", \"tensor\", \"tensorflow\", \"tensorflowjs\", \"terraform\", \"test-driven-development\", \"testing\", \"tests\", \"textacy\", \"timescale\", \"timeseries\", \"training-data\", \"transformers\", \"travisci\", \"twilio\", \"two-factor-auth\", \"two-factor-authentication\", \"typescript\", \"ubuntu\", \"unittest\", \"unity\", \"vision-api\", \"visualization\", \"vue\", \"vuejs\", \"vuetify\", \"vuex\", \"vulnerability\", \"waf\", \"web3\", \"webapp-firewall\", \"webapplicationfirewall\", \"web-application-firewall\", \"webcomponents\", \"web-components\", \"webpack\", \"websecurity\", \"web-security\", \"werkzeug\", \"wireshark\", \"wsgi\", \"yarn\", \"zapier\"]);\n\n// Maximum number of words of a document to analyze looking for keywords\n// This is simply a check against taking too much time on very long documents\nvar MAX_WORDS_ANALYZED = 9999;\n\n// Max number of detected keywords to send\n// Lowering this number means that only major topics of the page get sent on long pages\nvar MAX_KEYWORDS = 3;\n\n// Minimum number of occurrences of a keyword to consider it\nvar MIN_KEYWORD_OCCURRENCES = 2;\n\n// Time between checking whether the ad is in the viewport to count the time viewed\n// Time viewed is an important advertiser metric\nvar VIEW_TIME_INTERVAL = 1; // seconds\nvar VIEW_TIME_MAX = 5 * 60; // seconds\n\n// In-viewport fudge factor\n// A fudge factor of ~3 is needed for the case where the ad\n// is hidden off the side of the screen by a sliding sidebar\n// For example, if the right side of the ad is at x=0\n// or the left side of the ad is at the right side of the viewport\nvar VIEWPORT_FUDGE_FACTOR = -3; // px\n\n// An ad may be rotated if it has been visible for sufficient time\n// And there is user interaction such as a hashchange or visibilitychange.\n// We rotate no more than the maximum number of rotations.\n// Loading the ad the first time counts as the first rotation.\nvar MIN_VIEW_TIME_ROTATION_DURATION = 45; // seconds\nvar MAX_ROTATIONS = 3;\n\n// Enable ad rotation on hash change (intra-site nav)\nvar HASHCHANGE_ROTATION_ENABLE = true;\n\n// Seconds after a tab comes back into focus to rotate an ad.\nvar VISIBILITYCHANGE_ROTATION_ENABLE = false;\nvar VISIBILITYCHANGE_ROTATION_DELAY = 3; // seconds\n\n/* Placement object to query decision API and return an Element node\n *\n * @param {string} publisher - Publisher ID\n * @param {string} ad_type - Placement ad type id\n * @param {Element} target - Target element\n * @param {Object} options - Various options for configuring the placement such as:\n keywords, styles, campaign_types, load_manually, force_ad, force_campaign\n */\nvar Placement = /*#__PURE__*/function () {\n function Placement(publisher, ad_type, target, options) {\n _classCallCheck(this, Placement);\n this.publisher = publisher;\n this.ad_type = ad_type;\n this.target = target;\n\n // Options\n this.options = options;\n this.style = options.style;\n this.keywords = options.keywords || [];\n this.load_manually = options.load_manually;\n this.force_ad = options.force_ad;\n this.force_campaign = options.force_campaign;\n this.campaign_types = options.campaign_types || [];\n if (!this.campaign_types.length) {\n this.campaign_types = [\"paid\", \"publisher-house\", \"community\", \"house\"];\n }\n\n // Initialized and will be used in the future\n this.view_time = 0;\n this.view_time_sent = false; // true once the view time is sent to the server\n this.response = null;\n this.tab_hidden = false;\n this.rotations = 1;\n this.index = null;\n }\n\n /* Create a placement from an element\n *\n * Returns null if the placement is already loaded.\n *\n * @static\n * @param {Element} element - Load placement and append to this Element\n * @returns {Placement}\n */\n return _createClass(Placement, [{\n key: \"load\",\n value:\n /* Transforms target element into a placement\n *\n * This method organizes all of the operations to transform the placement\n * configuration wrapper `div` into an ad placement -- including starting the\n * API transaction, displaying the ad element,\n * and handling the viewport detection.\n *\n * @returns {Promise}\n */\n function load() {\n var _this = this;\n // Detect the keywords\n this.keywords = this.keywords.concat(this.detectKeywords());\n return this.fetch().then(function (element) {\n if (element === undefined) {\n throw new EthicalAdsWarning(\"Ad decision request blocked or invalid.\");\n }\n if (!element) {\n throw new EthicalAdsWarning(\"No ads to show.\");\n }\n\n // Add `loaded` class, signifying that the CSS styles should finally be\n // applied to the target element.\n var classes = _this.target.className || \"\";\n classes += \" loaded\";\n _this.target.className = classes.trim();\n\n // Make this element the only child element of the target element\n while (_this.target.firstChild) {\n _this.target.removeChild(_this.target.firstChild);\n }\n\n // Apply any styles based on the specified styling\n _this.applyStyles(element);\n _this.target.appendChild(element);\n return _this;\n }).then(function (placement) {\n // Detect when the ad is in the viewport\n // Add the view pixel to the DOM to count the view\n // Also count the time the ad is in view\n // this will be sent before the page/tab is closed or navigated away\n\n var viewport_detection = setInterval(function (element) {\n if (placement.inViewport(element)) {\n // This ad was seen!\n var pixel = document.createElement(\"img\");\n pixel.src = placement.response.view_url;\n if (uplifted) {\n pixel.src += \"?uplift=true\";\n }\n pixel.className = \"ea-pixel\";\n element.appendChild(pixel);\n clearInterval(viewport_detection);\n }\n }, 100, placement.target);\n placement.view_time_counter = setInterval(function (element) {\n if (placement.tab_hidden === false && placement.inViewport(element)) {\n // Increment the ad's time in view counter\n placement.view_time += VIEW_TIME_INTERVAL;\n if (placement.view_time >= VIEW_TIME_MAX) {\n clearInterval(placement.view_time_counter);\n }\n }\n }, VIEW_TIME_INTERVAL * 1000, placement.target);\n placement.hashchange_listener = function () {\n if (placement.canRotate()) {\n placement.sendViewTime();\n placement.rotate();\n }\n };\n if (HASHCHANGE_ROTATION_ENABLE) {\n window.addEventListener(\"hashchange\", placement.hashchange_listener);\n }\n\n // Listens to the window visibility\n // Rotates the ad when the window comes back into focus if\n // other conditions (minimum view time, under max rotations)\n // are met.\n // When the tab loses focus, send the view time to the server.\n placement.visibilitychange_listener = function () {\n if (document.visibilityState === \"hidden\" || document.visibilityState === \"unloaded\") {\n // Check if the tab loses focus/is closed or the browser/app is minimized/closed\n // In that case, no longer count further time that the ad is in view\n // Send the time the ad was viewed to the server\n placement.tab_hidden = true;\n placement.sendViewTime();\n }\n\n // This tab was invisible and has come back into focus\n // Trigger an ad rotation\n if (placement.tab_hidden === true && document.visibilityState === \"visible\") {\n placement.tab_hidden = false;\n if (placement.canRotate()) {\n placement.sendViewTime(); // Should already be sent, but just in case\n setTimeout(function () {\n placement.rotate();\n }, VISIBILITYCHANGE_ROTATION_DELAY * 1000);\n }\n }\n };\n if (VISIBILITYCHANGE_ROTATION_ENABLE) {\n document.addEventListener(\"visibilitychange\", placement.visibilitychange_listener);\n }\n return _this;\n });\n }\n\n /* Clears all the placement's event listeners */\n }, {\n key: \"clearListeners\",\n value: function clearListeners() {\n if (this.view_time_counter) {\n clearInterval(this.view_time_counter);\n }\n if (this.hashchange_listener && HASHCHANGE_ROTATION_ENABLE) {\n window.removeEventListener(\"hashchange\", this.hashchange_listener);\n }\n if (this.visibilitychange_listener && VISIBILITYCHANGE_ROTATION_ENABLE) {\n document.removeEventListener(\"visibilitychange\", this.visibilitychange_listener);\n }\n }\n\n /* Returns whether the conditions to rotate are met\n *\n * @returns {boolean} True if the placement can rotate\n */\n }, {\n key: \"canRotate\",\n value: function canRotate() {\n if (!this.inViewport(this.target) || this.view_time < MIN_VIEW_TIME_ROTATION_DURATION || this.rotations >= MAX_ROTATIONS) {\n return false;\n }\n return true;\n }\n\n /* Reloads the placement with a new ad (if applicable)\n *\n * @returns {Promise}\n */\n }, {\n key: \"rotate\",\n value: function rotate() {\n if (!this.canRotate()) {\n return;\n }\n this.clearListeners();\n this.view_time = 0;\n this.view_time_sent = false;\n this.response = null;\n this.tab_hidden = false;\n this.rotations += 1;\n return this.load();\n }\n\n /* Returns whether the ad is visible in the viewport\n *\n * @param {Element} element - The ad element\n * @returns {boolean} True if the ad is loaded and visible in the viewport\n * (including the tab being focused and not minimized) and returns false otherwise.\n */\n }, {\n key: \"inViewport\",\n value: function inViewport(element) {\n if (this.response && this.response.view_url && verge__WEBPACK_IMPORTED_MODULE_0___default.a.inViewport(element, VIEWPORT_FUDGE_FACTOR) && document.visibilityState === \"visible\") {\n return true;\n }\n return false;\n }\n\n /* Get placement data from decision API\n *\n * @returns {Promise} Resolves with an Element converted from an HTML\n * string from API response. Can also be null, indicating a noop action.\n */\n }, {\n key: \"fetch\",\n value: function fetch() {\n var _this2 = this;\n // Make sure callbacks don't collide even with multiple placements\n var callback = \"ad_\" + Date.now() + \"_\" + Math.floor(Math.random() * 1000000);\n var div_id = callback;\n if (this.target.id) {\n div_id = this.target.id;\n }\n\n // There's no hard maximum on URL lengths (all of these get added to the query params)\n // but ideally we want to keep our URLs below ~2k which should work basically everywhere\n var params = {\n publisher: this.publisher,\n ad_types: this.ad_type,\n div_ids: div_id,\n callback: callback,\n keywords: this.keywords.join(\"|\"),\n campaign_types: this.campaign_types.join(\"|\"),\n format: \"jsonp\",\n client_version: AD_CLIENT_VERSION,\n placement_index: this.index,\n // location.href includes query params (possibly sensitive) and fragments (unnecessary)\n url: (window.location.origin + window.location.pathname).slice(0, 256)\n };\n if (this.force_ad) {\n params[\"force_ad\"] = this.force_ad;\n }\n if (this.force_campaign) {\n params[\"force_campaign\"] = this.force_campaign;\n }\n if (this.rotations > 1) {\n params[\"rotations\"] = this.rotations;\n }\n var url_params = new URLSearchParams(params);\n var url = new URL(AD_DECISION_URL + \"?\" + url_params.toString());\n return new Promise(function (resolve, reject) {\n window[callback] = function (response) {\n if (response && response.html && response.view_url) {\n _this2.response = response;\n var node_convert = document.createElement(\"div\");\n node_convert.innerHTML = response.html;\n return resolve(node_convert.firstChild);\n } else {\n // No ad to show for this targeting/publisher\n return resolve(null);\n }\n };\n var script = document.createElement(\"script\");\n script.src = url;\n script.type = \"text/javascript\";\n script.async = true;\n script.addEventListener(\"error\", function (err) {\n // There was a problem loading this request, likely this was blocked by\n // an ad blocker. We'll resolve with an empty response instead of\n // throwing an error.\n return resolve();\n });\n document.getElementsByTagName(\"head\")[0].appendChild(script);\n });\n }\n\n /* Sends the view time of the ad to the server\n */\n }, {\n key: \"sendViewTime\",\n value: function sendViewTime() {\n if (this.view_time <= 0 || this.view_time_sent || !this.response || !this.response.view_time_url) return;\n var pixel = document.createElement(\"img\");\n pixel.src = this.response.view_time_url + \"?view_time=\" + this.view_time;\n pixel.className = \"ea-pixel\";\n this.target.appendChild(pixel);\n this.view_time_sent = true;\n }\n\n /* Detect whether this ad is \"uplifted\" meaning allowed by ABP's Acceptable Ads list\n *\n * Calls the provided callback passing a boolean whether this ad is uplifted.\n * We need this data to provide back to the AcceptableAds folks.\n *\n * This code comes directly from Eyeo/AdblockPlus team to measure Acceptable Ads.\n *\n * @static\n * @param {string} px - A URL of a pixel to test\n * @param {function) callback - A callback to call when finished\n */\n }, {\n key: \"detectABP\",\n value: function detectABP(px, callback) {\n var detected = false;\n var checksRemain = 2;\n var error1 = false;\n var error2 = false;\n if (typeof callback != \"function\") return;\n px += \"?ch=*&rn=*\";\n function beforeCheck(callback, timeout) {\n if (checksRemain == 0 || timeout > 1e3) callback(checksRemain == 0 && detected);else setTimeout(function () {\n beforeCheck(callback, timeout * 2);\n }, timeout * 2);\n }\n function checkImages() {\n if (--checksRemain) return;\n detected = !error1 && error2;\n }\n var random = Math.random() * 11;\n var img1 = new Image();\n img1.onload = checkImages;\n img1.onerror = function () {\n error1 = true;\n checkImages();\n };\n img1.src = px.replace(/\\*/, 1).replace(/\\*/, random);\n var img2 = new Image();\n img2.onload = checkImages;\n img2.onerror = function () {\n error2 = true;\n checkImages();\n };\n img2.src = px.replace(/\\*/, 2).replace(/\\*/, random);\n beforeCheck(callback, 250);\n }\n\n /* Returns an array of keywords (strings) found on the page\n *\n * @returns {Array[string]} Advertising keywords found on the page\n */\n }, {\n key: \"detectKeywords\",\n value: function detectKeywords() {\n // Return previously detected keywords\n // If this code has already run.\n // Note: if there are \"no\" keywords (an empty list) this is still true\n if (detectedKeywords) return detectedKeywords;\n var keywordHist = {}; // Keywords found => count of keyword\n var mainContent = document.querySelector(\"[role='main']\") || document.querySelector(\"main\") || document.querySelector(\"body\");\n var words = mainContent.textContent.split(/\\s+/);\n var wordTrimmer = /^[\\('\"]?(.*?)[,\\.\\?\\!:;\\)'\"]?$/g;\n for (var x = 0; x < words.length && x < MAX_WORDS_ANALYZED; x++) {\n // Remove certain punctuation from beginning and end of the word\n var word = words[x].replace(wordTrimmer, \"$1\").toLowerCase();\n if (KEYWORDS.has(word)) {\n keywordHist[word] = (keywordHist[word] || 0) + 1;\n }\n }\n\n // Sort the hist with the most common items first\n // Grab only the MAX_KEYWORDS most common\n var keywords = Object.entries(keywordHist).filter(\n // Only consider a keyword with at least this many occurrences\n function (a) {\n return a[1] >= MIN_KEYWORD_OCCURRENCES;\n }).sort(function (a, b) {\n if (a[1] > b[1]) return -1;\n if (a[1] < b[1]) return 1;\n return 0;\n }).slice(0, MAX_KEYWORDS).map(function (x) {\n return x[0];\n });\n detectedKeywords = keywords;\n return keywords;\n }\n\n /* Apply custom styles based on data-ea-style\n *\n */\n }, {\n key: \"applyStyles\",\n value: function applyStyles(element) {\n // Stickybox: https://ethical-ad-client.readthedocs.io/en/latest/#stickybox\n if (this.style === \"stickybox\") {\n var hideButton = document.createElement(\"div\");\n hideButton.setAttribute(\"class\", \"ea-stickybox-hide\");\n hideButton.innerHTML = \"×\";\n hideButton.addEventListener(\"click\", function () {\n document.querySelector(\"[data-ea-publisher]\").remove();\n });\n element.appendChild(hideButton);\n }\n\n // FixedFooter: https://ethical-ad-client.readthedocs.io/en/latest/#fixedfooter\n if (this.style === \"fixedfooter\") {\n //element.querySelector('.ea-callout a').remove();\n\n var container = document.createElement(\"div\");\n container.setAttribute(\"class\", \"ea-fixedfooter-hide\");\n element.appendChild(container);\n var _hideButton = document.createElement(\"span\");\n _hideButton.append(\"Close Ad\");\n _hideButton.addEventListener(\"click\", function () {\n document.querySelector(\"[data-ea-publisher]\").remove();\n });\n container.appendChild(_hideButton);\n }\n\n // FixedHeader: https://ethical-ad-client.readthedocs.io/en/latest/#fixedheader\n // No special elements required\n }\n }], [{\n key: \"from_element\",\n value: function from_element(element) {\n // Get attributes from DOM node\n var publisher = element.getAttribute(ATTR_PREFIX + \"publisher\");\n var ad_type = element.getAttribute(ATTR_PREFIX + \"type\");\n if (!ad_type) {\n ad_type = \"image\";\n element.setAttribute(ATTR_PREFIX + \"type\", \"image\");\n }\n var keywords = (element.getAttribute(ATTR_PREFIX + \"keywords\") || \"\").split(\"|\").filter(function (word) {\n return word.length > 1;\n });\n var campaign_types = (element.getAttribute(ATTR_PREFIX + \"campaign-types\") || \"\").split(\"|\").filter(function (word) {\n return word.length > 1;\n });\n var load_manually = element.getAttribute(ATTR_PREFIX + \"manual\") === \"true\";\n var style = element.getAttribute(ATTR_PREFIX + \"style\");\n var force_ad = element.getAttribute(ATTR_PREFIX + \"force-ad\");\n var force_campaign = element.getAttribute(ATTR_PREFIX + \"force-campaign\");\n\n // Add version to ad type to verison the HTML return\n if (ad_type === \"image\" || ad_type === \"text\") {\n ad_type += \"-v\" + AD_TYPES_VERSION;\n }\n var classes = (element.className || \"\").split(\" \");\n if (classes.indexOf(\"loaded\") >= 0) {\n logger.warn(\"EthicalAd already loaded.\");\n return null;\n }\n\n // Note: this attribute value *must* contain a unit (eg. '200px')\n var placementBottom = element.getAttribute(ATTR_PREFIX + \"placement-bottom\");\n if (placementBottom) {\n element.style.setProperty(\"bottom\", placementBottom);\n }\n return new Placement(publisher, ad_type, element, {\n keywords: keywords,\n style: style,\n campaign_types: campaign_types,\n load_manually: load_manually,\n force_ad: force_ad,\n force_campaign: force_campaign\n });\n }\n }]);\n}();\n\n/* Detects whether the browser supports the necessary JS APIs to support the ad client\n *\n * Generally we support recent versions of evergreen browsers (Chrome, Firefox, Safari, Edge)\n * but we no longer support IE11.\n *\n * @returns {boolean} true if all dependencies met and false otherwise\n */\nfunction check_dependencies() {\n if (!Object.entries || !window.URL || !window.URLSearchParams || !window.Promise) {\n logger.error(\"Browser does not meet ethical ad client dependencies. Not showing ads\");\n return false;\n }\n return true;\n}\n\n/* Find all placement DOM elements and hot load HTML as child nodes\n *\n * @param {boolean} force_load - load placements even if they are set to load manually\n * @returns {Promise<[Placement]>} Resolves to a list of Placement instances\n */\nfunction load_placements() {\n var force_load = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;\n // Find all elements matching required data binding attribute.\n var node_list = document.querySelectorAll(\"[\" + ATTR_PREFIX + \"publisher]\");\n var elements = Array.prototype.slice.call(node_list);\n if (elements.length === 0) {\n logger.warn(\"No ad placements found.\");\n }\n\n // Create main promise. Iterator `all()` Promise will surround array of found\n // elements. If any of these elements have issues, this main promise will\n // reject.\n return Promise.all(elements.map(function (element, index) {\n var placement = Placement.from_element(element);\n if (!placement) {\n // Placement has already been loaded\n return null;\n }\n placement.index = index;\n\n // Run AcceptableAds detection code\n // This lets us know how many impressions are attributed to AceeptableAds\n // Only run this once even for multiple placements\n // All impressions will be correctly attributed\n if (index === 0 && placement && !force_load) {\n placement.detectABP(ABP_DETECTION_PX, function (usesABP) {\n uplifted = usesABP;\n if (usesABP) {\n logger.debug(\"Acceptable Ads enabled. Thanks for allowing our non-tracking ads :)\");\n }\n });\n }\n if (placement && (force_load || !placement.load_manually)) {\n return placement.load();\n } else {\n // This will be manually loaded later or has already been loaded\n return null;\n }\n }));\n}\nfunction unload_placements() {\n var node_list = document.querySelectorAll(\"[\" + ATTR_PREFIX + \"publisher]\");\n var elements = Array.prototype.slice.call(node_list);\n elements.forEach(function (div) {\n div.innerHTML = \"\";\n div.classList.remove(\"loaded\");\n });\n}\nfunction set_verbosity() {\n var element = document.querySelector(\"[\" + ATTR_PREFIX + \"publisher]\");\n if (element) {\n var user_verbosity = element.getAttribute(ATTR_PREFIX + \"verbosity\");\n if (VERBOSITY.hasOwnProperty(user_verbosity)) {\n logger.verbosity = VERBOSITY[user_verbosity];\n }\n }\n}\n\n// An error class that we will not surface to clients normally.\nvar EthicalAdsWarning = /*#__PURE__*/function (_Error) {\n function EthicalAdsWarning() {\n _classCallCheck(this, EthicalAdsWarning);\n return _callSuper(this, EthicalAdsWarning, arguments);\n }\n _inherits(EthicalAdsWarning, _Error);\n return _createClass(EthicalAdsWarning);\n}(/*#__PURE__*/_wrapNativeSuper(Error));\n/* Wrapping Promise to allow for handling of errors by user\n *\n * This promise currently does not reject on error as this will emit a console\n * warning if the user hasn't added a promise rejection handler (which is most\n * cases).\n *\n * This promise resolves to an aray of Placement instances, or an empty list if\n * there was any error configuring the placements.\n *\n * For example, to perform an action when no placements are loaded:\n *\n * \n *\n * @type {Promise<[Placement]>}\n */\nvar wait;\n\n/* Loading placements manually rather than the normal way\n *\n *
\n * \n *\n * @type function\n */\nvar load;\n\n/* Reloading placements. Used by SPAs.\n * @type function\n */\nvar reload;\n\n/* Whether this ad impression is attributed to being on the Acceptable Ads list.\n * @type boolean\n */\nvar uplifted = false;\n\n/* Keywords detected on the page\n * @type {Array[string]}\n */\nvar detectedKeywords = null;\n\n/* If importing this as a module, do not automatically process DOM and fetch the\n * ad placement. Only do this if using the module directly, from a `script`\n * element. This will allow for future extension and packaging as a module.\n *\n * This also replicates JQuery `$(document).ready()`, with added protection for\n * usage of `async` -- the DOM ready event can fire before the script is loaded..\n */\nif (window.ethicalads) {\n // Always display this warning regardless of log level\n // This is a code mistake by publishers and should be caught right away.\n console.warn(\"Double-loading the EthicalAds client. Use reload() instead. https://ethical-ad-client.readthedocs.io/en/latest/#single-page-apps\");\n}\nif (check_dependencies()) {\n // Set the client verbosity\n set_verbosity();\n var wait_dom = new Promise(function (resolve) {\n if (document.readyState === \"interactive\" || document.readyState === \"complete\") {\n return resolve();\n } else {\n document.addEventListener(\"DOMContentLoaded\", function () {\n resolve();\n }, {\n capture: true,\n once: true,\n passive: true\n });\n }\n });\n wait = new Promise(function (resolve) {\n wait_dom.then(function () {\n load_placements().then(function (placements) {\n resolve(placements);\n })[\"catch\"](function (err) {\n resolve([]);\n if (err instanceof EthicalAdsWarning) {\n logger.warn(err.message);\n } else {\n logger.error(err.message);\n }\n });\n });\n });\n load = function load() {\n logger.debug(\"Loading placements manually\");\n load_placements(true)[\"catch\"](function (err) {\n if (err instanceof EthicalAdsWarning) {\n logger.warn(err.message);\n } else {\n logger.error(err.message);\n }\n });\n };\n reload = function reload() {\n logger.debug(\"Reloading ad placement\");\n detectedKeywords = null;\n unload_placements();\n load_placements()[\"catch\"](function (err) {\n if (err instanceof EthicalAdsWarning) {\n logger.warn(err.message);\n } else {\n logger.error(err.message);\n }\n });\n };\n}\n\n//# sourceURL=webpack://ethicalads/./index.js?"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"Placement\", function() { return Placement; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"check_dependencies\", function() { return check_dependencies; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"load_placements\", function() { return load_placements; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"unload_placements\", function() { return unload_placements; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"set_verbosity\", function() { return set_verbosity; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"wait\", function() { return wait; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"load\", function() { return load; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"reload\", function() { return reload; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"uplifted\", function() { return uplifted; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"detectedKeywords\", function() { return detectedKeywords; });\n/* harmony import */ var verge__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! verge */ \"./node_modules/verge/verge.js\");\n/* harmony import */ var verge__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(verge__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var _styles_scss__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./styles.scss */ \"./styles.scss\");\n/* harmony import */ var _styles_scss__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_styles_scss__WEBPACK_IMPORTED_MODULE_1__);\nfunction _typeof(o) { \"@babel/helpers - typeof\"; return _typeof = \"function\" == typeof Symbol && \"symbol\" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && \"function\" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? \"symbol\" : typeof o; }, _typeof(o); }\nfunction _callSuper(t, o, e) { return o = _getPrototypeOf(o), _possibleConstructorReturn(t, _isNativeReflectConstruct() ? Reflect.construct(o, e || [], _getPrototypeOf(t).constructor) : o.apply(t, e)); }\nfunction _possibleConstructorReturn(t, e) { if (e && (\"object\" == _typeof(e) || \"function\" == typeof e)) return e; if (void 0 !== e) throw new TypeError(\"Derived constructors may only return object or undefined\"); return _assertThisInitialized(t); }\nfunction _assertThisInitialized(e) { if (void 0 === e) throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\"); return e; }\nfunction _inherits(t, e) { if (\"function\" != typeof e && null !== e) throw new TypeError(\"Super expression must either be null or a function\"); t.prototype = Object.create(e && e.prototype, { constructor: { value: t, writable: !0, configurable: !0 } }), Object.defineProperty(t, \"prototype\", { writable: !1 }), e && _setPrototypeOf(t, e); }\nfunction _wrapNativeSuper(t) { var r = \"function\" == typeof Map ? new Map() : void 0; return _wrapNativeSuper = function _wrapNativeSuper(t) { if (null === t || !_isNativeFunction(t)) return t; if (\"function\" != typeof t) throw new TypeError(\"Super expression must either be null or a function\"); if (void 0 !== r) { if (r.has(t)) return r.get(t); r.set(t, Wrapper); } function Wrapper() { return _construct(t, arguments, _getPrototypeOf(this).constructor); } return Wrapper.prototype = Object.create(t.prototype, { constructor: { value: Wrapper, enumerable: !1, writable: !0, configurable: !0 } }), _setPrototypeOf(Wrapper, t); }, _wrapNativeSuper(t); }\nfunction _construct(t, e, r) { if (_isNativeReflectConstruct()) return Reflect.construct.apply(null, arguments); var o = [null]; o.push.apply(o, e); var p = new (t.bind.apply(t, o))(); return r && _setPrototypeOf(p, r.prototype), p; }\nfunction _isNativeReflectConstruct() { try { var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); } catch (t) {} return (_isNativeReflectConstruct = function _isNativeReflectConstruct() { return !!t; })(); }\nfunction _isNativeFunction(t) { try { return -1 !== Function.toString.call(t).indexOf(\"[native code]\"); } catch (n) { return \"function\" == typeof t; } }\nfunction _setPrototypeOf(t, e) { return _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function (t, e) { return t.__proto__ = e, t; }, _setPrototypeOf(t, e); }\nfunction _getPrototypeOf(t) { return _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf.bind() : function (t) { return t.__proto__ || Object.getPrototypeOf(t); }, _getPrototypeOf(t); }\nfunction _classCallCheck(a, n) { if (!(a instanceof n)) throw new TypeError(\"Cannot call a class as a function\"); }\nfunction _defineProperties(e, r) { for (var t = 0; t < r.length; t++) { var o = r[t]; o.enumerable = o.enumerable || !1, o.configurable = !0, \"value\" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o); } }\nfunction _createClass(e, r, t) { return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, \"prototype\", { writable: !1 }), e; }\nfunction _toPropertyKey(t) { var i = _toPrimitive(t, \"string\"); return \"symbol\" == _typeof(i) ? i : i + \"\"; }\nfunction _toPrimitive(t, r) { if (\"object\" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || \"default\"); if (\"object\" != _typeof(i)) return i; throw new TypeError(\"@@toPrimitive must return a primitive value.\"); } return (\"string\" === r ? String : Number)(t); }\n/* Ethical ad publisher JavaScript client\n *\n * Loads placement from Ethical Ad decision API. Searches for elements with\n * `ethical-ad` data binding attributes and uses these attributes to query the\n * decision API.\n *\n * This is native JavaScript, no JQuery. It uses the API JSONP interface to get\n * around CORS and related issues. A script is added with a callback on\n * `window`. The promise is rejected if there are errors with the request or the\n * response doesn't look correct.\n *\n * Currently, only two parameters are supported with the ad placement: publisher\n * id and the place type. All of this is determined by the server and this\n * client so far only renders the API return HTML.\n *\n * This can be loaded async. CSS styles are preloaded via webpack `style-loader`.\n * There is some potential for problems if CSP rules disallow inline\n * stylesheets, but webpack does allow for a hardcoded nonce.\n *\n * Usage:\n *\n * \n *
\n */\n\n\n\nvar AD_CLIENT_VERSION = \"2.0.0\"; // Sent with the ad request\n\n// For local testing, set this\n// const AD_DECISION_URL = \"http://ethicaladserver:5000/api/v1/decision/\";\nvar AD_DECISION_URL = \"https://server.ethicalads.io/api/v1/decision/\";\nvar AD_TYPES_VERSION = 1; // Used with the ad type slugs\nvar ATTR_PREFIX = \"data-ea-\";\nvar ABP_DETECTION_PX = \"https://media.ethicalads.io/abp/px.gif\";\n\n// Verbosity and logging\n//\n// Set with:\n//\n//
\nvar VERBOSITY = {\n quiet: 0,\n // Errors only\n normal: 1,\n // Warnings only (default)\n verbose: 2 // Debug messages\n};\nvar logger = {\n verbosity: VERBOSITY[\"normal\"],\n // Default\n debug: function debug(message) {\n if (this.verbosity >= VERBOSITY[\"verbose\"]) {\n var _console;\n for (var _len = arguments.length, params = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {\n params[_key - 1] = arguments[_key];\n }\n (_console = console).debug.apply(_console, [message].concat(params));\n }\n },\n info: function info(message) {\n if (this.verbosity >= VERBOSITY[\"verbose\"]) {\n var _console2;\n for (var _len2 = arguments.length, params = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {\n params[_key2 - 1] = arguments[_key2];\n }\n (_console2 = console).info.apply(_console2, [message].concat(params));\n }\n },\n warn: function warn(message) {\n if (this.verbosity >= VERBOSITY[\"normal\"]) {\n var _console3;\n for (var _len3 = arguments.length, params = new Array(_len3 > 1 ? _len3 - 1 : 0), _key3 = 1; _key3 < _len3; _key3++) {\n params[_key3 - 1] = arguments[_key3];\n }\n (_console3 = console).warn.apply(_console3, [message].concat(params));\n }\n },\n error: function error(message) {\n if (this.verbosity >= VERBOSITY[\"quiet\"]) {\n var _console4;\n for (var _len4 = arguments.length, params = new Array(_len4 > 1 ? _len4 - 1 : 0), _key4 = 1; _key4 < _len4; _key4++) {\n params[_key4 - 1] = arguments[_key4];\n }\n (_console4 = console).error.apply(_console4, [message].concat(params));\n }\n }\n};\n\n// Keywords and topics\n//\n// This allows us to categorize pages simply and have better content targeting.\n// Additional categorization can be done on the server side for pages\n// that request ads commonly but this quick and easy categorization\n// works decently well most of the time.\nvar KEYWORDS = new Set([\"2fa\", \"ai\", \"airflow\", \"algolia\", \"android\", \"angular\", \"angularjs\", \"ansible\", \"api\", \"appengine\", \"app-engine\", \"arangodb\", \"artificial-intelligence\", \"asp-net\", \"auth0\", \"authentication\", \"authorization\", \"aws\", \"azure\", \"babel\", \"backend\", \"backend-web\", \"bayes\", \"bayesian\", \"billing\", \"bitcoin\", \"blender\", \"blockchain\", \"celery\", \"chartjs\", \"chatbot\", \"chatbots\", \"chatgpt\", \"chatgpt3\", \"chatgpt4\", \"ci\", \"cicd\", \"ci-cd\", \"classifier\", \"cloud\", \"cloudformation\", \"cloud-formation\", \"cloudfront\", \"clustering\", \"cockroachdb\", \"commonjs\", \"computer-vision\", \"container\", \"containers\", \"continuousdeployment\", \"continuous-deployment\", \"continuousintegration\", \"continuous-integration\", \"cordova\", \"cplusplus\", \"cryptocurrency\", \"cryptography\", \"csharp\", \"c-sharp\", \"css\", \"cssinjs\", \"cuda\", \"cve\", \"cyber-attack\", \"cybersecurity\", \"cyber-security\", \"d3js\", \"dalle\", \"dall-e\", \"dao\", \"dapp\", \"dataanalytics\", \"data-analytics\", \"database\", \"datadog\", \"datalake\", \"data-lake\", \"datamesh\", \"data-mesh\", \"datascience\", \"data-science\", \"datascientist\", \"data-scientist\", \"data-visualization\", \"data-warehouse\", \"decryption\", \"deeplearning\", \"deep-learning\", \"deepreinforcement\", \"deep-reinforcement\", \"defi\", \"devops\", \"django\", \"djangorestframework\", \"django-rest-framework\", \"dnssec\", \"docker\", \"dockerhub\", \"docker-hub\", \"dockerizing\", \"dogecoin\", \"dotnet\", \"duckdb\", \"elasticsearch\", \"elastic-search\", \"emberjs\", \"erlang\", \"es6\", \"eslint\", \"ethereum\", \"express\", \"facedetection\", \"face-detection\", \"fiddler\", \"firebase\", \"firewall\", \"flask\", \"frontend\", \"frontend-web\", \"fsharp\", \"full-stack\", \"game\", \"gamedev\", \"gatsbyjs\", \"gcp\", \"gitguardian\", \"godot\", \"golang\", \"google-cloud\", \"gpt\", \"grafana\", \"grails\", \"graphql\", \"hacking\", \"haskell\", \"heroku\", \"hyperledger\", \"indiegame\", \"indie-game\", \"influxdb\", \"infosec\", \"invoice\", \"ionic\", \"ios\", \"ipfs\", \"iphone\", \"java\", \"javascript\", \"jenkins\", \"jfrog\", \"jinja\", \"jquery\", \"julia\", \"jupyter\", \"jvm\", \"kafka\", \"k-means-clustering\", \"kotlin\", \"kubernetes\", \"laravel\", \"lint\", \"linux\", \"llm\", \"llms\", \"log4j\", \"lucene\", \"machinelearning\", \"machine-learning\", \"mariadb\", \"matlab\", \"matplotlib\", \"maven\", \"metabase\", \"mfa\", \"midjourney\", \"minecraft\", \"mkdocs\", \"ml\", \"mobile\", \"model-training\", \"mongodb\", \"monitoring\", \"montecarlo\", \"monte-carlo\", \"mysql\", \"naivebayes\", \"naive-bayes\", \"neo4j\", \"neuralnet\", \"neural-net\", \"neural-nets\", \"neuralnetworks\", \"neural-networks\", \"newrelic\", \"new-relic\", \"nft\", \"nginx\", \"nlp\", \"node\", \"nodejs\", \"nosql\", \"numpy\", \"nuxt\", \"nuxtjs\", \"oauth\", \"obj-c\", \"objectdetection\", \"object-detection\", \"openai\", \"opencv-python-library\", \"openid\", \"openid-connect\", \"openjdk\", \"openshift\", \"openssl\", \"otp\", \"overfitting\", \"owasp\", \"pandas\", \"payment\", \"payments\", \"paypal\", \"penetration-test\", \"pentest\", \"perl\", \"phishing\", \"phonegap\", \"php\", \"pip\", \"postcss\", \"postgres\", \"postgresql\", \"privacy\", \"psf\", \"pwa\", \"pydata\", \"pygame\", \"pylint\", \"pypi\", \"pytest\", \"python\", \"pytorch\", \"pytorch3d\", \"rabbitmq\", \"rails\", \"rdbms\", \"rds\", \"react\", \"reactjs\", \"react-native\", \"redis\", \"redux\", \"regression\", \"regressionmodel\", \"regression-model\", \"reinforcement-learning\", \"rollbar\", \"ruby\", \"rust\", \"saltstack\", \"scala\", \"scikitlearn\", \"scikit-learn\", \"scipy\", \"scss\", \"security\", \"securityvulnerabilities\", \"security-vulnerabilities\", \"selenium\", \"selinux\", \"sencha\", \"sentiment-analysis\", \"sentry\", \"serverless\", \"single-page-application\", \"sklearn\", \"smartphone\", \"sms\", \"snowflake\", \"snyk\", \"solana\", \"solidity\", \"solr\", \"spa\", \"spacy\", \"sphinx\", \"sphinx-doc\", \"spring\", \"sql\", \"sqlite\", \"sqlserver\", \"sql-server\", \"stripe\", \"struts\", \"subscriptions\", \"svelte\", \"sveltejs\", \"swift\", \"symfony\", \"tableau\", \"tailwind\", \"tailwindcss\", \"tailwind-css\", \"tdd\", \"technical-writing\", \"tensor\", \"tensorflow\", \"tensorflowjs\", \"terraform\", \"test-driven-development\", \"testing\", \"tests\", \"textacy\", \"timescale\", \"timeseries\", \"training-data\", \"transformers\", \"travisci\", \"twilio\", \"two-factor-auth\", \"two-factor-authentication\", \"typescript\", \"ubuntu\", \"unittest\", \"unity\", \"vision-api\", \"visualization\", \"vue\", \"vuejs\", \"vuetify\", \"vuex\", \"vulnerability\", \"waf\", \"web3\", \"webapp-firewall\", \"webapplicationfirewall\", \"web-application-firewall\", \"webcomponents\", \"web-components\", \"webpack\", \"websecurity\", \"web-security\", \"werkzeug\", \"wireshark\", \"wsgi\", \"yarn\", \"zapier\"]);\n\n// Maximum number of words of a document to analyze looking for keywords\n// This is simply a check against taking too much time on very long documents\nvar MAX_WORDS_ANALYZED = 9999;\n\n// Max number of detected keywords to send\n// Lowering this number means that only major topics of the page get sent on long pages\nvar MAX_KEYWORDS = 3;\n\n// Minimum number of occurrences of a keyword to consider it\nvar MIN_KEYWORD_OCCURRENCES = 2;\n\n// Time between checking whether the ad is in the viewport to count the time viewed\n// Time viewed is an important advertiser metric\nvar VIEW_TIME_INTERVAL = 1; // seconds\nvar VIEW_TIME_MAX = 5 * 60; // seconds\n\n// In-viewport fudge factor\n// A fudge factor of ~3 is needed for the case where the ad\n// is hidden off the side of the screen by a sliding sidebar\n// For example, if the right side of the ad is at x=0\n// or the left side of the ad is at the right side of the viewport\nvar VIEWPORT_FUDGE_FACTOR = -3; // px\n\n// An ad may be rotated if it has been visible for sufficient time\n// And there is user interaction such as a hashchange or visibilitychange.\n// We rotate no more than the maximum number of rotations.\n// Loading the ad the first time counts as the first rotation.\nvar MIN_VIEW_TIME_ROTATION_DURATION = 45; // seconds\nvar MAX_ROTATIONS = 3;\n\n// Enable ad rotation on hash change (intra-site nav)\nvar HASHCHANGE_ROTATION_ENABLE = true;\n\n// Seconds after a tab comes back into focus to rotate an ad.\nvar VISIBILITYCHANGE_ROTATION_ENABLE = false;\nvar VISIBILITYCHANGE_ROTATION_DELAY = 3; // seconds\n\n/* Placement object to query decision API and return an Element node\n *\n * @param {string} publisher - Publisher ID\n * @param {string} ad_type - Placement ad type id\n * @param {Element} target - Target element\n * @param {Object} options - Various options for configuring the placement such as:\n keywords, styles, campaign_types, load_manually, force_ad, force_campaign\n */\nvar Placement = /*#__PURE__*/function () {\n function Placement(publisher, ad_type, target, options) {\n _classCallCheck(this, Placement);\n this.publisher = publisher;\n this.ad_type = ad_type;\n this.target = target;\n\n // Options\n this.options = options;\n this.style = options.style;\n this.keywords = options.keywords || [];\n this.load_manually = options.load_manually;\n this.force_ad = options.force_ad;\n this.force_campaign = options.force_campaign;\n this.campaign_types = options.campaign_types || [];\n if (!this.campaign_types.length) {\n this.campaign_types = [\"paid\", \"publisher-house\", \"community\", \"house\"];\n }\n\n // Initialized and will be used in the future\n this.view_time = 0;\n this.view_time_sent = false; // true once the view time is sent to the server\n this.response = null;\n this.tab_hidden = false;\n this.rotations = 1;\n this.index = null;\n }\n\n /* Create a placement from an element\n *\n * Returns null if the placement is already loaded.\n *\n * @static\n * @param {Element} element - Load placement and append to this Element\n * @returns {Placement}\n */\n return _createClass(Placement, [{\n key: \"load\",\n value:\n /* Transforms target element into a placement\n *\n * This method organizes all of the operations to transform the placement\n * configuration wrapper `div` into an ad placement -- including starting the\n * API transaction, displaying the ad element,\n * and handling the viewport detection.\n *\n * @returns {Promise}\n */\n function load() {\n var _this = this;\n // Detect the keywords\n this.keywords = this.keywords.concat(this.detectKeywords());\n return this.fetch().then(function (element) {\n if (element === undefined) {\n throw new EthicalAdsWarning(\"Ad decision request blocked or invalid.\");\n }\n if (!element) {\n throw new EthicalAdsWarning(\"No ads to show.\");\n }\n\n // Add `loaded` class, signifying that the CSS styles should finally be\n // applied to the target element.\n var classes = _this.target.className || \"\";\n classes += \" loaded\";\n _this.target.className = classes.trim();\n\n // Make this element the only child element of the target element\n while (_this.target.firstChild) {\n _this.target.removeChild(_this.target.firstChild);\n }\n\n // Apply any styles based on the specified styling\n _this.applyStyles(element);\n _this.target.appendChild(element);\n return _this;\n }).then(function (placement) {\n // Detect when the ad is in the viewport\n // Add the view pixel to the DOM to count the view\n // Also count the time the ad is in view\n // this will be sent before the page/tab is closed or navigated away\n\n var viewport_detection = setInterval(function (element) {\n if (placement.inViewport(element)) {\n // This ad was seen!\n var pixel = document.createElement(\"img\");\n pixel.src = placement.response.view_url;\n if (uplifted) {\n pixel.src += \"?uplift=true\";\n }\n pixel.className = \"ea-pixel\";\n element.appendChild(pixel);\n clearInterval(viewport_detection);\n }\n }, 100, placement.target);\n placement.view_time_counter = setInterval(function (element) {\n if (placement.tab_hidden === false && placement.inViewport(element)) {\n // Increment the ad's time in view counter\n placement.view_time += VIEW_TIME_INTERVAL;\n if (placement.view_time >= VIEW_TIME_MAX) {\n clearInterval(placement.view_time_counter);\n }\n }\n }, VIEW_TIME_INTERVAL * 1000, placement.target);\n placement.hashchange_listener = function () {\n if (placement.canRotate()) {\n placement.sendViewTime();\n placement.rotate();\n }\n };\n if (HASHCHANGE_ROTATION_ENABLE) {\n window.addEventListener(\"hashchange\", placement.hashchange_listener);\n }\n\n // Listens to the window visibility\n // Rotates the ad when the window comes back into focus if\n // other conditions (minimum view time, under max rotations)\n // are met.\n // When the tab loses focus, send the view time to the server.\n placement.visibilitychange_listener = function () {\n if (document.visibilityState === \"hidden\" || document.visibilityState === \"unloaded\") {\n // Check if the tab loses focus/is closed or the browser/app is minimized/closed\n // In that case, no longer count further time that the ad is in view\n // Send the time the ad was viewed to the server\n placement.tab_hidden = true;\n placement.sendViewTime();\n }\n\n // This tab was invisible and has come back into focus\n // Trigger an ad rotation\n if (placement.tab_hidden === true && document.visibilityState === \"visible\") {\n placement.tab_hidden = false;\n if (placement.canRotate()) {\n placement.sendViewTime(); // Should already be sent, but just in case\n setTimeout(function () {\n placement.rotate();\n }, VISIBILITYCHANGE_ROTATION_DELAY * 1000);\n }\n }\n };\n if (VISIBILITYCHANGE_ROTATION_ENABLE) {\n document.addEventListener(\"visibilitychange\", placement.visibilitychange_listener);\n }\n return _this;\n });\n }\n\n /* Clears all the placement's event listeners */\n }, {\n key: \"clearListeners\",\n value: function clearListeners() {\n if (this.view_time_counter) {\n clearInterval(this.view_time_counter);\n }\n if (this.hashchange_listener && HASHCHANGE_ROTATION_ENABLE) {\n window.removeEventListener(\"hashchange\", this.hashchange_listener);\n }\n if (this.visibilitychange_listener && VISIBILITYCHANGE_ROTATION_ENABLE) {\n document.removeEventListener(\"visibilitychange\", this.visibilitychange_listener);\n }\n }\n\n /* Returns whether the conditions to rotate are met\n *\n * @returns {boolean} True if the placement can rotate\n */\n }, {\n key: \"canRotate\",\n value: function canRotate() {\n if (!this.inViewport(this.target) || this.view_time < MIN_VIEW_TIME_ROTATION_DURATION || this.rotations >= MAX_ROTATIONS) {\n return false;\n }\n return true;\n }\n\n /* Reloads the placement with a new ad (if applicable)\n *\n * @returns {Promise}\n */\n }, {\n key: \"rotate\",\n value: function rotate() {\n if (!this.canRotate()) {\n return;\n }\n this.clearListeners();\n this.view_time = 0;\n this.view_time_sent = false;\n this.response = null;\n this.tab_hidden = false;\n this.rotations += 1;\n return this.load();\n }\n\n /* Returns whether the ad is visible in the viewport\n *\n * @param {Element} element - The ad element\n * @returns {boolean} True if the ad is loaded and visible in the viewport\n * (including the tab being focused and not minimized) and returns false otherwise.\n */\n }, {\n key: \"inViewport\",\n value: function inViewport(element) {\n if (this.response && this.response.view_url && verge__WEBPACK_IMPORTED_MODULE_0___default.a.inViewport(element, VIEWPORT_FUDGE_FACTOR) && document.visibilityState === \"visible\") {\n return true;\n }\n return false;\n }\n\n /* Get placement data from decision API\n *\n * @returns {Promise} Resolves with an Element converted from an HTML\n * string from API response. Can also be null, indicating a noop action.\n */\n }, {\n key: \"fetch\",\n value: function fetch() {\n var _this2 = this;\n // Make sure callbacks don't collide even with multiple placements\n var callback = \"ad_\" + Date.now() + \"_\" + Math.floor(Math.random() * 1000000);\n var div_id = callback;\n if (this.target.id) {\n div_id = this.target.id;\n }\n\n // There's no hard maximum on URL lengths (all of these get added to the query params)\n // but ideally we want to keep our URLs below ~2k which should work basically everywhere\n var params = {\n publisher: this.publisher,\n ad_types: this.ad_type,\n div_ids: div_id,\n callback: callback,\n keywords: this.keywords.join(\"|\"),\n campaign_types: this.campaign_types.join(\"|\"),\n format: \"jsonp\",\n client_version: AD_CLIENT_VERSION,\n placement_index: this.index,\n // location.href includes query params (possibly sensitive) and fragments (unnecessary)\n url: (window.location.origin + window.location.pathname).slice(0, 256)\n };\n if (this.force_ad) {\n params[\"force_ad\"] = this.force_ad;\n }\n if (this.force_campaign) {\n params[\"force_campaign\"] = this.force_campaign;\n }\n if (this.rotations > 1) {\n params[\"rotations\"] = this.rotations;\n }\n var url_params = new URLSearchParams(params);\n var url = new URL(AD_DECISION_URL + \"?\" + url_params.toString());\n return new Promise(function (resolve, reject) {\n window[callback] = function (response) {\n if (response && response.html && response.view_url) {\n _this2.response = response;\n var node_convert = document.createElement(\"div\");\n node_convert.innerHTML = response.html;\n return resolve(node_convert.firstChild);\n } else {\n // No ad to show for this targeting/publisher\n return resolve(null);\n }\n };\n var script = document.createElement(\"script\");\n script.src = url;\n script.type = \"text/javascript\";\n script.async = true;\n script.addEventListener(\"error\", function (err) {\n // There was a problem loading this request, likely this was blocked by\n // an ad blocker. We'll resolve with an empty response instead of\n // throwing an error.\n return resolve();\n });\n document.getElementsByTagName(\"head\")[0].appendChild(script);\n });\n }\n\n /* Sends the view time of the ad to the server\n */\n }, {\n key: \"sendViewTime\",\n value: function sendViewTime() {\n if (this.view_time <= 0 || this.view_time_sent || !this.response || !this.response.view_time_url) return;\n var pixel = document.createElement(\"img\");\n pixel.src = this.response.view_time_url + \"?view_time=\" + this.view_time;\n pixel.className = \"ea-pixel\";\n this.target.appendChild(pixel);\n this.view_time_sent = true;\n }\n\n /* Detect whether this ad is \"uplifted\" meaning allowed by ABP's Acceptable Ads list\n *\n * Calls the provided callback passing a boolean whether this ad is uplifted.\n * We need this data to provide back to the AcceptableAds folks.\n *\n * This code comes directly from Eyeo/AdblockPlus team to measure Acceptable Ads.\n *\n * @static\n * @param {string} px - A URL of a pixel to test\n * @param {function) callback - A callback to call when finished\n */\n }, {\n key: \"detectABP\",\n value: function detectABP(px, callback) {\n var detected = false;\n var checksRemain = 2;\n var error1 = false;\n var error2 = false;\n if (typeof callback != \"function\") return;\n px += \"?ch=*&rn=*\";\n function beforeCheck(callback, timeout) {\n if (checksRemain == 0 || timeout > 1e3) callback(checksRemain == 0 && detected);else setTimeout(function () {\n beforeCheck(callback, timeout * 2);\n }, timeout * 2);\n }\n function checkImages() {\n if (--checksRemain) return;\n detected = !error1 && error2;\n }\n var random = Math.random() * 11;\n var img1 = new Image();\n img1.onload = checkImages;\n img1.onerror = function () {\n error1 = true;\n checkImages();\n };\n img1.src = px.replace(/\\*/, 1).replace(/\\*/, random);\n var img2 = new Image();\n img2.onload = checkImages;\n img2.onerror = function () {\n error2 = true;\n checkImages();\n };\n img2.src = px.replace(/\\*/, 2).replace(/\\*/, random);\n beforeCheck(callback, 250);\n }\n\n /* Returns an array of keywords (strings) found on the page\n *\n * @returns {Array[string]} Advertising keywords found on the page\n */\n }, {\n key: \"detectKeywords\",\n value: function detectKeywords() {\n // Return previously detected keywords\n // If this code has already run.\n // Note: if there are \"no\" keywords (an empty list) this is still true\n if (detectedKeywords) return detectedKeywords;\n var keywordHist = {}; // Keywords found => count of keyword\n var mainContent = document.querySelector(\"[role='main']\") || document.querySelector(\"main\") || document.querySelector(\"body\");\n var words = mainContent.textContent.split(/\\s+/);\n var wordTrimmer = /^[\\('\"]?(.*?)[,\\.\\?\\!:;\\)'\"]?$/g;\n for (var x = 0; x < words.length && x < MAX_WORDS_ANALYZED; x++) {\n // Remove certain punctuation from beginning and end of the word\n var word = words[x].replace(wordTrimmer, \"$1\").toLowerCase();\n if (KEYWORDS.has(word)) {\n keywordHist[word] = (keywordHist[word] || 0) + 1;\n }\n }\n\n // Sort the hist with the most common items first\n // Grab only the MAX_KEYWORDS most common\n var keywords = Object.entries(keywordHist).filter(\n // Only consider a keyword with at least this many occurrences\n function (a) {\n return a[1] >= MIN_KEYWORD_OCCURRENCES;\n }).sort(function (a, b) {\n if (a[1] > b[1]) return -1;\n if (a[1] < b[1]) return 1;\n return 0;\n }).slice(0, MAX_KEYWORDS).map(function (x) {\n return x[0];\n });\n detectedKeywords = keywords;\n return keywords;\n }\n\n /* Apply custom styles based on data-ea-style\n *\n */\n }, {\n key: \"applyStyles\",\n value: function applyStyles(element) {\n // Stickybox: https://ethical-ad-client.readthedocs.io/en/latest/#stickybox\n if (this.style === \"stickybox\") {\n var hideButton = document.createElement(\"div\");\n hideButton.setAttribute(\"class\", \"ea-stickybox-hide\");\n hideButton.innerHTML = \"×\";\n hideButton.addEventListener(\"click\", function () {\n document.querySelector(\"[data-ea-publisher]\").remove();\n });\n element.appendChild(hideButton);\n }\n\n // FixedFooter: https://ethical-ad-client.readthedocs.io/en/latest/#fixedfooter\n if (this.style === \"fixedfooter\") {\n //element.querySelector('.ea-callout a').remove();\n\n var container = document.createElement(\"div\");\n container.setAttribute(\"class\", \"ea-fixedfooter-hide\");\n element.appendChild(container);\n var _hideButton = document.createElement(\"span\");\n _hideButton.append(\"Close Ad\");\n _hideButton.addEventListener(\"click\", function () {\n document.querySelector(\"[data-ea-publisher]\").remove();\n });\n container.appendChild(_hideButton);\n }\n\n // FixedHeader: https://ethical-ad-client.readthedocs.io/en/latest/#fixedheader\n // No special elements required\n }\n }], [{\n key: \"from_element\",\n value: function from_element(element) {\n // Get attributes from DOM node\n var publisher = element.getAttribute(ATTR_PREFIX + \"publisher\");\n var ad_type = element.getAttribute(ATTR_PREFIX + \"type\");\n if (!ad_type) {\n ad_type = \"image\";\n element.setAttribute(ATTR_PREFIX + \"type\", \"image\");\n }\n var keywords = (element.getAttribute(ATTR_PREFIX + \"keywords\") || \"\").split(\"|\").filter(function (word) {\n return word.length > 1;\n });\n var campaign_types = (element.getAttribute(ATTR_PREFIX + \"campaign-types\") || \"\").split(\"|\").filter(function (word) {\n return word.length > 1;\n });\n var load_manually = element.getAttribute(ATTR_PREFIX + \"manual\") === \"true\";\n var style = element.getAttribute(ATTR_PREFIX + \"style\");\n var force_ad = element.getAttribute(ATTR_PREFIX + \"force-ad\");\n var force_campaign = element.getAttribute(ATTR_PREFIX + \"force-campaign\");\n\n // Add version to ad type to verison the HTML return\n if (ad_type === \"image\" || ad_type === \"text\") {\n ad_type += \"-v\" + AD_TYPES_VERSION;\n }\n var classes = (element.className || \"\").split(\" \");\n if (classes.indexOf(\"loaded\") >= 0) {\n logger.warn(\"EthicalAd already loaded.\");\n return null;\n }\n\n // Note: this attribute value *must* contain a unit (eg. '200px')\n var placementBottom = element.getAttribute(ATTR_PREFIX + \"placement-bottom\");\n if (placementBottom) {\n element.style.setProperty(\"bottom\", placementBottom);\n }\n return new Placement(publisher, ad_type, element, {\n keywords: keywords,\n style: style,\n campaign_types: campaign_types,\n load_manually: load_manually,\n force_ad: force_ad,\n force_campaign: force_campaign\n });\n }\n }]);\n}();\n\n/* Detects whether the browser supports the necessary JS APIs to support the ad client\n *\n * Generally we support recent versions of evergreen browsers (Chrome, Firefox, Safari, Edge)\n * but we no longer support IE11.\n *\n * @returns {boolean} true if all dependencies met and false otherwise\n */\nfunction check_dependencies() {\n if (!Object.entries || !window.URL || !window.URLSearchParams || !window.Promise) {\n logger.error(\"Browser does not meet ethical ad client dependencies. Not showing ads\");\n return false;\n }\n return true;\n}\n\n/* Find all placement DOM elements and hot load HTML as child nodes\n *\n * @param {boolean} force_load - load placements even if they are set to load manually\n * @returns {Promise<[Placement]>} Resolves to a list of Placement instances\n */\nfunction load_placements() {\n var force_load = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;\n // Find all elements matching required data binding attribute.\n var node_list = document.querySelectorAll(\"[\" + ATTR_PREFIX + \"publisher]\");\n var elements = Array.prototype.slice.call(node_list);\n if (elements.length === 0) {\n logger.warn(\"No ad placements found.\");\n }\n\n // Create main promise. Iterator `all()` Promise will surround array of found\n // elements. If any of these elements have issues, this main promise will\n // reject.\n return Promise.all(elements.map(function (element, index) {\n var placement = Placement.from_element(element);\n if (!placement) {\n // Placement has already been loaded\n return null;\n }\n placement.index = index;\n\n // Run AcceptableAds detection code\n // This lets us know how many impressions are attributed to AceeptableAds\n // Only run this once even for multiple placements\n // All impressions will be correctly attributed\n if (index === 0 && placement && !force_load) {\n placement.detectABP(ABP_DETECTION_PX, function (usesABP) {\n uplifted = usesABP;\n if (usesABP) {\n logger.debug(\"Acceptable Ads enabled. Thanks for allowing our non-tracking ads :)\");\n }\n });\n }\n if (placement && (force_load || !placement.load_manually)) {\n return placement.load();\n } else {\n // This will be manually loaded later or has already been loaded\n return null;\n }\n }));\n}\nfunction unload_placements() {\n var node_list = document.querySelectorAll(\"[\" + ATTR_PREFIX + \"publisher]\");\n var elements = Array.prototype.slice.call(node_list);\n elements.forEach(function (div) {\n div.innerHTML = \"\";\n div.classList.remove(\"loaded\");\n });\n}\nfunction set_verbosity() {\n var element = document.querySelector(\"[\" + ATTR_PREFIX + \"publisher]\");\n if (element) {\n var user_verbosity = element.getAttribute(ATTR_PREFIX + \"verbosity\");\n if (VERBOSITY.hasOwnProperty(user_verbosity)) {\n logger.verbosity = VERBOSITY[user_verbosity];\n }\n }\n}\n\n// An error class that we will not surface to clients normally.\nvar EthicalAdsWarning = /*#__PURE__*/function (_Error) {\n function EthicalAdsWarning() {\n _classCallCheck(this, EthicalAdsWarning);\n return _callSuper(this, EthicalAdsWarning, arguments);\n }\n _inherits(EthicalAdsWarning, _Error);\n return _createClass(EthicalAdsWarning);\n}(/*#__PURE__*/_wrapNativeSuper(Error));\n/* Wrapping Promise to allow for handling of errors by user\n *\n * This promise currently does not reject on error as this will emit a console\n * warning if the user hasn't added a promise rejection handler (which is most\n * cases).\n *\n * This promise resolves to an aray of Placement instances, or an empty list if\n * there was any error configuring the placements.\n *\n * For example, to perform an action when no placements are loaded:\n *\n * \n *\n * @type {Promise<[Placement]>}\n */\nvar wait;\n\n/* Loading placements manually rather than the normal way\n *\n *
\n * \n *\n * @type function\n */\nvar load;\n\n/* Reloading placements. Used by SPAs.\n * @type function\n */\nvar reload;\n\n/* Whether this ad impression is attributed to being on the Acceptable Ads list.\n * @type boolean\n */\nvar uplifted = false;\n\n/* Keywords detected on the page\n * @type {Array[string]}\n */\nvar detectedKeywords = null;\n\n/* If importing this as a module, do not automatically process DOM and fetch the\n * ad placement. Only do this if using the module directly, from a `script`\n * element. This will allow for future extension and packaging as a module.\n *\n * This also replicates JQuery `$(document).ready()`, with added protection for\n * usage of `async` -- the DOM ready event can fire before the script is loaded..\n */\nif (window.ethicalads) {\n // Always display this warning regardless of log level\n // This is a code mistake by publishers and should be caught right away.\n console.warn(\"Double-loading the EthicalAds client. Use reload() instead. https://ethical-ad-client.readthedocs.io/en/latest/#single-page-apps\");\n}\nif (check_dependencies()) {\n // Set the client verbosity\n set_verbosity();\n var wait_dom = new Promise(function (resolve) {\n if (document.readyState === \"interactive\" || document.readyState === \"complete\") {\n return resolve();\n } else {\n document.addEventListener(\"DOMContentLoaded\", function () {\n resolve();\n }, {\n capture: true,\n once: true,\n passive: true\n });\n }\n });\n wait = new Promise(function (resolve) {\n wait_dom.then(function () {\n load_placements().then(function (placements) {\n resolve(placements.filter(function (p) {\n return p != null;\n }));\n })[\"catch\"](function (err) {\n resolve([]);\n if (err instanceof EthicalAdsWarning) {\n logger.warn(err.message);\n } else {\n logger.error(err.message);\n }\n });\n });\n });\n load = function load() {\n logger.debug(\"Loading placements manually\");\n load_placements(true)[\"catch\"](function (err) {\n if (err instanceof EthicalAdsWarning) {\n logger.warn(err.message);\n } else {\n logger.error(err.message);\n }\n });\n };\n reload = function reload() {\n logger.debug(\"Reloading ad placement\");\n detectedKeywords = null;\n unload_placements();\n load_placements()[\"catch\"](function (err) {\n if (err instanceof EthicalAdsWarning) {\n logger.warn(err.message);\n } else {\n logger.error(err.message);\n }\n });\n };\n}\n\n//# sourceURL=webpack://ethicalads/./index.js?"); /***/ }), diff --git a/dist/ethicalads.min.js b/dist/ethicalads.min.js index 5baad57..2389ce3 100644 --- a/dist/ethicalads.min.js +++ b/dist/ethicalads.min.js @@ -1 +1 @@ -var ethicalads=function(e){var a={};function t(o){if(a[o])return a[o].exports;var n=a[o]={i:o,l:!1,exports:{}};return e[o].call(n.exports,n,n.exports,t),n.l=!0,n.exports}return t.m=e,t.c=a,t.d=function(e,a,o){t.o(e,a)||Object.defineProperty(e,a,{enumerable:!0,get:o})},t.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},t.t=function(e,a){if(1&a&&(e=t(e)),8&a)return e;if(4&a&&"object"==typeof e&&e&&e.__esModule)return e;var o=Object.create(null);if(t.r(o),Object.defineProperty(o,"default",{enumerable:!0,value:e}),2&a&&"string"!=typeof e)for(var n in e)t.d(o,n,function(a){return e[a]}.bind(null,n));return o},t.n=function(e){var a=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(a,"a",a),a},t.o=function(e,a){return Object.prototype.hasOwnProperty.call(e,a)},t.p="",t(t.s=1)}([function(e,a,t){var o,n;o=this,n=function(){var e={},a="undefined"!=typeof window&&window,t="undefined"!=typeof document&&document,o=t&&t.documentElement,n=a.matchMedia||a.msMatchMedia,r=n?function(e){return!!n.call(a,e).matches}:function(){return!1},i=e.viewportW=function(){var e=o.clientWidth,t=a.innerWidth;return e=0&&t.left<=i()},e.inY=function(e,a){var t=c(e,a);return!!t&&t.bottom>=0&&t.top<=d()},e.inViewport=function(e,a){var t=c(e,a);return!!t&&t.bottom>=0&&t.right>=0&&t.top<=d()&&t.left<=i()},e},e.exports?e.exports=n():o.verge=n()},function(e,a,t){"use strict";t.r(a),t.d(a,"Placement",(function(){return g})),t.d(a,"check_dependencies",(function(){return k})),t.d(a,"load_placements",(function(){return x})),t.d(a,"unload_placements",(function(){return w})),t.d(a,"set_verbosity",(function(){return _})),t.d(a,"wait",(function(){return j})),t.d(a,"load",(function(){return S})),t.d(a,"reload",(function(){return z})),t.d(a,"uplifted",(function(){return O})),t.d(a,"detectedKeywords",(function(){return E}));var o=t(0),n=t.n(o);t(2);function r(e){return(r="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}function i(e,a,t){return a=u(a),function(e,a){if(a&&("object"==r(a)||"function"==typeof a))return a;if(void 0!==a)throw new TypeError("Derived constructors may only return object or undefined");return function(e){if(void 0===e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return e}(e)}(e,c()?Reflect.construct(a,t||[],u(e).constructor):a.apply(e,t))}function d(e){var a="function"==typeof Map?new Map:void 0;return(d=function(e){if(null===e||!function(e){try{return-1!==Function.toString.call(e).indexOf("[native code]")}catch(a){return"function"==typeof e}}(e))return e;if("function"!=typeof e)throw new TypeError("Super expression must either be null or a function");if(void 0!==a){if(a.has(e))return a.get(e);a.set(e,t)}function t(){return l(e,arguments,u(this).constructor)}return t.prototype=Object.create(e.prototype,{constructor:{value:t,enumerable:!1,writable:!0,configurable:!0}}),s(t,e)})(e)}function l(e,a,t){if(c())return Reflect.construct.apply(null,arguments);var o=[null];o.push.apply(o,a);var n=new(e.bind.apply(e,o));return t&&s(n,t.prototype),n}function c(){try{var e=!Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],(function(){})))}catch(e){}return(c=function(){return!!e})()}function s(e,a){return(s=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(e,a){return e.__proto__=a,e})(e,a)}function u(e){return(u=Object.setPrototypeOf?Object.getPrototypeOf.bind():function(e){return e.__proto__||Object.getPrototypeOf(e)})(e)}function p(e,a){if(!(e instanceof a))throw new TypeError("Cannot call a class as a function")}function f(e,a){for(var t=0;t=m.verbose){for(var a,t=arguments.length,o=new Array(t>1?t-1:0),n=1;n=m.verbose){for(var a,t=arguments.length,o=new Array(t>1?t-1:0),n=1;n=m.normal){for(var a,t=arguments.length,o=new Array(t>1?t-1:0),n=1;n=m.quiet){for(var a,t=arguments.length,o=new Array(t>1?t-1:0),n=1;n=300&&clearInterval(a.view_time_counter))}),1e3,a.target),a.hashchange_listener=function(){a.canRotate()&&(a.sendViewTime(),a.rotate())},window.addEventListener("hashchange",a.hashchange_listener),a.visibilitychange_listener=function(){"hidden"!==document.visibilityState&&"unloaded"!==document.visibilityState||(a.tab_hidden=!0,a.sendViewTime()),!0===a.tab_hidden&&"visible"===document.visibilityState&&(a.tab_hidden=!1,a.canRotate()&&(a.sendViewTime(),setTimeout((function(){a.rotate()}),3e3)))},e}))}},{key:"clearListeners",value:function(){this.view_time_counter&&clearInterval(this.view_time_counter),this.hashchange_listener&&window.removeEventListener("hashchange",this.hashchange_listener),this.visibilitychange_listener}},{key:"canRotate",value:function(){return!(!this.inViewport(this.target)||this.view_time<45||this.rotations>=3)}},{key:"rotate",value:function(){if(this.canRotate())return this.clearListeners(),this.view_time=0,this.view_time_sent=!1,this.response=null,this.tab_hidden=!1,this.rotations+=1,this.load()}},{key:"inViewport",value:function(e){return!!(this.response&&this.response.view_url&&n.a.inViewport(e,-3)&&"visible"===document.visibilityState)}},{key:"fetch",value:function(){var e=this,a="ad_"+Date.now()+"_"+Math.floor(1e6*Math.random()),t=a;this.target.id&&(t=this.target.id);var o={publisher:this.publisher,ad_types:this.ad_type,div_ids:t,callback:a,keywords:this.keywords.join("|"),campaign_types:this.campaign_types.join("|"),format:"jsonp",client_version:"2.0.0",placement_index:this.index,url:(window.location.origin+window.location.pathname).slice(0,256)};this.force_ad&&(o.force_ad=this.force_ad),this.force_campaign&&(o.force_campaign=this.force_campaign),this.rotations>1&&(o.rotations=this.rotations);var n=new URLSearchParams(o),r=new URL("https://server.ethicalads.io/api/v1/decision/?"+n.toString());return new Promise((function(t,o){window[a]=function(a){if(a&&a.html&&a.view_url){e.response=a;var o=document.createElement("div");return o.innerHTML=a.html,t(o.firstChild)}return t(null)};var n=document.createElement("script");n.src=r,n.type="text/javascript",n.async=!0,n.addEventListener("error",(function(e){return t()})),document.getElementsByTagName("head")[0].appendChild(n)}))}},{key:"sendViewTime",value:function(){if(!(this.view_time<=0||this.view_time_sent)&&this.response&&this.response.view_time_url){var e=document.createElement("img");e.src=this.response.view_time_url+"?view_time="+this.view_time,e.className="ea-pixel",this.target.appendChild(e),this.view_time_sent=!0}}},{key:"detectABP",value:function(e,a){var t=!1,o=2,n=!1,r=!1;if("function"==typeof a){e+="?ch=*&rn=*";var i=11*Math.random(),d=new Image;d.onload=c,d.onerror=function(){n=!0,c()},d.src=e.replace(/\*/,1).replace(/\*/,i);var l=new Image;l.onload=c,l.onerror=function(){r=!0,c()},l.src=e.replace(/\*/,2).replace(/\*/,i),function e(a,n){0==o||n>1e3?a(0==o&&t):setTimeout((function(){e(a,2*n)}),2*n)}(a,250)}function c(){--o||(t=!n&&r)}}},{key:"detectKeywords",value:function(){if(E)return E;for(var e={},a=(document.querySelector("[role='main']")||document.querySelector("main")||document.querySelector("body")).textContent.split(/\s+/),t=/^[\('"]?(.*?)[,\.\?\!:;\)'"]?$/g,o=0;o=2})).sort((function(e,a){return e[1]>a[1]?-1:e[1]1})),r=(a.getAttribute("data-ea-campaign-types")||"").split("|").filter((function(e){return e.length>1})),i="true"===a.getAttribute("data-ea-manual"),d=a.getAttribute("data-ea-style"),l=a.getAttribute("data-ea-force-ad"),c=a.getAttribute("data-ea-force-campaign");if("image"!==o&&"text"!==o||(o+="-v1"),(a.className||"").split(" ").indexOf("loaded")>=0)return v.warn("EthicalAd already loaded."),null;var s=a.getAttribute("data-ea-placement-bottom");return s&&a.style.setProperty("bottom",s),new e(t,o,a,{keywords:n,style:d,campaign_types:r,load_manually:i,force_ad:l,force_campaign:c})}}])}();function k(){return!!(Object.entries&&window.URL&&window.URLSearchParams&&window.Promise)||(v.error("Browser does not meet ethical ad client dependencies. Not showing ads"),!1)}function x(){var e=arguments.length>0&&void 0!==arguments[0]&&arguments[0],a=document.querySelectorAll("[data-ea-publisher]"),t=Array.prototype.slice.call(a);return 0===t.length&&v.warn("No ad placements found."),Promise.all(t.map((function(a,t){var o=g.from_element(a);return o?(o.index=t,0===t&&o&&!e&&o.detectABP("https://media.ethicalads.io/abp/px.gif",(function(e){O=e,e&&v.debug("Acceptable Ads enabled. Thanks for allowing our non-tracking ads :)")})),!o||!e&&o.load_manually?null:o.load()):null})))}function w(){var e=document.querySelectorAll("[data-ea-publisher]");Array.prototype.slice.call(e).forEach((function(e){e.innerHTML="",e.classList.remove("loaded")}))}function _(){var e=document.querySelector("[data-ea-publisher]");if(e){var a=e.getAttribute("data-ea-verbosity");m.hasOwnProperty(a)&&(v.verbosity=m[a])}}var j,S,z,A=function(e){function a(){return p(this,a),i(this,a,arguments)}return function(e,a){if("function"!=typeof a&&null!==a)throw new TypeError("Super expression must either be null or a function");e.prototype=Object.create(a&&a.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),Object.defineProperty(e,"prototype",{writable:!1}),a&&s(e,a)}(a,e),h(a)}(d(Error)),O=!1,E=null;if(window.ethicalads&&console.warn("Double-loading the EthicalAds client. Use reload() instead. https://ethical-ad-client.readthedocs.io/en/latest/#single-page-apps"),k()){_();var C=new Promise((function(e){if("interactive"===document.readyState||"complete"===document.readyState)return e();document.addEventListener("DOMContentLoaded",(function(){e()}),{capture:!0,once:!0,passive:!0})}));j=new Promise((function(e){C.then((function(){x().then((function(a){e(a)})).catch((function(a){e([]),a instanceof A?v.warn(a.message):v.error(a.message)}))}))})),S=function(){v.debug("Loading placements manually"),x(!0).catch((function(e){e instanceof A?v.warn(e.message):v.error(e.message)}))},z=function(){v.debug("Reloading ad placement"),E=null,w(),x().catch((function(e){e instanceof A?v.warn(e.message):v.error(e.message)}))}}},function(e,a,t){var o=t(3),n=t(4);"string"==typeof(n=n.__esModule?n.default:n)&&(n=[[e.i,n,""]]);var r={insert:"head",singleton:!1};o(n,r);e.exports=n.locals||{}},function(e,a,t){"use strict";var o,n=function(){return void 0===o&&(o=Boolean(window&&document&&document.all&&!window.atob)),o},r=function(){var e={};return function(a){if(void 0===e[a]){var t=document.querySelector(a);if(window.HTMLIFrameElement&&t instanceof window.HTMLIFrameElement)try{t=t.contentDocument.head}catch(e){t=null}e[a]=t}return e[a]}}(),i=[];function d(e){for(var a=-1,t=0;ta>img,[data-ea-publisher]:not([data-ea-type]).loaded .ea-content>a>img,.ea-type-image .ea-content>a>img{width:var(--ea-image-width);height:auto;display:inline-block}[data-ea-type=image].loaded .ea-content>.ea-text,[data-ea-publisher]:not([data-ea-type]).loaded .ea-content>.ea-text,.ea-type-image .ea-content>.ea-text{margin-top:1em;font-size:1em;text-align:center}[data-ea-type=image].loaded .ea-callout,[data-ea-publisher]:not([data-ea-type]).loaded .ea-callout,.ea-type-image .ea-callout{max-width:var(--ea-image-placement-width);margin:0em 1em 1em 1em;padding-left:1em;padding-right:1em;font-style:italic;text-align:right}[data-ea-type=image].loaded.horizontal .ea-content,[data-ea-publisher]:not([data-ea-type]).loaded.horizontal .ea-content,.ea-type-image.horizontal .ea-content{max-width:var(--ea-image-placement-width-horizontal)}[data-ea-type=image].loaded.horizontal .ea-content>a>img,[data-ea-publisher]:not([data-ea-type]).loaded.horizontal .ea-content>a>img,.ea-type-image.horizontal .ea-content>a>img{float:left;margin-right:1em}[data-ea-type=image].loaded.horizontal .ea-content .ea-text,[data-ea-publisher]:not([data-ea-type]).loaded.horizontal .ea-content .ea-text,.ea-type-image.horizontal .ea-content .ea-text{margin-top:0em;text-align:left;overflow:auto}[data-ea-type=image].loaded.horizontal .ea-callout,[data-ea-publisher]:not([data-ea-type]).loaded.horizontal .ea-callout,.ea-type-image.horizontal .ea-callout{max-width:var(--ea-image-placement-width-horizontal);text-align:right}[data-ea-type=text].loaded,.ea-type-text{font-size:var(--ea-font-size)}[data-ea-type=text].loaded .ea-content,.ea-type-text .ea-content{text-align:left}[data-ea-type=text].loaded .ea-callout,.ea-type-text .ea-callout{margin:.5em 1em 1em 1em;padding-left:1em;padding-right:1em;text-align:right;font-style:italic}[data-ea-style=stickybox].loaded{position:fixed;bottom:20px;right:20px;z-index:100}[data-ea-style=stickybox].loaded .ea-type-image .ea-stickybox-hide{cursor:pointer;position:absolute;top:.75em;right:.75em;background-color:#fefefe;border:1px solid #088cdb;border-radius:50%;color:#088cdb;font-size:1em;text-align:center;height:1.5em;width:1.5em;line-height:1.4}[data-ea-style=stickybox].loaded .ea-type-text{display:none !important}@media(max-width: 1300px){[data-ea-style=stickybox].loaded{position:static;bottom:0;right:0;margin:auto;text-align:center}[data-ea-style=stickybox].loaded .ea-stickybox-hide{display:none}}@media(min-width: 1301px){[data-ea-style=stickybox].loaded .ea-type-image .ea-content{background:var(--ea-stylefixed-bgcolor)}[data-ea-style=stickybox].loaded.dark .ea-type-image .ea-content{background:var(--ea-stylefixed-bgcolor-dark)}}@media(min-width: 1301px)and (prefers-color-scheme: dark){[data-ea-style=stickybox].loaded.adaptive .ea-type-image .ea-content{background:var(--ea-stylefixed-bgcolor-dark)}}[data-ea-style=fixedfooter].loaded{position:fixed;bottom:0;left:0;z-index:200;width:100%;max-width:100%}[data-ea-style=fixedfooter].loaded .ea-type-text{width:100%;max-width:100%;display:flex;z-index:200;background:var(--ea-stylefixed-bgcolor)}[data-ea-style=fixedfooter].loaded .ea-type-text .ea-content{border:0px;border-radius:3px;box-shadow:none}[data-ea-style=fixedfooter].loaded .ea-type-text .ea-content{background-color:inherit;max-width:100%;margin:0;padding:1em;flex:auto}[data-ea-style=fixedfooter].loaded .ea-type-text .ea-callout{max-width:100%;margin:0;padding:1em;flex:initial}@media(max-width: 576px){[data-ea-style=fixedfooter].loaded .ea-type-text .ea-callout{display:none}}[data-ea-style=fixedfooter].loaded .ea-type-text .ea-fixedfooter-hide{cursor:pointer;color:var(--ea-color-link);padding:1em;flex:initial;margin:auto 0}[data-ea-style=fixedfooter].loaded .ea-type-text .ea-fixedfooter-hide span{padding:.25em;font-size:.8em;font-weight:bold;border:.15em solid var(--ea-color-link);border-radius:.5em;white-space:nowrap}[data-ea-style=fixedfooter].loaded .ea-type-image{display:none !important}[data-ea-style=fixedfooter].loaded.dark .ea-type-text{background:var(--ea-stylefixed-bgcolor-dark)}[data-ea-style=fixedfooter].loaded.dark .ea-type-text .ea-fixedfooter-hide span{color:var(--ea-color-link-dark);border-color:var(--ea-color-link-dark)}@media(prefers-color-scheme: dark){[data-ea-style=fixedfooter].loaded.adaptive .ea-type-text{background:var(--ea-stylefixed-bgcolor-dark)}[data-ea-style=fixedfooter].loaded.adaptive .ea-type-text .ea-fixedfooter-hide span{color:var(--ea-color-link-dark);border-color:var(--ea-color-link-dark)}}[data-ea-style=fixedheader]{height:var(--ea-fixedheader-height);width:100%;max-width:100%;background:var(--ea-stylefixed-bgcolor);border-bottom:1px solid var(--ea-background-color)}@media(max-width: 768px){[data-ea-style=fixedheader]{display:none !important}}[data-ea-style=fixedheader].loaded .ea-type-image,[data-ea-style=fixedheader].loaded .ea-type-text{width:var(--ea-container-xl);margin:0 auto;display:flex}@media(max-width: 992px){[data-ea-style=fixedheader].loaded .ea-type-image,[data-ea-style=fixedheader].loaded .ea-type-text{width:var(--ea-container-md)}}@media(max-width: 1200px){[data-ea-style=fixedheader].loaded .ea-type-image,[data-ea-style=fixedheader].loaded .ea-type-text{width:var(--ea-container-lg)}}[data-ea-style=fixedheader].loaded .ea-type-image .ea-content,[data-ea-style=fixedheader].loaded .ea-type-text .ea-content{border:0px;border-radius:3px;box-shadow:none}[data-ea-style=fixedheader].loaded .ea-type-image .ea-content,[data-ea-style=fixedheader].loaded .ea-type-text .ea-content{background-color:inherit;max-width:100%;margin:0;padding:0;flex:auto;display:flex}[data-ea-style=fixedheader].loaded .ea-type-image .ea-content .ea-text,[data-ea-style=fixedheader].loaded .ea-type-text .ea-content .ea-text{margin-top:0;padding:1em;flex:auto;text-align:left}[data-ea-style=fixedheader].loaded .ea-type-image .ea-callout,[data-ea-style=fixedheader].loaded .ea-type-text .ea-callout{max-width:100%;margin:0;padding:1em;flex:initial}@media(max-width: 576px){[data-ea-style=fixedheader].loaded .ea-type-image .ea-callout,[data-ea-style=fixedheader].loaded .ea-type-text .ea-callout{display:none}}[data-ea-style=fixedheader].loaded .ea-type-image img{width:var(--ea-image-width-xs) !important;margin:.6em}[data-ea-style=fixedheader].loaded .ea-type-image .ea-domain{display:none}[data-ea-style=fixedheader].loaded.dark{background-color:var(--ea-stylefixed-bgcolor-dark)}@media(prefers-color-scheme: dark){[data-ea-style=fixedheader].loaded.adaptive{background-color:var(--ea-stylefixed-bgcolor-dark)}}",""]),e.exports=a},function(e,a,t){"use strict";e.exports=function(e){var a=[];return a.toString=function(){return this.map((function(a){var t=function(e,a){var t=e[1]||"",o=e[3];if(!o)return t;if(a&&"function"==typeof btoa){var n=(i=o,d=btoa(unescape(encodeURIComponent(JSON.stringify(i)))),l="sourceMappingURL=data:application/json;charset=utf-8;base64,".concat(d),"/*# ".concat(l," */")),r=o.sources.map((function(e){return"/*# sourceURL=".concat(o.sourceRoot||"").concat(e," */")}));return[t].concat(r).concat([n]).join("\n")}var i,d,l;return[t].join("\n")}(a,e);return a[2]?"@media ".concat(a[2]," {").concat(t,"}"):t})).join("")},a.i=function(e,t,o){"string"==typeof e&&(e=[[null,e,""]]);var n={};if(o)for(var r=0;r=0&&t.left<=i()},e.inY=function(e,a){var t=c(e,a);return!!t&&t.bottom>=0&&t.top<=d()},e.inViewport=function(e,a){var t=c(e,a);return!!t&&t.bottom>=0&&t.right>=0&&t.top<=d()&&t.left<=i()},e},e.exports?e.exports=n():o.verge=n()},function(e,a,t){"use strict";t.r(a),t.d(a,"Placement",(function(){return g})),t.d(a,"check_dependencies",(function(){return k})),t.d(a,"load_placements",(function(){return x})),t.d(a,"unload_placements",(function(){return w})),t.d(a,"set_verbosity",(function(){return _})),t.d(a,"wait",(function(){return j})),t.d(a,"load",(function(){return S})),t.d(a,"reload",(function(){return z})),t.d(a,"uplifted",(function(){return O})),t.d(a,"detectedKeywords",(function(){return E}));var o=t(0),n=t.n(o);t(2);function r(e){return(r="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}function i(e,a,t){return a=u(a),function(e,a){if(a&&("object"==r(a)||"function"==typeof a))return a;if(void 0!==a)throw new TypeError("Derived constructors may only return object or undefined");return function(e){if(void 0===e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return e}(e)}(e,c()?Reflect.construct(a,t||[],u(e).constructor):a.apply(e,t))}function d(e){var a="function"==typeof Map?new Map:void 0;return(d=function(e){if(null===e||!function(e){try{return-1!==Function.toString.call(e).indexOf("[native code]")}catch(a){return"function"==typeof e}}(e))return e;if("function"!=typeof e)throw new TypeError("Super expression must either be null or a function");if(void 0!==a){if(a.has(e))return a.get(e);a.set(e,t)}function t(){return l(e,arguments,u(this).constructor)}return t.prototype=Object.create(e.prototype,{constructor:{value:t,enumerable:!1,writable:!0,configurable:!0}}),s(t,e)})(e)}function l(e,a,t){if(c())return Reflect.construct.apply(null,arguments);var o=[null];o.push.apply(o,a);var n=new(e.bind.apply(e,o));return t&&s(n,t.prototype),n}function c(){try{var e=!Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],(function(){})))}catch(e){}return(c=function(){return!!e})()}function s(e,a){return(s=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(e,a){return e.__proto__=a,e})(e,a)}function u(e){return(u=Object.setPrototypeOf?Object.getPrototypeOf.bind():function(e){return e.__proto__||Object.getPrototypeOf(e)})(e)}function p(e,a){if(!(e instanceof a))throw new TypeError("Cannot call a class as a function")}function f(e,a){for(var t=0;t=m.verbose){for(var a,t=arguments.length,o=new Array(t>1?t-1:0),n=1;n=m.verbose){for(var a,t=arguments.length,o=new Array(t>1?t-1:0),n=1;n=m.normal){for(var a,t=arguments.length,o=new Array(t>1?t-1:0),n=1;n=m.quiet){for(var a,t=arguments.length,o=new Array(t>1?t-1:0),n=1;n=300&&clearInterval(a.view_time_counter))}),1e3,a.target),a.hashchange_listener=function(){a.canRotate()&&(a.sendViewTime(),a.rotate())},window.addEventListener("hashchange",a.hashchange_listener),a.visibilitychange_listener=function(){"hidden"!==document.visibilityState&&"unloaded"!==document.visibilityState||(a.tab_hidden=!0,a.sendViewTime()),!0===a.tab_hidden&&"visible"===document.visibilityState&&(a.tab_hidden=!1,a.canRotate()&&(a.sendViewTime(),setTimeout((function(){a.rotate()}),3e3)))},e}))}},{key:"clearListeners",value:function(){this.view_time_counter&&clearInterval(this.view_time_counter),this.hashchange_listener&&window.removeEventListener("hashchange",this.hashchange_listener),this.visibilitychange_listener}},{key:"canRotate",value:function(){return!(!this.inViewport(this.target)||this.view_time<45||this.rotations>=3)}},{key:"rotate",value:function(){if(this.canRotate())return this.clearListeners(),this.view_time=0,this.view_time_sent=!1,this.response=null,this.tab_hidden=!1,this.rotations+=1,this.load()}},{key:"inViewport",value:function(e){return!!(this.response&&this.response.view_url&&n.a.inViewport(e,-3)&&"visible"===document.visibilityState)}},{key:"fetch",value:function(){var e=this,a="ad_"+Date.now()+"_"+Math.floor(1e6*Math.random()),t=a;this.target.id&&(t=this.target.id);var o={publisher:this.publisher,ad_types:this.ad_type,div_ids:t,callback:a,keywords:this.keywords.join("|"),campaign_types:this.campaign_types.join("|"),format:"jsonp",client_version:"2.0.0",placement_index:this.index,url:(window.location.origin+window.location.pathname).slice(0,256)};this.force_ad&&(o.force_ad=this.force_ad),this.force_campaign&&(o.force_campaign=this.force_campaign),this.rotations>1&&(o.rotations=this.rotations);var n=new URLSearchParams(o),r=new URL("https://server.ethicalads.io/api/v1/decision/?"+n.toString());return new Promise((function(t,o){window[a]=function(a){if(a&&a.html&&a.view_url){e.response=a;var o=document.createElement("div");return o.innerHTML=a.html,t(o.firstChild)}return t(null)};var n=document.createElement("script");n.src=r,n.type="text/javascript",n.async=!0,n.addEventListener("error",(function(e){return t()})),document.getElementsByTagName("head")[0].appendChild(n)}))}},{key:"sendViewTime",value:function(){if(!(this.view_time<=0||this.view_time_sent)&&this.response&&this.response.view_time_url){var e=document.createElement("img");e.src=this.response.view_time_url+"?view_time="+this.view_time,e.className="ea-pixel",this.target.appendChild(e),this.view_time_sent=!0}}},{key:"detectABP",value:function(e,a){var t=!1,o=2,n=!1,r=!1;if("function"==typeof a){e+="?ch=*&rn=*";var i=11*Math.random(),d=new Image;d.onload=c,d.onerror=function(){n=!0,c()},d.src=e.replace(/\*/,1).replace(/\*/,i);var l=new Image;l.onload=c,l.onerror=function(){r=!0,c()},l.src=e.replace(/\*/,2).replace(/\*/,i),function e(a,n){0==o||n>1e3?a(0==o&&t):setTimeout((function(){e(a,2*n)}),2*n)}(a,250)}function c(){--o||(t=!n&&r)}}},{key:"detectKeywords",value:function(){if(E)return E;for(var e={},a=(document.querySelector("[role='main']")||document.querySelector("main")||document.querySelector("body")).textContent.split(/\s+/),t=/^[\('"]?(.*?)[,\.\?\!:;\)'"]?$/g,o=0;o=2})).sort((function(e,a){return e[1]>a[1]?-1:e[1]1})),r=(a.getAttribute("data-ea-campaign-types")||"").split("|").filter((function(e){return e.length>1})),i="true"===a.getAttribute("data-ea-manual"),d=a.getAttribute("data-ea-style"),l=a.getAttribute("data-ea-force-ad"),c=a.getAttribute("data-ea-force-campaign");if("image"!==o&&"text"!==o||(o+="-v1"),(a.className||"").split(" ").indexOf("loaded")>=0)return v.warn("EthicalAd already loaded."),null;var s=a.getAttribute("data-ea-placement-bottom");return s&&a.style.setProperty("bottom",s),new e(t,o,a,{keywords:n,style:d,campaign_types:r,load_manually:i,force_ad:l,force_campaign:c})}}])}();function k(){return!!(Object.entries&&window.URL&&window.URLSearchParams&&window.Promise)||(v.error("Browser does not meet ethical ad client dependencies. Not showing ads"),!1)}function x(){var e=arguments.length>0&&void 0!==arguments[0]&&arguments[0],a=document.querySelectorAll("[data-ea-publisher]"),t=Array.prototype.slice.call(a);return 0===t.length&&v.warn("No ad placements found."),Promise.all(t.map((function(a,t){var o=g.from_element(a);return o?(o.index=t,0===t&&o&&!e&&o.detectABP("https://media.ethicalads.io/abp/px.gif",(function(e){O=e,e&&v.debug("Acceptable Ads enabled. Thanks for allowing our non-tracking ads :)")})),!o||!e&&o.load_manually?null:o.load()):null})))}function w(){var e=document.querySelectorAll("[data-ea-publisher]");Array.prototype.slice.call(e).forEach((function(e){e.innerHTML="",e.classList.remove("loaded")}))}function _(){var e=document.querySelector("[data-ea-publisher]");if(e){var a=e.getAttribute("data-ea-verbosity");m.hasOwnProperty(a)&&(v.verbosity=m[a])}}var j,S,z,A=function(e){function a(){return p(this,a),i(this,a,arguments)}return function(e,a){if("function"!=typeof a&&null!==a)throw new TypeError("Super expression must either be null or a function");e.prototype=Object.create(a&&a.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),Object.defineProperty(e,"prototype",{writable:!1}),a&&s(e,a)}(a,e),h(a)}(d(Error)),O=!1,E=null;if(window.ethicalads&&console.warn("Double-loading the EthicalAds client. Use reload() instead. https://ethical-ad-client.readthedocs.io/en/latest/#single-page-apps"),k()){_();var C=new Promise((function(e){if("interactive"===document.readyState||"complete"===document.readyState)return e();document.addEventListener("DOMContentLoaded",(function(){e()}),{capture:!0,once:!0,passive:!0})}));j=new Promise((function(e){C.then((function(){x().then((function(a){e(a.filter((function(e){return null!=e})))})).catch((function(a){e([]),a instanceof A?v.warn(a.message):v.error(a.message)}))}))})),S=function(){v.debug("Loading placements manually"),x(!0).catch((function(e){e instanceof A?v.warn(e.message):v.error(e.message)}))},z=function(){v.debug("Reloading ad placement"),E=null,w(),x().catch((function(e){e instanceof A?v.warn(e.message):v.error(e.message)}))}}},function(e,a,t){var o=t(3),n=t(4);"string"==typeof(n=n.__esModule?n.default:n)&&(n=[[e.i,n,""]]);var r={insert:"head",singleton:!1};o(n,r);e.exports=n.locals||{}},function(e,a,t){"use strict";var o,n=function(){return void 0===o&&(o=Boolean(window&&document&&document.all&&!window.atob)),o},r=function(){var e={};return function(a){if(void 0===e[a]){var t=document.querySelector(a);if(window.HTMLIFrameElement&&t instanceof window.HTMLIFrameElement)try{t=t.contentDocument.head}catch(e){t=null}e[a]=t}return e[a]}}(),i=[];function d(e){for(var a=-1,t=0;ta>img,[data-ea-publisher]:not([data-ea-type]).loaded .ea-content>a>img,.ea-type-image .ea-content>a>img{width:var(--ea-image-width);height:auto;display:inline-block}[data-ea-type=image].loaded .ea-content>.ea-text,[data-ea-publisher]:not([data-ea-type]).loaded .ea-content>.ea-text,.ea-type-image .ea-content>.ea-text{margin-top:1em;font-size:1em;text-align:center}[data-ea-type=image].loaded .ea-callout,[data-ea-publisher]:not([data-ea-type]).loaded .ea-callout,.ea-type-image .ea-callout{max-width:var(--ea-image-placement-width);margin:0em 1em 1em 1em;padding-left:1em;padding-right:1em;font-style:italic;text-align:right}[data-ea-type=image].loaded.horizontal .ea-content,[data-ea-publisher]:not([data-ea-type]).loaded.horizontal .ea-content,.ea-type-image.horizontal .ea-content{max-width:var(--ea-image-placement-width-horizontal)}[data-ea-type=image].loaded.horizontal .ea-content>a>img,[data-ea-publisher]:not([data-ea-type]).loaded.horizontal .ea-content>a>img,.ea-type-image.horizontal .ea-content>a>img{float:left;margin-right:1em}[data-ea-type=image].loaded.horizontal .ea-content .ea-text,[data-ea-publisher]:not([data-ea-type]).loaded.horizontal .ea-content .ea-text,.ea-type-image.horizontal .ea-content .ea-text{margin-top:0em;text-align:left;overflow:auto}[data-ea-type=image].loaded.horizontal .ea-callout,[data-ea-publisher]:not([data-ea-type]).loaded.horizontal .ea-callout,.ea-type-image.horizontal .ea-callout{max-width:var(--ea-image-placement-width-horizontal);text-align:right}[data-ea-type=text].loaded,.ea-type-text{font-size:var(--ea-font-size)}[data-ea-type=text].loaded .ea-content,.ea-type-text .ea-content{text-align:left}[data-ea-type=text].loaded .ea-callout,.ea-type-text .ea-callout{margin:.5em 1em 1em 1em;padding-left:1em;padding-right:1em;text-align:right;font-style:italic}[data-ea-style=stickybox].loaded{position:fixed;bottom:20px;right:20px;z-index:100}[data-ea-style=stickybox].loaded .ea-type-image .ea-stickybox-hide{cursor:pointer;position:absolute;top:.75em;right:.75em;background-color:#fefefe;border:1px solid #088cdb;border-radius:50%;color:#088cdb;font-size:1em;text-align:center;height:1.5em;width:1.5em;line-height:1.4}[data-ea-style=stickybox].loaded .ea-type-text{display:none !important}@media(max-width: 1300px){[data-ea-style=stickybox].loaded{position:static;bottom:0;right:0;margin:auto;text-align:center}[data-ea-style=stickybox].loaded .ea-stickybox-hide{display:none}}@media(min-width: 1301px){[data-ea-style=stickybox].loaded .ea-type-image .ea-content{background:var(--ea-stylefixed-bgcolor)}[data-ea-style=stickybox].loaded.dark .ea-type-image .ea-content{background:var(--ea-stylefixed-bgcolor-dark)}}@media(min-width: 1301px)and (prefers-color-scheme: dark){[data-ea-style=stickybox].loaded.adaptive .ea-type-image .ea-content{background:var(--ea-stylefixed-bgcolor-dark)}}[data-ea-style=fixedfooter].loaded{position:fixed;bottom:0;left:0;z-index:200;width:100%;max-width:100%}[data-ea-style=fixedfooter].loaded .ea-type-text{width:100%;max-width:100%;display:flex;z-index:200;background:var(--ea-stylefixed-bgcolor)}[data-ea-style=fixedfooter].loaded .ea-type-text .ea-content{border:0px;border-radius:3px;box-shadow:none}[data-ea-style=fixedfooter].loaded .ea-type-text .ea-content{background-color:inherit;max-width:100%;margin:0;padding:1em;flex:auto}[data-ea-style=fixedfooter].loaded .ea-type-text .ea-callout{max-width:100%;margin:0;padding:1em;flex:initial}@media(max-width: 576px){[data-ea-style=fixedfooter].loaded .ea-type-text .ea-callout{display:none}}[data-ea-style=fixedfooter].loaded .ea-type-text .ea-fixedfooter-hide{cursor:pointer;color:var(--ea-color-link);padding:1em;flex:initial;margin:auto 0}[data-ea-style=fixedfooter].loaded .ea-type-text .ea-fixedfooter-hide span{padding:.25em;font-size:.8em;font-weight:bold;border:.15em solid var(--ea-color-link);border-radius:.5em;white-space:nowrap}[data-ea-style=fixedfooter].loaded .ea-type-image{display:none !important}[data-ea-style=fixedfooter].loaded.dark .ea-type-text{background:var(--ea-stylefixed-bgcolor-dark)}[data-ea-style=fixedfooter].loaded.dark .ea-type-text .ea-fixedfooter-hide span{color:var(--ea-color-link-dark);border-color:var(--ea-color-link-dark)}@media(prefers-color-scheme: dark){[data-ea-style=fixedfooter].loaded.adaptive .ea-type-text{background:var(--ea-stylefixed-bgcolor-dark)}[data-ea-style=fixedfooter].loaded.adaptive .ea-type-text .ea-fixedfooter-hide span{color:var(--ea-color-link-dark);border-color:var(--ea-color-link-dark)}}[data-ea-style=fixedheader]{height:var(--ea-fixedheader-height);width:100%;max-width:100%;background:var(--ea-stylefixed-bgcolor);border-bottom:1px solid var(--ea-background-color)}@media(max-width: 768px){[data-ea-style=fixedheader]{display:none !important}}[data-ea-style=fixedheader].loaded .ea-type-image,[data-ea-style=fixedheader].loaded .ea-type-text{width:var(--ea-container-xl);margin:0 auto;display:flex}@media(max-width: 992px){[data-ea-style=fixedheader].loaded .ea-type-image,[data-ea-style=fixedheader].loaded .ea-type-text{width:var(--ea-container-md)}}@media(max-width: 1200px){[data-ea-style=fixedheader].loaded .ea-type-image,[data-ea-style=fixedheader].loaded .ea-type-text{width:var(--ea-container-lg)}}[data-ea-style=fixedheader].loaded .ea-type-image .ea-content,[data-ea-style=fixedheader].loaded .ea-type-text .ea-content{border:0px;border-radius:3px;box-shadow:none}[data-ea-style=fixedheader].loaded .ea-type-image .ea-content,[data-ea-style=fixedheader].loaded .ea-type-text .ea-content{background-color:inherit;max-width:100%;margin:0;padding:0;flex:auto;display:flex}[data-ea-style=fixedheader].loaded .ea-type-image .ea-content .ea-text,[data-ea-style=fixedheader].loaded .ea-type-text .ea-content .ea-text{margin-top:0;padding:1em;flex:auto;text-align:left}[data-ea-style=fixedheader].loaded .ea-type-image .ea-callout,[data-ea-style=fixedheader].loaded .ea-type-text .ea-callout{max-width:100%;margin:0;padding:1em;flex:initial}@media(max-width: 576px){[data-ea-style=fixedheader].loaded .ea-type-image .ea-callout,[data-ea-style=fixedheader].loaded .ea-type-text .ea-callout{display:none}}[data-ea-style=fixedheader].loaded .ea-type-image img{width:var(--ea-image-width-xs) !important;margin:.6em}[data-ea-style=fixedheader].loaded .ea-type-image .ea-domain{display:none}[data-ea-style=fixedheader].loaded.dark{background-color:var(--ea-stylefixed-bgcolor-dark)}@media(prefers-color-scheme: dark){[data-ea-style=fixedheader].loaded.adaptive{background-color:var(--ea-stylefixed-bgcolor-dark)}}",""]),e.exports=a},function(e,a,t){"use strict";e.exports=function(e){var a=[];return a.toString=function(){return this.map((function(a){var t=function(e,a){var t=e[1]||"",o=e[3];if(!o)return t;if(a&&"function"==typeof btoa){var n=(i=o,d=btoa(unescape(encodeURIComponent(JSON.stringify(i)))),l="sourceMappingURL=data:application/json;charset=utf-8;base64,".concat(d),"/*# ".concat(l," */")),r=o.sources.map((function(e){return"/*# sourceURL=".concat(o.sourceRoot||"").concat(e," */")}));return[t].concat(r).concat([n]).join("\n")}var i,d,l;return[t].join("\n")}(a,e);return a[2]?"@media ".concat(a[2]," {").concat(t,"}"):t})).join("")},a.i=function(e,t,o){"string"==typeof e&&(e=[[null,e,""]]);var n={};if(o)for(var r=0;r { load_placements() .then((placements) => { - resolve(placements); + resolve(placements.filter((p) => p != null)); }) .catch((err) => { resolve([]); diff --git a/tests/missing-placement.test.html b/tests/missing-placement.test.html index 738c0ff..26f54de 100644 --- a/tests/missing-placement.test.html +++ b/tests/missing-placement.test.html @@ -15,7 +15,7 @@ describe("EthicalAds library", () => { it("skips manual placements", async () => { const placements = await wait; - expect(placements).to.be.an("array").that.includes(null); + expect(placements.length).equals(0); }); it("can force load manual placements", async () => { const placements = await load_placements(true);