├── .babelrc ├── .editorconfig ├── .eslintrc.json ├── .gitattributes ├── .github └── workflows │ └── npmpublish.yml ├── .gitignore ├── .npmignore ├── .nvmrc ├── .travis.yml ├── LICENSE ├── Readme.md ├── js ├── adslot.js ├── dfpslotsprovider.js ├── index.js ├── manager.js └── utils.js ├── karma.conf.js ├── lib ├── adslot.js ├── dfpslotsprovider.js ├── index.js ├── manager.js └── utils.js ├── package-lock.json ├── package.json └── spec ├── test-adslot.js ├── test-dfpslotsprovider.js └── test-manager.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/env", "@babel/react"], 3 | "plugins": ["@babel/plugin-proposal-class-properties"] 4 | } 5 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb", 3 | "plugins": [ 4 | "babel", 5 | "react" 6 | ], 7 | "globals": { 8 | "googletag": true, 9 | "describe": true, 10 | "expect": true, 11 | "it": true, 12 | "beforeAll": true, 13 | "afterAll": true, 14 | "beforeEach": true, 15 | "afterEach": true, 16 | "window": true, 17 | "document": true 18 | }, 19 | "parser": "babel-eslint", 20 | "parserOptions": { 21 | "ecmaVersion": 6 22 | }, 23 | "rules": { 24 | "prefer-arrow-callback": 0, 25 | "react/jsx-filename-extension": 0, 26 | "react/no-unused-prop-types": 0, 27 | "react/require-default-props": 0, 28 | "react/forbid-prop-types": 0, 29 | "import/prefer-default-export": 0, 30 | "class-methods-use-this": 0, 31 | "no-plusplus": 0 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto -------------------------------------------------------------------------------- /.github/workflows/npmpublish.yml: -------------------------------------------------------------------------------- 1 | name: Node.js Package 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v1 13 | - uses: actions/setup-node@v1 14 | with: 15 | node-version: 8.12 16 | - run: npm install 17 | - run: npm run dist 18 | 19 | publish-npm: 20 | needs: build 21 | runs-on: ubuntu-latest 22 | steps: 23 | - uses: actions/checkout@v1 24 | - uses: actions/setup-node@v1 25 | with: 26 | node-version: 8.12 27 | registry-url: https://registry.npmjs.org/ 28 | - run: npm publish 29 | env: 30 | NODE_AUTH_TOKEN: ${{secrets.NPM_AUTH_TOKEN}} 31 | 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Git ignore for NGS projects 2 | 3 | # Ignore hidden folders # 4 | # This takes care of .tmp, .sass-cache, and many others # 5 | .*/ 6 | 7 | # Ignore OS generated files # 8 | .DS_Store* 9 | ehthumbs.db 10 | Icon? 11 | Thumbs.db 12 | 13 | # Ignore packages # 14 | *.7z 15 | *.dmg 16 | *.gz 17 | *.iso 18 | *.jar 19 | *.rar 20 | *.tar 21 | *.zip 22 | 23 | # Ignore all log files 24 | *.log 25 | 26 | dist 27 | node_modules 28 | bower_components 29 | .tmp 30 | 31 | # ignore test coverage results 32 | coverage 33 | 34 | # Ignore Compass Cache 35 | .sass-cache 36 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | examples/ 2 | spec/ 3 | karma.conf.js 4 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 8.12 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | script: 3 | - nvm use 4 | - npm install 5 | - npm run eslint 6 | - npm run dist 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2016 Jonatan Alexis Anauati 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | 6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # React DFP · [![Build Status](https://travis-ci.org/jaanauati/react-dfp.svg?branch=master)](https://travis-ci.org/jaanauati/react-dfp) [![](https://img.shields.io/npm/dm/react-dfp.svg?label=DL)](https://github.com/jaanauati/react-dfp) [![Minizipped size](https://img.shields.io/bundlephobia/minzip/react-dfp.svg)](https://github.com/jaanauati/react-dfp) [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/jaanauati/react-dfp/blob/master/LICENSE) [![](https://img.shields.io/npm/dependency-version/react-dfp/peer/react.svg)](https://github.com/jaanauati/react-dfp/blob/master/LICENSE) 2 | 3 | A React implementation of the google [DFP](https://developers.google.com/doubleclick-gpt/reference "GPT Reference") API. This package is inspired in the awesome library [jquery.dfp](https://github.com/coop182/jquery.dfp.js), and aims to provide its same ease of usage but, of course, taking into consideration the react concepts & lifecycle features. 4 | 5 | ## Installation: 6 | 7 | To install just run the following command (no other dependencies are required): 8 | 9 | ```bash 10 | npm install --save react-dfp 11 | ``` 12 | 13 | or 14 | 15 | ```bash 16 | yarn add react-dfp 17 | ``` 18 | 19 | You can find more details in the [React-dfp site](https://react-dfp.surge.sh/). 20 | 21 | ## Getting started 22 | 23 | React-dfp has been designed in a very React-ish way, its main goal is to be able to serve DFP ads in your React application just using React components, which means that you won't have to programmatically perform any initialization call when your page loads. 24 | 25 | Here a quick example, notice that ads will render in your page as soon as your component is mounted, through its components (DFPSlotsProvider and AdSlot), react-dfp is making a full abstraction of the google dfp/gpt API. 26 | 27 | ```javascript 28 | import React, { Component } from 'react'; 29 | import { DFPSlotsProvider, AdSlot } from 'react-dfp'; 30 | ... 31 | class Page extends Component { 32 | render() { 33 | ... 34 | 35 | return ( 36 | 37 | ... 38 | 39 | ... 40 | /* you can override the props */ 41 | 42 | ... 43 |
44 | ... 45 | 46 | ... 47 |
48 | ... 49 |
50 | ); 51 | } 52 | } 53 | ``` 54 | 55 | ## Examples 56 | 57 | See the [React-dfp site](https://react-dfp.surge.sh) for more examples (basic example, how to have refreshable ads, etc). 58 | 59 | ## Documentation 60 | 61 | You can find the React-dfp documentation [on the website](https://react-dfp.surge.sh). The site is divided into many sections that describe each one the Components, properties and also the DFPManager API (in case you need to use this one manually). 62 | 63 | The website is also full of live/working examples. 64 | 65 | You can find the source code of the website here: https://github.com/jaanauati/react-dfp-website. 66 | 67 | ## Wanna help? 68 | 69 | I certainly know that test cases need to be improved, but, as long as your syntax is clean, submit test cases and, of course, all the interfaces are kept working, all kind of contribution is welcome. 70 | 71 | ## Complaints. 72 | 73 | Pull requests are welcome 🍻. 74 | -------------------------------------------------------------------------------- /js/adslot.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import DFPManager from './manager'; 4 | import { Context } from './dfpslotsprovider'; 5 | 6 | let dynamicAdCount = 0; 7 | 8 | export class AdSlot extends React.Component { 9 | static propTypes = { 10 | dfpNetworkId: PropTypes.string, 11 | adUnit: PropTypes.string, 12 | sizes: PropTypes.arrayOf( 13 | PropTypes.oneOfType([ 14 | PropTypes.arrayOf(PropTypes.number), 15 | PropTypes.string, 16 | ]), 17 | ), 18 | renderOutOfThePage: PropTypes.bool, 19 | sizeMapping: PropTypes.arrayOf(PropTypes.object), 20 | fetchNow: PropTypes.bool, 21 | adSenseAttributes: PropTypes.object, 22 | targetingArguments: PropTypes.object, 23 | onSlotRender: PropTypes.func, 24 | onSlotRegister: PropTypes.func, 25 | onSlotIsViewable: PropTypes.func, 26 | onSlotVisibilityChanged: PropTypes.func, 27 | shouldRefresh: PropTypes.func, 28 | slotId: PropTypes.string, 29 | className: PropTypes.string, 30 | }; 31 | 32 | static defaultProps = { 33 | fetchNow: false, 34 | }; 35 | 36 | constructor(props) { 37 | super(props); 38 | this.doRegisterSlot = this.doRegisterSlot.bind(this); 39 | this.generateSlotId = this.generateSlotId.bind(this); 40 | this.getSlotId = this.getSlotId.bind(this); 41 | this.mapContextToAdSlotProps = this.mapContextToAdSlotProps.bind(this); 42 | this.slotShouldRefresh = this.slotShouldRefresh.bind(this); 43 | this.slotRenderEnded = this.slotRenderEnded.bind(this); 44 | this.slotRegisterCallback = this.slotRegisterCallback.bind(this); 45 | this.slotIsViewable = this.slotIsViewable.bind(this); 46 | this.slotVisibilityChanged = this.slotVisibilityChanged.bind(this); 47 | this.getClasses = this.getClasses.bind(this); 48 | this.state = { 49 | slotId: this.props.slotId || null, 50 | className: this.props.className || '', 51 | }; 52 | this.adElementRef = React.createRef ? React.createRef() : (element) => { 53 | this.adElementRef = element; 54 | }; 55 | } 56 | 57 | componentDidMount() { 58 | // register this ad-unit in the , when available. 59 | if (this.context !== undefined && this.context.newSlotCallback) { 60 | this.context.newSlotCallback(); 61 | } 62 | this.registerSlot(); 63 | } 64 | 65 | componentWillUnmount() { 66 | this.unregisterSlot(); 67 | } 68 | 69 | getSlotId() { 70 | return this.props.slotId || this.state.slotId; 71 | } 72 | 73 | getClasses() { 74 | const baseClass = 'adunitContainer'; 75 | const extraClasses = this.state.className.split(' '); 76 | extraClasses.push(baseClass); 77 | return extraClasses; 78 | } 79 | 80 | generateSlotId() { 81 | return `adSlot-${dynamicAdCount++}`; 82 | } 83 | 84 | mapContextToAdSlotProps() { 85 | const context = this.context; 86 | const mappedProps = {}; 87 | if (context.dfpNetworkId !== undefined) { 88 | mappedProps.dfpNetworkId = context.dfpNetworkId; 89 | } 90 | if (context.dfpAdUnit !== undefined) { 91 | mappedProps.adUnit = context.dfpAdUnit; 92 | } 93 | if (context.dfpSizeMapping !== undefined) { 94 | mappedProps.sizeMapping = context.dfpSizeMapping; 95 | } 96 | if (context.dfpTargetingArguments !== undefined) { 97 | mappedProps.targetingArguments = context.dfpTargetingArguments; 98 | } 99 | return mappedProps; 100 | } 101 | 102 | doRegisterSlot() { 103 | DFPManager.registerSlot({ 104 | ...this.mapContextToAdSlotProps(), 105 | ...this.props, 106 | ...this.state, 107 | slotShouldRefresh: this.slotShouldRefresh, 108 | }); 109 | if (this.props.fetchNow === true) { 110 | DFPManager.load(this.getSlotId()); 111 | } 112 | DFPManager.attachSlotRenderEnded(this.slotRenderEnded); 113 | DFPManager.attachSlotIsViewable(this.slotIsViewable); 114 | DFPManager.attachSlotVisibilityChanged(this.slotVisibilityChanged); 115 | 116 | this.slotRegisterCallback(); 117 | } 118 | 119 | registerSlot() { 120 | if (this.state.slotId === null) { 121 | this.setState({ 122 | slotId: this.generateSlotId(), 123 | }, this.doRegisterSlot); 124 | } else { 125 | this.doRegisterSlot(); 126 | } 127 | } 128 | 129 | unregisterSlot() { 130 | DFPManager.unregisterSlot({ 131 | ...this.mapContextToAdSlotProps(), 132 | ...this.props, 133 | ...this.state, 134 | }); 135 | DFPManager.detachSlotRenderEnded(this.slotRenderEnded); 136 | DFPManager.detachSlotIsViewable(this.slotIsViewable); 137 | DFPManager.detachSlotVisibilityChanged(this.slotVisibilityChanged); 138 | } 139 | 140 | slotRenderEnded(eventData) { 141 | if (eventData.slotId === this.getSlotId()) { 142 | if (this.props.onSlotRender !== undefined) { 143 | // now that slot has rendered we have access to the ref 144 | const params = { 145 | ...eventData, 146 | adElementRef: this.adElementRef, 147 | }; 148 | this.props.onSlotRender(params); 149 | } 150 | } 151 | } 152 | 153 | slotRegisterCallback() { 154 | if (typeof this.props.onSlotRegister === 'function') { 155 | this.props.onSlotRegister({ 156 | slotId: this.getSlotId(), 157 | sizes: this.props.sizes, 158 | slotCount: dynamicAdCount, 159 | adElementRef: this.adElementRef, 160 | }); 161 | } 162 | } 163 | 164 | slotIsViewable(eventData) { 165 | if (eventData.slotId === this.getSlotId()) { 166 | if (this.props.onSlotIsViewable !== undefined) { 167 | this.props.onSlotIsViewable(eventData); 168 | } 169 | } 170 | } 171 | 172 | slotVisibilityChanged(eventData) { 173 | if (eventData.slotId === this.getSlotId()) { 174 | if (this.props.onSlotVisibilityChanged !== undefined) { 175 | this.props.onSlotVisibilityChanged(eventData); 176 | } 177 | } 178 | } 179 | 180 | slotShouldRefresh() { 181 | let r = true; 182 | if (this.props.shouldRefresh !== undefined) { 183 | r = this.props.shouldRefresh({ 184 | ...this.mapContextToAdSlotProps(), 185 | ...this.props, 186 | slotId: this.getSlotId(), 187 | }); 188 | } 189 | return r; 190 | } 191 | 192 | render() { 193 | const { slotId } = this.state; 194 | const props = { className: 'adBox' }; 195 | if (slotId !== null) { 196 | props.id = slotId; 197 | } 198 | 199 | return ( 200 |
201 |
202 |
203 | ); 204 | } 205 | } 206 | 207 | if (Context === null) { 208 | // React < 16.3 209 | AdSlot.contextTypes = { 210 | dfpNetworkId: PropTypes.string, 211 | dfpAdUnit: PropTypes.string, 212 | dfpSizeMapping: PropTypes.arrayOf(PropTypes.object), 213 | dfpTargetingArguments: PropTypes.object, 214 | newSlotCallback: PropTypes.func, 215 | }; 216 | } else { 217 | AdSlot.contextType = Context; 218 | } 219 | 220 | 221 | export default AdSlot; 222 | -------------------------------------------------------------------------------- /js/dfpslotsprovider.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import DFPManager from './manager'; 4 | 5 | // React.createContext is undefined for React < 16.3 6 | export const Context = React.createContext ? React.createContext({ 7 | dfpNetworkId: null, 8 | dfpAdUnit: null, 9 | dfpSizeMapping: null, 10 | dfpTargetingArguments: null, 11 | newSlotCallback: null, 12 | }) : null; 13 | 14 | export default class DFPSlotsProvider extends React.Component { 15 | static propTypes = { 16 | children: PropTypes.oneOfType([ 17 | PropTypes.element, 18 | PropTypes.array, 19 | ]).isRequired, 20 | autoLoad: PropTypes.bool, 21 | autoReload: PropTypes.shape({ 22 | dfpNetworkId: PropTypes.bool, 23 | personalizedAds: PropTypes.bool, 24 | cookieOption: PropTypes.bool, 25 | singleRequest: PropTypes.bool, 26 | disableInitialLoad: PropTypes.bool, 27 | adUnit: PropTypes.bool, 28 | sizeMapping: PropTypes.bool, 29 | adSenseAttributes: PropTypes.bool, 30 | targetingArguments: PropTypes.bool, 31 | collapseEmptyDivs: PropTypes.bool, 32 | lazyLoad: PropTypes.bool, 33 | }), 34 | dfpNetworkId: PropTypes.string.isRequired, 35 | personalizedAds: PropTypes.bool, 36 | cookieOption: PropTypes.bool, 37 | singleRequest: PropTypes.bool, 38 | disableInitialLoad: PropTypes.bool, 39 | adUnit: PropTypes.string, 40 | sizeMapping: PropTypes.arrayOf(PropTypes.object), 41 | adSenseAttributes: PropTypes.object, 42 | targetingArguments: PropTypes.object, 43 | collapseEmptyDivs: PropTypes.oneOfType([ 44 | PropTypes.bool, 45 | PropTypes.object, 46 | ]), 47 | adSenseAttrs: PropTypes.object, 48 | lazyLoad: PropTypes.oneOfType([ 49 | PropTypes.bool, 50 | PropTypes.shape({ 51 | fetchMarginPercent: PropTypes.number, 52 | renderMarginPercent: PropTypes.number, 53 | mobileScaling: PropTypes.number, 54 | }), 55 | ]), 56 | limitedAds: PropTypes.bool, 57 | }; 58 | 59 | static defaultProps = { 60 | autoLoad: true, 61 | autoReload: { 62 | dfpNetworkId: false, 63 | personalizedAds: false, 64 | cookieOption: false, 65 | singleRequest: false, 66 | disableInitialLoad: false, 67 | adUnit: false, 68 | sizeMapping: false, 69 | adSenseAttributes: false, 70 | targetingArguments: false, 71 | collapseEmptyDivs: false, 72 | lazyLoad: false, 73 | }, 74 | personalizedAds: true, 75 | cookieOption: true, 76 | singleRequest: true, 77 | disableInitialLoad: false, 78 | collapseEmptyDivs: null, 79 | lazyLoad: false, 80 | limitedAds: false, 81 | }; 82 | 83 | constructor(props) { 84 | super(props); 85 | this.loadAdsIfPossible = this.loadAdsIfPossible.bind(this); 86 | this.newSlotCallback = this.newSlotCallback.bind(this); 87 | this.applyConfigs = this.applyConfigs.bind(this); 88 | this.shouldReloadConfig = this.shouldReloadConfig.bind(this); 89 | this.attachLoadCallback = this.attachLoadCallback.bind(this); 90 | this.getContextValue = this.getContextValue.bind(this); 91 | this.loadAlreadyCalled = false; 92 | this.loadCallbackAttached = false; 93 | this.shouldReloadAds = false; 94 | this.totalSlots = 0; 95 | this.contextValue = {}; 96 | if (Context === null) { 97 | this.getChildContext = () => this.getContextValue(); 98 | } 99 | } 100 | 101 | componentDidMount() { 102 | this.applyConfigs(); 103 | if (this.props.autoLoad && !this.loadAdsIfPossible()) { 104 | this.attachLoadCallback(); 105 | } 106 | } 107 | 108 | shouldComponentUpdate(nextProps) { 109 | this.shouldReloadAds = this.shouldReloadConfig(nextProps); 110 | if (nextProps.children !== this.props.children) { 111 | return true; 112 | } 113 | if (nextProps.autoLoad && !this.props.autoLoad) { 114 | return true; 115 | } 116 | return this.shouldReloadAds; 117 | } 118 | 119 | componentDidUpdate() { 120 | this.applyConfigs(); 121 | if (this.props.autoLoad) { 122 | if (this.loadAlreadyCalled) { 123 | if (this.shouldReloadAds) { 124 | DFPManager.reload(); 125 | } 126 | } else if (!this.loadAdsIfPossible()) { 127 | this.attachLoadCallback(); 128 | } 129 | } 130 | this.shouldReloadAds = false; 131 | } 132 | 133 | getContextValue() { 134 | const { 135 | props: { 136 | dfpNetworkId, 137 | adUnit: dfpAdUnit, 138 | sizeMapping: dfpSizeMapping, 139 | targetingArguments: dfpTargetingArguments, 140 | }, 141 | contextValue: { 142 | dfpNetworkId: ctxDfpNetworkId, 143 | adUnit: ctxDfpAdUnit, 144 | sizeMapping: ctxDfpSizeMapping, 145 | targetingArguments: ctxDfpTargetingArguments, 146 | }, 147 | } = this; 148 | // performance: update context value object only when any of its 149 | // props is updated. 150 | if ( 151 | dfpNetworkId !== ctxDfpNetworkId || 152 | dfpAdUnit !== ctxDfpAdUnit || 153 | dfpSizeMapping !== ctxDfpSizeMapping || 154 | dfpTargetingArguments !== ctxDfpTargetingArguments 155 | ) { 156 | this.contextValue = { 157 | dfpNetworkId, 158 | dfpAdUnit, 159 | dfpSizeMapping, 160 | dfpTargetingArguments, 161 | newSlotCallback: this.newSlotCallback, 162 | }; 163 | } 164 | return this.contextValue; 165 | } 166 | 167 | applyConfigs() { 168 | DFPManager.configurePersonalizedAds(this.props.personalizedAds); 169 | DFPManager.configureCookieOption(this.props.cookieOption); 170 | DFPManager.configureSingleRequest(this.props.singleRequest); 171 | DFPManager.configureDisableInitialLoad(this.props.disableInitialLoad); 172 | DFPManager.configureLazyLoad( 173 | !!this.props.lazyLoad, 174 | typeof this.props.lazyLoad === 'boolean' ? null : this.props.lazyLoad, 175 | ); 176 | DFPManager.setAdSenseAttributes(this.props.adSenseAttributes); 177 | DFPManager.setCollapseEmptyDivs(this.props.collapseEmptyDivs); 178 | DFPManager.configureLimitedAds(this.props.limitedAds); 179 | } 180 | 181 | attachLoadCallback() { 182 | if (this.loadCallbackAttached === false) { 183 | DFPManager.on('slotRegistered', this.loadAdsIfPossible); 184 | this.loadCallbackAttached = true; 185 | return true; 186 | } 187 | return false; 188 | } 189 | 190 | // pretty strait-forward interface that children ad slots use to register 191 | // with their DFPSlotProvider parent node. 192 | newSlotCallback() { 193 | this.totalSlots++; 194 | } 195 | 196 | // Checks all the mounted children ads have been already registered 197 | // in the DFPManager before trying to call the gpt load scripts. 198 | // This is helpful when trying to fetch ads with a single request. 199 | loadAdsIfPossible() { 200 | let r = false; 201 | if (Object.keys(DFPManager.getRegisteredSlots()).length >= this.totalSlots) { 202 | DFPManager.removeListener('slotRegistered', this.loadAdsIfPossible); 203 | DFPManager.load(); 204 | this.loadAlreadyCalled = true; 205 | this.loadCallbackAttached = false; 206 | r = true; 207 | } 208 | return r; 209 | } 210 | 211 | shouldReloadConfig(nextProps) { 212 | const reloadConfig = nextProps.autoReload || this.props.autoReload; 213 | if (this.props.autoLoad || nextProps.autoLoad) { 214 | if (typeof reloadConfig === 'object') { 215 | const attrs = Object.keys(reloadConfig); 216 | // eslint-disable-next-line guard-for-in, no-restricted-syntax 217 | for (const i in attrs) { 218 | const propName = attrs[i]; 219 | // eslint-disable-next-line 220 | if (reloadConfig[propName] === true && this.props[propName] !== nextProps[propName]) { 221 | return true; 222 | } 223 | } 224 | } 225 | } 226 | return false; 227 | } 228 | 229 | render() { 230 | const { children } = this.props; 231 | if (Context === null) { 232 | return children; 233 | } 234 | return ( 235 | 236 | {children} 237 | 238 | ); 239 | } 240 | } 241 | 242 | if (Context === null) { 243 | // React < 16.3 244 | DFPSlotsProvider.childContextTypes = { 245 | dfpNetworkId: PropTypes.string, 246 | dfpAdUnit: PropTypes.string, 247 | dfpSizeMapping: PropTypes.arrayOf(PropTypes.object), 248 | dfpTargetingArguments: PropTypes.object, 249 | newSlotCallback: PropTypes.func, 250 | }; 251 | } 252 | -------------------------------------------------------------------------------- /js/index.js: -------------------------------------------------------------------------------- 1 | import manager from './manager'; 2 | import slot from './adslot'; 3 | import provider from './dfpslotsprovider'; 4 | 5 | export const DFPManager = manager; 6 | export const AdSlot = slot; 7 | export const DFPSlotsProvider = provider; 8 | -------------------------------------------------------------------------------- /js/manager.js: -------------------------------------------------------------------------------- 1 | import { EventEmitter } from 'events'; 2 | import * as Utils from './utils'; 3 | 4 | let loadPromise = null; 5 | let googleGPTScriptLoadPromise = null; 6 | let singleRequestEnabled = true; 7 | let disableInitialLoadEnabled = false; 8 | let lazyLoadEnabled = false; 9 | let lazyLoadConfig = null; 10 | let servePersonalizedAds = true; 11 | let serveCookies = true; 12 | const registeredSlots = {}; 13 | let managerAlreadyInitialized = false; 14 | const globalTargetingArguments = {}; 15 | const globalAdSenseAttributes = {}; 16 | let limitedAds = false; 17 | 18 | const DFPManager = Object.assign(new EventEmitter().setMaxListeners(0), { 19 | 20 | singleRequestIsEnabled() { 21 | return singleRequestEnabled; 22 | }, 23 | 24 | configureSingleRequest(value) { 25 | singleRequestEnabled = !!value; 26 | }, 27 | 28 | disableInitialLoadIsEnabled() { 29 | return disableInitialLoadEnabled; 30 | }, 31 | 32 | configureDisableInitialLoad(value) { 33 | disableInitialLoadEnabled = !!value; 34 | }, 35 | 36 | configureLazyLoad(enable = true, config = null) { 37 | let conf = null; 38 | if (config !== null && typeof config === 'object') { 39 | conf = { ...config }; 40 | } 41 | lazyLoadEnabled = !!enable; 42 | lazyLoadConfig = conf; 43 | }, 44 | 45 | lazyLoadIsEnabled() { 46 | return lazyLoadEnabled; 47 | }, 48 | 49 | limitedAdsIsEnabled() { 50 | return limitedAds; 51 | }, 52 | 53 | configureLimitedAds(value) { 54 | limitedAds = !!value; 55 | }, 56 | 57 | getLazyLoadConfig() { 58 | return lazyLoadConfig; 59 | }, 60 | 61 | getAdSenseAttribute(key) { 62 | return globalAdSenseAttributes[key]; 63 | }, 64 | 65 | configurePersonalizedAds(value) { 66 | servePersonalizedAds = value; 67 | }, 68 | 69 | configureCookieOption(value) { 70 | serveCookies = value; 71 | }, 72 | 73 | personalizedAdsEnabled() { 74 | return servePersonalizedAds; 75 | }, 76 | 77 | cookiesEnabled() { 78 | return serveCookies; 79 | }, 80 | 81 | setAdSenseAttribute(key, value) { 82 | this.setAdSenseAttributes({ [key]: value }); 83 | }, 84 | 85 | getAdSenseAttributes() { 86 | return { ...globalAdSenseAttributes }; 87 | }, 88 | 89 | setAdSenseAttributes(attrs) { 90 | Object.assign(globalAdSenseAttributes, attrs); 91 | if (managerAlreadyInitialized === true) { 92 | this.getGoogletag().then((googletag) => { 93 | googletag.cmd.push(() => { 94 | const pubadsService = googletag.pubads(); 95 | Object.keys(globalAdSenseAttributes).forEach( 96 | (key) => { 97 | pubadsService.set(key, globalTargetingArguments[key]); 98 | }, 99 | ); 100 | }); 101 | }); 102 | } 103 | }, 104 | 105 | setTargetingArguments(data) { 106 | Object.assign(globalTargetingArguments, data); 107 | const availableKeys = Object.keys(registeredSlots); 108 | availableKeys.forEach((slotId) => { 109 | registeredSlots[slotId].targetingArguments = data; 110 | }); 111 | if (managerAlreadyInitialized === true) { 112 | this.getGoogletag().then((googletag) => { 113 | googletag.cmd.push(() => { 114 | const pubadsService = googletag.pubads(); 115 | Object.keys(globalTargetingArguments).forEach((varName) => { 116 | if (pubadsService) { 117 | pubadsService.setTargeting(varName, globalTargetingArguments[varName]); 118 | } 119 | }); 120 | }); 121 | }); 122 | } 123 | }, 124 | 125 | getTargetingArguments() { 126 | return { ...globalTargetingArguments }; 127 | }, 128 | 129 | getSlotProperty(slotId, propName) { 130 | const slot = this.getRegisteredSlots()[slotId]; 131 | let ret = null; 132 | if (slot !== undefined) { 133 | ret = slot[propName] || ret; 134 | } 135 | return ret; 136 | }, 137 | 138 | getSlotTargetingArguments(slotId) { 139 | const propValue = this.getSlotProperty(slotId, 'targetingArguments'); 140 | return propValue ? { ...propValue } : null; 141 | }, 142 | 143 | getSlotAdSenseAttributes(slotId) { 144 | const propValue = this.getSlotProperty(slotId, 'adSenseAttributes'); 145 | return propValue ? { ...propValue } : null; 146 | }, 147 | 148 | init() { 149 | if (managerAlreadyInitialized === false) { 150 | managerAlreadyInitialized = true; 151 | this.getGoogletag().then((googletag) => { 152 | googletag.cmd.push(() => { 153 | const pubadsService = googletag.pubads(); 154 | pubadsService.addEventListener('slotRenderEnded', (event) => { 155 | const slotId = event.slot.getSlotElementId(); 156 | this.emit('slotRenderEnded', { slotId, event }); 157 | }); 158 | pubadsService.addEventListener('impressionViewable', (event) => { 159 | const slotId = event.slot.getSlotElementId(); 160 | this.emit('impressionViewable', { slotId, event }); 161 | }); 162 | pubadsService.addEventListener('slotVisibilityChanged', (event) => { 163 | const slotId = event.slot.getSlotElementId(); 164 | this.emit('slotVisibilityChanged', { slotId, event }); 165 | }); 166 | pubadsService.setRequestNonPersonalizedAds( 167 | this.personalizedAdsEnabled() ? 0 : 1, 168 | ); 169 | pubadsService.setCookieOptions( 170 | this.cookiesEnabled() ? 0 : 1, 171 | ); 172 | }); 173 | }); 174 | } 175 | }, 176 | 177 | getGoogletag() { 178 | if (googleGPTScriptLoadPromise === null) { 179 | googleGPTScriptLoadPromise = Utils.loadGPTScript(limitedAds); 180 | } 181 | return googleGPTScriptLoadPromise; 182 | }, 183 | 184 | setCollapseEmptyDivs(collapse) { 185 | this.collapseEmptyDivs = collapse; 186 | }, 187 | 188 | load(...slots) { 189 | if (loadPromise === null) { 190 | loadPromise = this.doLoad(...slots); 191 | } else { 192 | loadPromise = loadPromise.then( 193 | () => this.doLoad(...slots), 194 | ); 195 | } 196 | }, 197 | 198 | doLoad(...slots) { 199 | this.init(); 200 | let availableSlots = {}; 201 | 202 | if (slots.length > 0) { 203 | availableSlots = slots.filter( 204 | slotId => Object.prototype.hasOwnProperty.call(registeredSlots, slotId), 205 | ); 206 | } else { 207 | availableSlots = Object.keys(registeredSlots); 208 | } 209 | availableSlots = availableSlots.filter( 210 | id => !registeredSlots[id].loading && !registeredSlots[id].gptSlot, 211 | ); 212 | availableSlots.forEach((slotId) => { 213 | registeredSlots[slotId].loading = true; 214 | }); 215 | return this.gptLoadAds(availableSlots); 216 | }, 217 | 218 | gptLoadAds(slotsToInitialize) { 219 | return new Promise((resolve) => { 220 | this.getGoogletag().then((googletag) => { 221 | this.configureInitialOptions(googletag); 222 | slotsToInitialize.forEach((currentSlotId) => { 223 | registeredSlots[currentSlotId].loading = false; 224 | 225 | googletag.cmd.push(() => { 226 | const slot = registeredSlots[currentSlotId]; 227 | let gptSlot; 228 | const adUnit = `${slot.dfpNetworkId}/${slot.adUnit}`; 229 | if (slot.renderOutOfThePage === true) { 230 | gptSlot = googletag.defineOutOfPageSlot(adUnit, currentSlotId); 231 | } else { 232 | gptSlot = googletag.defineSlot(adUnit, slot.sizes, currentSlotId); 233 | } 234 | if (gptSlot !== null) { 235 | slot.gptSlot = gptSlot; 236 | const slotTargetingArguments = this.getSlotTargetingArguments(currentSlotId); 237 | if (slotTargetingArguments !== null) { 238 | Object.keys(slotTargetingArguments).forEach((varName) => { 239 | if (slot && slot.gptSlot) { 240 | slot.gptSlot.setTargeting(varName, slotTargetingArguments[varName]); 241 | } 242 | }); 243 | } 244 | const slotAdSenseAttributes = this.getSlotAdSenseAttributes(currentSlotId); 245 | if (slotAdSenseAttributes !== null) { 246 | Object.keys(slotAdSenseAttributes).forEach((varName) => { 247 | slot.gptSlot.set(varName, slotAdSenseAttributes[varName]); 248 | }); 249 | } 250 | slot.gptSlot.addService(googletag.pubads()); 251 | if (slot.sizeMapping) { 252 | let smbuilder = googletag.sizeMapping(); 253 | slot.sizeMapping.forEach((mapping) => { 254 | smbuilder = smbuilder.addSize(mapping.viewport, mapping.sizes); 255 | }); 256 | slot.gptSlot.defineSizeMapping(smbuilder.build()); 257 | } 258 | } 259 | }); 260 | }); 261 | this.configureOptions(googletag); 262 | googletag.cmd.push(() => { 263 | googletag.enableServices(); 264 | slotsToInitialize.forEach((theSlotId) => { 265 | googletag.display(theSlotId); 266 | }); 267 | resolve(); 268 | }); 269 | }); 270 | }); 271 | }, 272 | 273 | // configure those gpt parameters that need to be set before pubsads service 274 | // initialization. 275 | configureInitialOptions(googletag) { 276 | googletag.cmd.push(() => { 277 | if (this.disableInitialLoadIsEnabled()) { 278 | googletag.pubads().disableInitialLoad(); 279 | } 280 | }); 281 | }, 282 | 283 | configureOptions(googletag) { 284 | googletag.cmd.push(() => { 285 | const pubadsService = googletag.pubads(); 286 | pubadsService.setRequestNonPersonalizedAds( 287 | this.personalizedAdsEnabled() ? 0 : 1, 288 | ); 289 | pubadsService.setCookieOptions( 290 | this.cookiesEnabled() ? 0 : 1, 291 | ); 292 | const targetingArguments = this.getTargetingArguments(); 293 | // set global targetting arguments 294 | Object.keys(targetingArguments).forEach((varName) => { 295 | if (pubadsService) { 296 | pubadsService.setTargeting(varName, targetingArguments[varName]); 297 | } 298 | }); 299 | // set global adSense attributes 300 | const adSenseAttributes = this.getAdSenseAttributes(); 301 | Object.keys(adSenseAttributes).forEach((key) => { 302 | pubadsService.set(key, adSenseAttributes[key]); 303 | }); 304 | if (this.lazyLoadIsEnabled()) { 305 | const config = this.getLazyLoadConfig(); 306 | if (config) { 307 | pubadsService.enableLazyLoad(config); 308 | } else { 309 | pubadsService.enableLazyLoad(); 310 | } 311 | } 312 | if (this.singleRequestIsEnabled()) { 313 | pubadsService.enableSingleRequest(); 314 | } 315 | if (this.collapseEmptyDivs === true || this.collapseEmptyDivs === false) { 316 | pubadsService.collapseEmptyDivs(this.collapseEmptyDivs); 317 | } 318 | }); 319 | }, 320 | 321 | getRefreshableSlots(...slotsArray) { 322 | const slots = {}; 323 | if (slotsArray.length === 0) { 324 | const slotsToRefresh = Object.keys(registeredSlots) 325 | .map(k => registeredSlots[k]); 326 | return slotsToRefresh.reduce((last, slot) => { 327 | if (slot.slotShouldRefresh() === true) { 328 | slots[slot.slotId] = slot; 329 | } 330 | return slots; 331 | }, slots); 332 | } 333 | return slotsArray.reduce((last, slotId) => { 334 | const slot = registeredSlots[slotId]; 335 | if (typeof slot !== 'undefined') { 336 | slots[slotId] = slot; 337 | } 338 | return slots; 339 | }, slots); 340 | }, 341 | 342 | refresh(...slots) { 343 | if (loadPromise === null) { 344 | this.load(); 345 | } else { 346 | loadPromise.then(() => { 347 | this.gptRefreshAds( 348 | Object.keys( 349 | this.getRefreshableSlots(...slots), 350 | ), 351 | ); 352 | }); 353 | } 354 | }, 355 | 356 | gptRefreshAds(slots) { 357 | return this.getGoogletag().then((googletag) => { 358 | this.configureOptions(googletag); 359 | googletag.cmd.push(() => { 360 | const pubadsService = googletag.pubads(); 361 | const slotsToRefreshArray = slots.map(slotId => registeredSlots[slotId].slotId); 362 | pubadsService.refresh(slotsToRefreshArray); 363 | }); 364 | }); 365 | }, 366 | 367 | reload(...slots) { 368 | return this.destroyGPTSlots(...slots).then(() => this.load()); 369 | }, 370 | 371 | destroyGPTSlots(...slotsToDestroy) { 372 | if (slotsToDestroy.length === 0) { 373 | // eslint-disable-next-line no-param-reassign 374 | slotsToDestroy = Object.keys(registeredSlots); 375 | } 376 | return new Promise((resolve) => { 377 | const slots = []; 378 | // eslint-disable-next-line guard-for-in,no-restricted-syntax 379 | for (const idx in slotsToDestroy) { 380 | const slotId = slotsToDestroy[idx]; 381 | const slot = registeredSlots[slotId]; 382 | slots.push(slot); 383 | } 384 | this.getGoogletag() 385 | .then((googletag) => { 386 | googletag.cmd.push(() => { 387 | if (managerAlreadyInitialized === true) { 388 | if (slotsToDestroy.length > 0) { 389 | // eslint-disable-next-line guard-for-in,no-restricted-syntax 390 | for (const idx in slots) { 391 | const slot = slots[idx]; 392 | slots.push(slot.gptSlot); 393 | delete slot.gptSlot; 394 | } 395 | googletag.destroySlots(slots); 396 | } else { 397 | googletag.destroySlots(); 398 | } 399 | } 400 | resolve(slotsToDestroy); 401 | }); 402 | }); 403 | }); 404 | }, 405 | 406 | registerSlot({ 407 | slotId, 408 | dfpNetworkId, 409 | adUnit, 410 | sizes, 411 | renderOutOfThePage, 412 | sizeMapping, 413 | adSenseAttributes, 414 | targetingArguments, 415 | slotShouldRefresh, 416 | }, autoLoad = true) { 417 | if (!Object.prototype.hasOwnProperty.call(registeredSlots, slotId)) { 418 | registeredSlots[slotId] = { 419 | slotId, 420 | sizes, 421 | renderOutOfThePage, 422 | dfpNetworkId, 423 | adUnit, 424 | adSenseAttributes, 425 | targetingArguments, 426 | sizeMapping, 427 | slotShouldRefresh, 428 | loading: false, 429 | }; 430 | this.emit('slotRegistered', { slotId }); 431 | if (autoLoad === true && loadPromise !== null) { 432 | loadPromise = loadPromise.catch().then(() => { 433 | const slot = registeredSlots[slotId]; 434 | if (typeof slot !== 'undefined') { 435 | const { loading, gptSlot } = slot; 436 | if (loading === false && !gptSlot) { 437 | this.load(slotId); 438 | } 439 | } 440 | }); 441 | } 442 | } 443 | }, 444 | 445 | unregisterSlot({ slotId }) { 446 | this.destroyGPTSlots(slotId); 447 | delete registeredSlots[slotId]; 448 | }, 449 | 450 | getRegisteredSlots() { 451 | return registeredSlots; 452 | }, 453 | 454 | attachSlotRenderEnded(cb) { 455 | this.on('slotRenderEnded', cb); 456 | }, 457 | 458 | detachSlotRenderEnded(cb) { 459 | this.removeListener('slotRenderEnded', cb); 460 | }, 461 | 462 | attachSlotVisibilityChanged(cb) { 463 | this.on('slotVisibilityChanged', cb); 464 | }, 465 | 466 | detachSlotVisibilityChanged(cb) { 467 | this.removeListener('slotVisibilityChanged', cb); 468 | }, 469 | 470 | attachSlotIsViewable(cb) { 471 | this.on('impressionViewable', cb); 472 | }, 473 | 474 | detachSlotIsViewable(cb) { 475 | this.removeListener('impressionViewable', cb); 476 | }, 477 | 478 | }); 479 | 480 | export default DFPManager; 481 | -------------------------------------------------------------------------------- /js/utils.js: -------------------------------------------------------------------------------- 1 | const GPT_SRC = { 2 | standard: 'securepubads.g.doubleclick.net', 3 | limitedAds: 'pagead2.googlesyndication.com', 4 | }; 5 | 6 | function doloadGPTScript(resolve, reject, limitedAds) { 7 | window.googletag = window.googletag || {}; 8 | window.googletag.cmd = window.googletag.cmd || []; 9 | 10 | const scriptTag = document.createElement('script'); 11 | scriptTag.src = `${document.location.protocol}//${limitedAds ? GPT_SRC.limitedAds : GPT_SRC.standard}/tag/js/gpt.js`; 12 | scriptTag.async = true; 13 | scriptTag.type = 'text/javascript'; 14 | scriptTag.onerror = function scriptTagOnError(errs) { 15 | reject(errs); 16 | }; 17 | scriptTag.onload = function scriptTagOnLoad() { 18 | resolve(window.googletag); 19 | }; 20 | document.getElementsByTagName('head')[0].appendChild(scriptTag); 21 | } 22 | 23 | export function loadGPTScript(limitedAds = false) { 24 | return new Promise((resolve, reject) => { 25 | doloadGPTScript(resolve, reject, limitedAds); 26 | }); 27 | } 28 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | module.exports = function karmaConfig(config) { 2 | config.set({ 3 | basePath: '', 4 | frameworks: ['jasmine'], 5 | files: [ 6 | 'lib/*.js', 7 | 'spec/*.js', 8 | ], 9 | preprocessors: { 10 | 'lib/*.js': ['webpack'], 11 | 'spec/*.js': ['webpack'], 12 | }, 13 | client: { 14 | captureConsole: true, 15 | jasmine: { 16 | random: false, 17 | }, 18 | }, 19 | webpack: { 20 | mode: 'development', 21 | module: { 22 | rules: [{ 23 | test: /\.js$/, 24 | exclude: /(node_modules)/, 25 | use: { 26 | loader: 'babel-loader', 27 | options: { 28 | presets: ['@babel/preset-env', '@babel/preset-react'], 29 | }, 30 | }, 31 | }], 32 | }, 33 | }, 34 | webpackMiddleware: { 35 | stats: 'errors-only', 36 | }, 37 | reporters: ['mocha'], 38 | port: 9877, 39 | colors: true, 40 | autoWatch: false, 41 | browsers: ['jsdom'], 42 | singleRun: true, 43 | browserNoActivityTimeout: 2000, 44 | }); 45 | }; 46 | -------------------------------------------------------------------------------- /lib/adslot.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.default = exports.AdSlot = void 0; 7 | 8 | var _react = _interopRequireDefault(require("react")); 9 | 10 | var _propTypes = _interopRequireDefault(require("prop-types")); 11 | 12 | var _manager = _interopRequireDefault(require("./manager")); 13 | 14 | var _dfpslotsprovider = require("./dfpslotsprovider"); 15 | 16 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 17 | 18 | function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } 19 | 20 | function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } 21 | 22 | function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; var ownKeys = Object.keys(source); if (typeof Object.getOwnPropertySymbols === 'function') { ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function (sym) { return Object.getOwnPropertyDescriptor(source, sym).enumerable; })); } ownKeys.forEach(function (key) { _defineProperty(target, key, source[key]); }); } return target; } 23 | 24 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 25 | 26 | function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } 27 | 28 | function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } 29 | 30 | function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); } 31 | 32 | function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } 33 | 34 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); } 35 | 36 | function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } 37 | 38 | function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } 39 | 40 | function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } 41 | 42 | var dynamicAdCount = 0; 43 | 44 | var AdSlot = 45 | /*#__PURE__*/ 46 | function (_React$Component) { 47 | _inherits(AdSlot, _React$Component); 48 | 49 | function AdSlot(props) { 50 | var _this; 51 | 52 | _classCallCheck(this, AdSlot); 53 | 54 | _this = _possibleConstructorReturn(this, _getPrototypeOf(AdSlot).call(this, props)); 55 | _this.doRegisterSlot = _this.doRegisterSlot.bind(_assertThisInitialized(_assertThisInitialized(_this))); 56 | _this.generateSlotId = _this.generateSlotId.bind(_assertThisInitialized(_assertThisInitialized(_this))); 57 | _this.getSlotId = _this.getSlotId.bind(_assertThisInitialized(_assertThisInitialized(_this))); 58 | _this.mapContextToAdSlotProps = _this.mapContextToAdSlotProps.bind(_assertThisInitialized(_assertThisInitialized(_this))); 59 | _this.slotShouldRefresh = _this.slotShouldRefresh.bind(_assertThisInitialized(_assertThisInitialized(_this))); 60 | _this.slotRenderEnded = _this.slotRenderEnded.bind(_assertThisInitialized(_assertThisInitialized(_this))); 61 | _this.slotRegisterCallback = _this.slotRegisterCallback.bind(_assertThisInitialized(_assertThisInitialized(_this))); 62 | _this.slotIsViewable = _this.slotIsViewable.bind(_assertThisInitialized(_assertThisInitialized(_this))); 63 | _this.slotVisibilityChanged = _this.slotVisibilityChanged.bind(_assertThisInitialized(_assertThisInitialized(_this))); 64 | _this.getClasses = _this.getClasses.bind(_assertThisInitialized(_assertThisInitialized(_this))); 65 | _this.state = { 66 | slotId: _this.props.slotId || null, 67 | className: _this.props.className || '' 68 | }; 69 | _this.adElementRef = _react.default.createRef ? _react.default.createRef() : function (element) { 70 | _this.adElementRef = element; 71 | }; 72 | return _this; 73 | } 74 | 75 | _createClass(AdSlot, [{ 76 | key: "componentDidMount", 77 | value: function componentDidMount() { 78 | // register this ad-unit in the , when available. 79 | if (this.context !== undefined && this.context.newSlotCallback) { 80 | this.context.newSlotCallback(); 81 | } 82 | 83 | this.registerSlot(); 84 | } 85 | }, { 86 | key: "componentWillUnmount", 87 | value: function componentWillUnmount() { 88 | this.unregisterSlot(); 89 | } 90 | }, { 91 | key: "getSlotId", 92 | value: function getSlotId() { 93 | return this.props.slotId || this.state.slotId; 94 | } 95 | }, { 96 | key: "getClasses", 97 | value: function getClasses() { 98 | var baseClass = 'adunitContainer'; 99 | var extraClasses = this.state.className.split(' '); 100 | extraClasses.push(baseClass); 101 | return extraClasses; 102 | } 103 | }, { 104 | key: "generateSlotId", 105 | value: function generateSlotId() { 106 | return "adSlot-".concat(dynamicAdCount++); 107 | } 108 | }, { 109 | key: "mapContextToAdSlotProps", 110 | value: function mapContextToAdSlotProps() { 111 | var context = this.context; 112 | var mappedProps = {}; 113 | 114 | if (context.dfpNetworkId !== undefined) { 115 | mappedProps.dfpNetworkId = context.dfpNetworkId; 116 | } 117 | 118 | if (context.dfpAdUnit !== undefined) { 119 | mappedProps.adUnit = context.dfpAdUnit; 120 | } 121 | 122 | if (context.dfpSizeMapping !== undefined) { 123 | mappedProps.sizeMapping = context.dfpSizeMapping; 124 | } 125 | 126 | if (context.dfpTargetingArguments !== undefined) { 127 | mappedProps.targetingArguments = context.dfpTargetingArguments; 128 | } 129 | 130 | return mappedProps; 131 | } 132 | }, { 133 | key: "doRegisterSlot", 134 | value: function doRegisterSlot() { 135 | _manager.default.registerSlot(_objectSpread({}, this.mapContextToAdSlotProps(), this.props, this.state, { 136 | slotShouldRefresh: this.slotShouldRefresh 137 | })); 138 | 139 | if (this.props.fetchNow === true) { 140 | _manager.default.load(this.getSlotId()); 141 | } 142 | 143 | _manager.default.attachSlotRenderEnded(this.slotRenderEnded); 144 | 145 | _manager.default.attachSlotIsViewable(this.slotIsViewable); 146 | 147 | _manager.default.attachSlotVisibilityChanged(this.slotVisibilityChanged); 148 | 149 | this.slotRegisterCallback(); 150 | } 151 | }, { 152 | key: "registerSlot", 153 | value: function registerSlot() { 154 | if (this.state.slotId === null) { 155 | this.setState({ 156 | slotId: this.generateSlotId() 157 | }, this.doRegisterSlot); 158 | } else { 159 | this.doRegisterSlot(); 160 | } 161 | } 162 | }, { 163 | key: "unregisterSlot", 164 | value: function unregisterSlot() { 165 | _manager.default.unregisterSlot(_objectSpread({}, this.mapContextToAdSlotProps(), this.props, this.state)); 166 | 167 | _manager.default.detachSlotRenderEnded(this.slotRenderEnded); 168 | 169 | _manager.default.detachSlotIsViewable(this.slotIsViewable); 170 | 171 | _manager.default.detachSlotVisibilityChanged(this.slotVisibilityChanged); 172 | } 173 | }, { 174 | key: "slotRenderEnded", 175 | value: function slotRenderEnded(eventData) { 176 | if (eventData.slotId === this.getSlotId()) { 177 | if (this.props.onSlotRender !== undefined) { 178 | // now that slot has rendered we have access to the ref 179 | var params = _objectSpread({}, eventData, { 180 | adElementRef: this.adElementRef 181 | }); 182 | 183 | this.props.onSlotRender(params); 184 | } 185 | } 186 | } 187 | }, { 188 | key: "slotRegisterCallback", 189 | value: function slotRegisterCallback() { 190 | if (typeof this.props.onSlotRegister === 'function') { 191 | this.props.onSlotRegister({ 192 | slotId: this.getSlotId(), 193 | sizes: this.props.sizes, 194 | slotCount: dynamicAdCount, 195 | adElementRef: this.adElementRef 196 | }); 197 | } 198 | } 199 | }, { 200 | key: "slotIsViewable", 201 | value: function slotIsViewable(eventData) { 202 | if (eventData.slotId === this.getSlotId()) { 203 | if (this.props.onSlotIsViewable !== undefined) { 204 | this.props.onSlotIsViewable(eventData); 205 | } 206 | } 207 | } 208 | }, { 209 | key: "slotVisibilityChanged", 210 | value: function slotVisibilityChanged(eventData) { 211 | if (eventData.slotId === this.getSlotId()) { 212 | if (this.props.onSlotVisibilityChanged !== undefined) { 213 | this.props.onSlotVisibilityChanged(eventData); 214 | } 215 | } 216 | } 217 | }, { 218 | key: "slotShouldRefresh", 219 | value: function slotShouldRefresh() { 220 | var r = true; 221 | 222 | if (this.props.shouldRefresh !== undefined) { 223 | r = this.props.shouldRefresh(_objectSpread({}, this.mapContextToAdSlotProps(), this.props, { 224 | slotId: this.getSlotId() 225 | })); 226 | } 227 | 228 | return r; 229 | } 230 | }, { 231 | key: "render", 232 | value: function render() { 233 | var slotId = this.state.slotId; 234 | var props = { 235 | className: 'adBox' 236 | }; 237 | 238 | if (slotId !== null) { 239 | props.id = slotId; 240 | } 241 | 242 | return _react.default.createElement("div", { 243 | className: this.getClasses().join(' ').trim() 244 | }, _react.default.createElement("div", _extends({ 245 | ref: this.adElementRef 246 | }, props))); 247 | } 248 | }]); 249 | 250 | return AdSlot; 251 | }(_react.default.Component); 252 | 253 | exports.AdSlot = AdSlot; 254 | 255 | _defineProperty(AdSlot, "propTypes", { 256 | dfpNetworkId: _propTypes.default.string, 257 | adUnit: _propTypes.default.string, 258 | sizes: _propTypes.default.arrayOf(_propTypes.default.oneOfType([_propTypes.default.arrayOf(_propTypes.default.number), _propTypes.default.string])), 259 | renderOutOfThePage: _propTypes.default.bool, 260 | sizeMapping: _propTypes.default.arrayOf(_propTypes.default.object), 261 | fetchNow: _propTypes.default.bool, 262 | adSenseAttributes: _propTypes.default.object, 263 | targetingArguments: _propTypes.default.object, 264 | onSlotRender: _propTypes.default.func, 265 | onSlotRegister: _propTypes.default.func, 266 | onSlotIsViewable: _propTypes.default.func, 267 | onSlotVisibilityChanged: _propTypes.default.func, 268 | shouldRefresh: _propTypes.default.func, 269 | slotId: _propTypes.default.string, 270 | className: _propTypes.default.string 271 | }); 272 | 273 | _defineProperty(AdSlot, "defaultProps", { 274 | fetchNow: false 275 | }); 276 | 277 | if (_dfpslotsprovider.Context === null) { 278 | // React < 16.3 279 | AdSlot.contextTypes = { 280 | dfpNetworkId: _propTypes.default.string, 281 | dfpAdUnit: _propTypes.default.string, 282 | dfpSizeMapping: _propTypes.default.arrayOf(_propTypes.default.object), 283 | dfpTargetingArguments: _propTypes.default.object, 284 | newSlotCallback: _propTypes.default.func 285 | }; 286 | } else { 287 | AdSlot.contextType = _dfpslotsprovider.Context; 288 | } 289 | 290 | var _default = AdSlot; 291 | exports.default = _default; -------------------------------------------------------------------------------- /lib/dfpslotsprovider.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.default = exports.Context = void 0; 7 | 8 | var _react = _interopRequireDefault(require("react")); 9 | 10 | var _propTypes = _interopRequireDefault(require("prop-types")); 11 | 12 | var _manager = _interopRequireDefault(require("./manager")); 13 | 14 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 15 | 16 | function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } 17 | 18 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 19 | 20 | function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } 21 | 22 | function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } 23 | 24 | function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); } 25 | 26 | function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } 27 | 28 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); } 29 | 30 | function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } 31 | 32 | function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } 33 | 34 | function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } 35 | 36 | // React.createContext is undefined for React < 16.3 37 | var Context = _react.default.createContext ? _react.default.createContext({ 38 | dfpNetworkId: null, 39 | dfpAdUnit: null, 40 | dfpSizeMapping: null, 41 | dfpTargetingArguments: null, 42 | newSlotCallback: null 43 | }) : null; 44 | exports.Context = Context; 45 | 46 | var DFPSlotsProvider = 47 | /*#__PURE__*/ 48 | function (_React$Component) { 49 | _inherits(DFPSlotsProvider, _React$Component); 50 | 51 | function DFPSlotsProvider(props) { 52 | var _this; 53 | 54 | _classCallCheck(this, DFPSlotsProvider); 55 | 56 | _this = _possibleConstructorReturn(this, _getPrototypeOf(DFPSlotsProvider).call(this, props)); 57 | _this.loadAdsIfPossible = _this.loadAdsIfPossible.bind(_assertThisInitialized(_assertThisInitialized(_this))); 58 | _this.newSlotCallback = _this.newSlotCallback.bind(_assertThisInitialized(_assertThisInitialized(_this))); 59 | _this.applyConfigs = _this.applyConfigs.bind(_assertThisInitialized(_assertThisInitialized(_this))); 60 | _this.shouldReloadConfig = _this.shouldReloadConfig.bind(_assertThisInitialized(_assertThisInitialized(_this))); 61 | _this.attachLoadCallback = _this.attachLoadCallback.bind(_assertThisInitialized(_assertThisInitialized(_this))); 62 | _this.getContextValue = _this.getContextValue.bind(_assertThisInitialized(_assertThisInitialized(_this))); 63 | _this.loadAlreadyCalled = false; 64 | _this.loadCallbackAttached = false; 65 | _this.shouldReloadAds = false; 66 | _this.totalSlots = 0; 67 | _this.contextValue = {}; 68 | 69 | if (Context === null) { 70 | _this.getChildContext = function () { 71 | return _this.getContextValue(); 72 | }; 73 | } 74 | 75 | return _this; 76 | } 77 | 78 | _createClass(DFPSlotsProvider, [{ 79 | key: "componentDidMount", 80 | value: function componentDidMount() { 81 | this.applyConfigs(); 82 | 83 | if (this.props.autoLoad && !this.loadAdsIfPossible()) { 84 | this.attachLoadCallback(); 85 | } 86 | } 87 | }, { 88 | key: "shouldComponentUpdate", 89 | value: function shouldComponentUpdate(nextProps) { 90 | this.shouldReloadAds = this.shouldReloadConfig(nextProps); 91 | 92 | if (nextProps.children !== this.props.children) { 93 | return true; 94 | } 95 | 96 | if (nextProps.autoLoad && !this.props.autoLoad) { 97 | return true; 98 | } 99 | 100 | return this.shouldReloadAds; 101 | } 102 | }, { 103 | key: "componentDidUpdate", 104 | value: function componentDidUpdate() { 105 | this.applyConfigs(); 106 | 107 | if (this.props.autoLoad) { 108 | if (this.loadAlreadyCalled) { 109 | if (this.shouldReloadAds) { 110 | _manager.default.reload(); 111 | } 112 | } else if (!this.loadAdsIfPossible()) { 113 | this.attachLoadCallback(); 114 | } 115 | } 116 | 117 | this.shouldReloadAds = false; 118 | } 119 | }, { 120 | key: "getContextValue", 121 | value: function getContextValue() { 122 | var _this$props = this.props, 123 | dfpNetworkId = _this$props.dfpNetworkId, 124 | dfpAdUnit = _this$props.adUnit, 125 | dfpSizeMapping = _this$props.sizeMapping, 126 | dfpTargetingArguments = _this$props.targetingArguments, 127 | _this$contextValue = this.contextValue, 128 | ctxDfpNetworkId = _this$contextValue.dfpNetworkId, 129 | ctxDfpAdUnit = _this$contextValue.adUnit, 130 | ctxDfpSizeMapping = _this$contextValue.sizeMapping, 131 | ctxDfpTargetingArguments = _this$contextValue.targetingArguments; // performance: update context value object only when any of its 132 | // props is updated. 133 | 134 | if (dfpNetworkId !== ctxDfpNetworkId || dfpAdUnit !== ctxDfpAdUnit || dfpSizeMapping !== ctxDfpSizeMapping || dfpTargetingArguments !== ctxDfpTargetingArguments) { 135 | this.contextValue = { 136 | dfpNetworkId: dfpNetworkId, 137 | dfpAdUnit: dfpAdUnit, 138 | dfpSizeMapping: dfpSizeMapping, 139 | dfpTargetingArguments: dfpTargetingArguments, 140 | newSlotCallback: this.newSlotCallback 141 | }; 142 | } 143 | 144 | return this.contextValue; 145 | } 146 | }, { 147 | key: "applyConfigs", 148 | value: function applyConfigs() { 149 | _manager.default.configurePersonalizedAds(this.props.personalizedAds); 150 | 151 | _manager.default.configureCookieOption(this.props.cookieOption); 152 | 153 | _manager.default.configureSingleRequest(this.props.singleRequest); 154 | 155 | _manager.default.configureDisableInitialLoad(this.props.disableInitialLoad); 156 | 157 | _manager.default.configureLazyLoad(!!this.props.lazyLoad, typeof this.props.lazyLoad === 'boolean' ? null : this.props.lazyLoad); 158 | 159 | _manager.default.setAdSenseAttributes(this.props.adSenseAttributes); 160 | 161 | _manager.default.setCollapseEmptyDivs(this.props.collapseEmptyDivs); 162 | 163 | _manager.default.configureLimitedAds(this.props.limitedAds); 164 | } 165 | }, { 166 | key: "attachLoadCallback", 167 | value: function attachLoadCallback() { 168 | if (this.loadCallbackAttached === false) { 169 | _manager.default.on('slotRegistered', this.loadAdsIfPossible); 170 | 171 | this.loadCallbackAttached = true; 172 | return true; 173 | } 174 | 175 | return false; 176 | } // pretty strait-forward interface that children ad slots use to register 177 | // with their DFPSlotProvider parent node. 178 | 179 | }, { 180 | key: "newSlotCallback", 181 | value: function newSlotCallback() { 182 | this.totalSlots++; 183 | } // Checks all the mounted children ads have been already registered 184 | // in the DFPManager before trying to call the gpt load scripts. 185 | // This is helpful when trying to fetch ads with a single request. 186 | 187 | }, { 188 | key: "loadAdsIfPossible", 189 | value: function loadAdsIfPossible() { 190 | var r = false; 191 | 192 | if (Object.keys(_manager.default.getRegisteredSlots()).length >= this.totalSlots) { 193 | _manager.default.removeListener('slotRegistered', this.loadAdsIfPossible); 194 | 195 | _manager.default.load(); 196 | 197 | this.loadAlreadyCalled = true; 198 | this.loadCallbackAttached = false; 199 | r = true; 200 | } 201 | 202 | return r; 203 | } 204 | }, { 205 | key: "shouldReloadConfig", 206 | value: function shouldReloadConfig(nextProps) { 207 | var reloadConfig = nextProps.autoReload || this.props.autoReload; 208 | 209 | if (this.props.autoLoad || nextProps.autoLoad) { 210 | if (_typeof(reloadConfig) === 'object') { 211 | var attrs = Object.keys(reloadConfig); // eslint-disable-next-line guard-for-in, no-restricted-syntax 212 | 213 | for (var i in attrs) { 214 | var propName = attrs[i]; // eslint-disable-next-line 215 | 216 | if (reloadConfig[propName] === true && this.props[propName] !== nextProps[propName]) { 217 | return true; 218 | } 219 | } 220 | } 221 | } 222 | 223 | return false; 224 | } 225 | }, { 226 | key: "render", 227 | value: function render() { 228 | var children = this.props.children; 229 | 230 | if (Context === null) { 231 | return children; 232 | } 233 | 234 | return _react.default.createElement(Context.Provider, { 235 | value: this.getContextValue() 236 | }, children); 237 | } 238 | }]); 239 | 240 | return DFPSlotsProvider; 241 | }(_react.default.Component); 242 | 243 | exports.default = DFPSlotsProvider; 244 | 245 | _defineProperty(DFPSlotsProvider, "propTypes", { 246 | children: _propTypes.default.oneOfType([_propTypes.default.element, _propTypes.default.array]).isRequired, 247 | autoLoad: _propTypes.default.bool, 248 | autoReload: _propTypes.default.shape({ 249 | dfpNetworkId: _propTypes.default.bool, 250 | personalizedAds: _propTypes.default.bool, 251 | cookieOption: _propTypes.default.bool, 252 | singleRequest: _propTypes.default.bool, 253 | disableInitialLoad: _propTypes.default.bool, 254 | adUnit: _propTypes.default.bool, 255 | sizeMapping: _propTypes.default.bool, 256 | adSenseAttributes: _propTypes.default.bool, 257 | targetingArguments: _propTypes.default.bool, 258 | collapseEmptyDivs: _propTypes.default.bool, 259 | lazyLoad: _propTypes.default.bool 260 | }), 261 | dfpNetworkId: _propTypes.default.string.isRequired, 262 | personalizedAds: _propTypes.default.bool, 263 | cookieOption: _propTypes.default.bool, 264 | singleRequest: _propTypes.default.bool, 265 | disableInitialLoad: _propTypes.default.bool, 266 | adUnit: _propTypes.default.string, 267 | sizeMapping: _propTypes.default.arrayOf(_propTypes.default.object), 268 | adSenseAttributes: _propTypes.default.object, 269 | targetingArguments: _propTypes.default.object, 270 | collapseEmptyDivs: _propTypes.default.oneOfType([_propTypes.default.bool, _propTypes.default.object]), 271 | adSenseAttrs: _propTypes.default.object, 272 | lazyLoad: _propTypes.default.oneOfType([_propTypes.default.bool, _propTypes.default.shape({ 273 | fetchMarginPercent: _propTypes.default.number, 274 | renderMarginPercent: _propTypes.default.number, 275 | mobileScaling: _propTypes.default.number 276 | })]), 277 | limitedAds: _propTypes.default.bool 278 | }); 279 | 280 | _defineProperty(DFPSlotsProvider, "defaultProps", { 281 | autoLoad: true, 282 | autoReload: { 283 | dfpNetworkId: false, 284 | personalizedAds: false, 285 | cookieOption: false, 286 | singleRequest: false, 287 | disableInitialLoad: false, 288 | adUnit: false, 289 | sizeMapping: false, 290 | adSenseAttributes: false, 291 | targetingArguments: false, 292 | collapseEmptyDivs: false, 293 | lazyLoad: false 294 | }, 295 | personalizedAds: true, 296 | cookieOption: true, 297 | singleRequest: true, 298 | disableInitialLoad: false, 299 | collapseEmptyDivs: null, 300 | lazyLoad: false, 301 | limitedAds: false 302 | }); 303 | 304 | if (Context === null) { 305 | // React < 16.3 306 | DFPSlotsProvider.childContextTypes = { 307 | dfpNetworkId: _propTypes.default.string, 308 | dfpAdUnit: _propTypes.default.string, 309 | dfpSizeMapping: _propTypes.default.arrayOf(_propTypes.default.object), 310 | dfpTargetingArguments: _propTypes.default.object, 311 | newSlotCallback: _propTypes.default.func 312 | }; 313 | } -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.DFPSlotsProvider = exports.AdSlot = exports.DFPManager = void 0; 7 | 8 | var _manager = _interopRequireDefault(require("./manager")); 9 | 10 | var _adslot = _interopRequireDefault(require("./adslot")); 11 | 12 | var _dfpslotsprovider = _interopRequireDefault(require("./dfpslotsprovider")); 13 | 14 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 15 | 16 | var DFPManager = _manager.default; 17 | exports.DFPManager = DFPManager; 18 | var AdSlot = _adslot.default; 19 | exports.AdSlot = AdSlot; 20 | var DFPSlotsProvider = _dfpslotsprovider.default; 21 | exports.DFPSlotsProvider = DFPSlotsProvider; -------------------------------------------------------------------------------- /lib/manager.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.default = void 0; 7 | 8 | var _events = require("events"); 9 | 10 | var Utils = _interopRequireWildcard(require("./utils")); 11 | 12 | function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = Object.defineProperty && Object.getOwnPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : {}; if (desc.get || desc.set) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } } newObj.default = obj; return newObj; } } 13 | 14 | function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; var ownKeys = Object.keys(source); if (typeof Object.getOwnPropertySymbols === 'function') { ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function (sym) { return Object.getOwnPropertyDescriptor(source, sym).enumerable; })); } ownKeys.forEach(function (key) { _defineProperty(target, key, source[key]); }); } return target; } 15 | 16 | function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } 17 | 18 | function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } 19 | 20 | var loadPromise = null; 21 | var googleGPTScriptLoadPromise = null; 22 | var singleRequestEnabled = true; 23 | var disableInitialLoadEnabled = false; 24 | var lazyLoadEnabled = false; 25 | var lazyLoadConfig = null; 26 | var servePersonalizedAds = true; 27 | var serveCookies = true; 28 | var registeredSlots = {}; 29 | var managerAlreadyInitialized = false; 30 | var globalTargetingArguments = {}; 31 | var globalAdSenseAttributes = {}; 32 | var limitedAds = false; 33 | var DFPManager = Object.assign(new _events.EventEmitter().setMaxListeners(0), { 34 | singleRequestIsEnabled: function singleRequestIsEnabled() { 35 | return singleRequestEnabled; 36 | }, 37 | configureSingleRequest: function configureSingleRequest(value) { 38 | singleRequestEnabled = !!value; 39 | }, 40 | disableInitialLoadIsEnabled: function disableInitialLoadIsEnabled() { 41 | return disableInitialLoadEnabled; 42 | }, 43 | configureDisableInitialLoad: function configureDisableInitialLoad(value) { 44 | disableInitialLoadEnabled = !!value; 45 | }, 46 | configureLazyLoad: function configureLazyLoad() { 47 | var enable = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true; 48 | var config = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null; 49 | var conf = null; 50 | 51 | if (config !== null && _typeof(config) === 'object') { 52 | conf = _objectSpread({}, config); 53 | } 54 | 55 | lazyLoadEnabled = !!enable; 56 | lazyLoadConfig = conf; 57 | }, 58 | lazyLoadIsEnabled: function lazyLoadIsEnabled() { 59 | return lazyLoadEnabled; 60 | }, 61 | limitedAdsIsEnabled: function limitedAdsIsEnabled() { 62 | return limitedAds; 63 | }, 64 | configureLimitedAds: function configureLimitedAds(value) { 65 | limitedAds = !!value; 66 | }, 67 | getLazyLoadConfig: function getLazyLoadConfig() { 68 | return lazyLoadConfig; 69 | }, 70 | getAdSenseAttribute: function getAdSenseAttribute(key) { 71 | return globalAdSenseAttributes[key]; 72 | }, 73 | configurePersonalizedAds: function configurePersonalizedAds(value) { 74 | servePersonalizedAds = value; 75 | }, 76 | configureCookieOption: function configureCookieOption(value) { 77 | serveCookies = value; 78 | }, 79 | personalizedAdsEnabled: function personalizedAdsEnabled() { 80 | return servePersonalizedAds; 81 | }, 82 | cookiesEnabled: function cookiesEnabled() { 83 | return serveCookies; 84 | }, 85 | setAdSenseAttribute: function setAdSenseAttribute(key, value) { 86 | this.setAdSenseAttributes(_defineProperty({}, key, value)); 87 | }, 88 | getAdSenseAttributes: function getAdSenseAttributes() { 89 | return _objectSpread({}, globalAdSenseAttributes); 90 | }, 91 | setAdSenseAttributes: function setAdSenseAttributes(attrs) { 92 | Object.assign(globalAdSenseAttributes, attrs); 93 | 94 | if (managerAlreadyInitialized === true) { 95 | this.getGoogletag().then(function (googletag) { 96 | googletag.cmd.push(function () { 97 | var pubadsService = googletag.pubads(); 98 | Object.keys(globalAdSenseAttributes).forEach(function (key) { 99 | pubadsService.set(key, globalTargetingArguments[key]); 100 | }); 101 | }); 102 | }); 103 | } 104 | }, 105 | setTargetingArguments: function setTargetingArguments(data) { 106 | Object.assign(globalTargetingArguments, data); 107 | var availableKeys = Object.keys(registeredSlots); 108 | availableKeys.forEach(function (slotId) { 109 | registeredSlots[slotId].targetingArguments = data; 110 | }); 111 | 112 | if (managerAlreadyInitialized === true) { 113 | this.getGoogletag().then(function (googletag) { 114 | googletag.cmd.push(function () { 115 | var pubadsService = googletag.pubads(); 116 | Object.keys(globalTargetingArguments).forEach(function (varName) { 117 | if (pubadsService) { 118 | pubadsService.setTargeting(varName, globalTargetingArguments[varName]); 119 | } 120 | }); 121 | }); 122 | }); 123 | } 124 | }, 125 | getTargetingArguments: function getTargetingArguments() { 126 | return _objectSpread({}, globalTargetingArguments); 127 | }, 128 | getSlotProperty: function getSlotProperty(slotId, propName) { 129 | var slot = this.getRegisteredSlots()[slotId]; 130 | var ret = null; 131 | 132 | if (slot !== undefined) { 133 | ret = slot[propName] || ret; 134 | } 135 | 136 | return ret; 137 | }, 138 | getSlotTargetingArguments: function getSlotTargetingArguments(slotId) { 139 | var propValue = this.getSlotProperty(slotId, 'targetingArguments'); 140 | return propValue ? _objectSpread({}, propValue) : null; 141 | }, 142 | getSlotAdSenseAttributes: function getSlotAdSenseAttributes(slotId) { 143 | var propValue = this.getSlotProperty(slotId, 'adSenseAttributes'); 144 | return propValue ? _objectSpread({}, propValue) : null; 145 | }, 146 | init: function init() { 147 | var _this = this; 148 | 149 | if (managerAlreadyInitialized === false) { 150 | managerAlreadyInitialized = true; 151 | this.getGoogletag().then(function (googletag) { 152 | googletag.cmd.push(function () { 153 | var pubadsService = googletag.pubads(); 154 | pubadsService.addEventListener('slotRenderEnded', function (event) { 155 | var slotId = event.slot.getSlotElementId(); 156 | 157 | _this.emit('slotRenderEnded', { 158 | slotId: slotId, 159 | event: event 160 | }); 161 | }); 162 | pubadsService.addEventListener('impressionViewable', function (event) { 163 | var slotId = event.slot.getSlotElementId(); 164 | 165 | _this.emit('impressionViewable', { 166 | slotId: slotId, 167 | event: event 168 | }); 169 | }); 170 | pubadsService.addEventListener('slotVisibilityChanged', function (event) { 171 | var slotId = event.slot.getSlotElementId(); 172 | 173 | _this.emit('slotVisibilityChanged', { 174 | slotId: slotId, 175 | event: event 176 | }); 177 | }); 178 | pubadsService.setRequestNonPersonalizedAds(_this.personalizedAdsEnabled() ? 0 : 1); 179 | pubadsService.setCookieOptions(_this.cookiesEnabled() ? 0 : 1); 180 | }); 181 | }); 182 | } 183 | }, 184 | getGoogletag: function getGoogletag() { 185 | if (googleGPTScriptLoadPromise === null) { 186 | googleGPTScriptLoadPromise = Utils.loadGPTScript(limitedAds); 187 | } 188 | 189 | return googleGPTScriptLoadPromise; 190 | }, 191 | setCollapseEmptyDivs: function setCollapseEmptyDivs(collapse) { 192 | this.collapseEmptyDivs = collapse; 193 | }, 194 | load: function load() { 195 | var _this2 = this; 196 | 197 | for (var _len = arguments.length, slots = new Array(_len), _key = 0; _key < _len; _key++) { 198 | slots[_key] = arguments[_key]; 199 | } 200 | 201 | if (loadPromise === null) { 202 | loadPromise = this.doLoad.apply(this, slots); 203 | } else { 204 | loadPromise = loadPromise.then(function () { 205 | return _this2.doLoad.apply(_this2, slots); 206 | }); 207 | } 208 | }, 209 | doLoad: function doLoad() { 210 | this.init(); 211 | var availableSlots = {}; 212 | 213 | for (var _len2 = arguments.length, slots = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { 214 | slots[_key2] = arguments[_key2]; 215 | } 216 | 217 | if (slots.length > 0) { 218 | availableSlots = slots.filter(function (slotId) { 219 | return Object.prototype.hasOwnProperty.call(registeredSlots, slotId); 220 | }); 221 | } else { 222 | availableSlots = Object.keys(registeredSlots); 223 | } 224 | 225 | availableSlots = availableSlots.filter(function (id) { 226 | return !registeredSlots[id].loading && !registeredSlots[id].gptSlot; 227 | }); 228 | availableSlots.forEach(function (slotId) { 229 | registeredSlots[slotId].loading = true; 230 | }); 231 | return this.gptLoadAds(availableSlots); 232 | }, 233 | gptLoadAds: function gptLoadAds(slotsToInitialize) { 234 | var _this3 = this; 235 | 236 | return new Promise(function (resolve) { 237 | _this3.getGoogletag().then(function (googletag) { 238 | _this3.configureInitialOptions(googletag); 239 | 240 | slotsToInitialize.forEach(function (currentSlotId) { 241 | registeredSlots[currentSlotId].loading = false; 242 | googletag.cmd.push(function () { 243 | var slot = registeredSlots[currentSlotId]; 244 | var gptSlot; 245 | var adUnit = "".concat(slot.dfpNetworkId, "/").concat(slot.adUnit); 246 | 247 | if (slot.renderOutOfThePage === true) { 248 | gptSlot = googletag.defineOutOfPageSlot(adUnit, currentSlotId); 249 | } else { 250 | gptSlot = googletag.defineSlot(adUnit, slot.sizes, currentSlotId); 251 | } 252 | 253 | if (gptSlot !== null) { 254 | slot.gptSlot = gptSlot; 255 | 256 | var slotTargetingArguments = _this3.getSlotTargetingArguments(currentSlotId); 257 | 258 | if (slotTargetingArguments !== null) { 259 | Object.keys(slotTargetingArguments).forEach(function (varName) { 260 | if (slot && slot.gptSlot) { 261 | slot.gptSlot.setTargeting(varName, slotTargetingArguments[varName]); 262 | } 263 | }); 264 | } 265 | 266 | var slotAdSenseAttributes = _this3.getSlotAdSenseAttributes(currentSlotId); 267 | 268 | if (slotAdSenseAttributes !== null) { 269 | Object.keys(slotAdSenseAttributes).forEach(function (varName) { 270 | slot.gptSlot.set(varName, slotAdSenseAttributes[varName]); 271 | }); 272 | } 273 | 274 | slot.gptSlot.addService(googletag.pubads()); 275 | 276 | if (slot.sizeMapping) { 277 | var smbuilder = googletag.sizeMapping(); 278 | slot.sizeMapping.forEach(function (mapping) { 279 | smbuilder = smbuilder.addSize(mapping.viewport, mapping.sizes); 280 | }); 281 | slot.gptSlot.defineSizeMapping(smbuilder.build()); 282 | } 283 | } 284 | }); 285 | }); 286 | 287 | _this3.configureOptions(googletag); 288 | 289 | googletag.cmd.push(function () { 290 | googletag.enableServices(); 291 | slotsToInitialize.forEach(function (theSlotId) { 292 | googletag.display(theSlotId); 293 | }); 294 | resolve(); 295 | }); 296 | }); 297 | }); 298 | }, 299 | // configure those gpt parameters that need to be set before pubsads service 300 | // initialization. 301 | configureInitialOptions: function configureInitialOptions(googletag) { 302 | var _this4 = this; 303 | 304 | googletag.cmd.push(function () { 305 | if (_this4.disableInitialLoadIsEnabled()) { 306 | googletag.pubads().disableInitialLoad(); 307 | } 308 | }); 309 | }, 310 | configureOptions: function configureOptions(googletag) { 311 | var _this5 = this; 312 | 313 | googletag.cmd.push(function () { 314 | var pubadsService = googletag.pubads(); 315 | pubadsService.setRequestNonPersonalizedAds(_this5.personalizedAdsEnabled() ? 0 : 1); 316 | pubadsService.setCookieOptions(_this5.cookiesEnabled() ? 0 : 1); 317 | 318 | var targetingArguments = _this5.getTargetingArguments(); // set global targetting arguments 319 | 320 | 321 | Object.keys(targetingArguments).forEach(function (varName) { 322 | if (pubadsService) { 323 | pubadsService.setTargeting(varName, targetingArguments[varName]); 324 | } 325 | }); // set global adSense attributes 326 | 327 | var adSenseAttributes = _this5.getAdSenseAttributes(); 328 | 329 | Object.keys(adSenseAttributes).forEach(function (key) { 330 | pubadsService.set(key, adSenseAttributes[key]); 331 | }); 332 | 333 | if (_this5.lazyLoadIsEnabled()) { 334 | var config = _this5.getLazyLoadConfig(); 335 | 336 | if (config) { 337 | pubadsService.enableLazyLoad(config); 338 | } else { 339 | pubadsService.enableLazyLoad(); 340 | } 341 | } 342 | 343 | if (_this5.singleRequestIsEnabled()) { 344 | pubadsService.enableSingleRequest(); 345 | } 346 | 347 | if (_this5.collapseEmptyDivs === true || _this5.collapseEmptyDivs === false) { 348 | pubadsService.collapseEmptyDivs(_this5.collapseEmptyDivs); 349 | } 350 | }); 351 | }, 352 | getRefreshableSlots: function getRefreshableSlots() { 353 | var slots = {}; 354 | 355 | for (var _len3 = arguments.length, slotsArray = new Array(_len3), _key3 = 0; _key3 < _len3; _key3++) { 356 | slotsArray[_key3] = arguments[_key3]; 357 | } 358 | 359 | if (slotsArray.length === 0) { 360 | var slotsToRefresh = Object.keys(registeredSlots).map(function (k) { 361 | return registeredSlots[k]; 362 | }); 363 | return slotsToRefresh.reduce(function (last, slot) { 364 | if (slot.slotShouldRefresh() === true) { 365 | slots[slot.slotId] = slot; 366 | } 367 | 368 | return slots; 369 | }, slots); 370 | } 371 | 372 | return slotsArray.reduce(function (last, slotId) { 373 | var slot = registeredSlots[slotId]; 374 | 375 | if (typeof slot !== 'undefined') { 376 | slots[slotId] = slot; 377 | } 378 | 379 | return slots; 380 | }, slots); 381 | }, 382 | refresh: function refresh() { 383 | var _this6 = this; 384 | 385 | for (var _len4 = arguments.length, slots = new Array(_len4), _key4 = 0; _key4 < _len4; _key4++) { 386 | slots[_key4] = arguments[_key4]; 387 | } 388 | 389 | if (loadPromise === null) { 390 | this.load(); 391 | } else { 392 | loadPromise.then(function () { 393 | _this6.gptRefreshAds(Object.keys(_this6.getRefreshableSlots.apply(_this6, slots))); 394 | }); 395 | } 396 | }, 397 | gptRefreshAds: function gptRefreshAds(slots) { 398 | var _this7 = this; 399 | 400 | return this.getGoogletag().then(function (googletag) { 401 | _this7.configureOptions(googletag); 402 | 403 | googletag.cmd.push(function () { 404 | var pubadsService = googletag.pubads(); 405 | var slotsToRefreshArray = slots.map(function (slotId) { 406 | return registeredSlots[slotId].slotId; 407 | }); 408 | pubadsService.refresh(slotsToRefreshArray); 409 | }); 410 | }); 411 | }, 412 | reload: function reload() { 413 | var _this8 = this; 414 | 415 | return this.destroyGPTSlots.apply(this, arguments).then(function () { 416 | return _this8.load(); 417 | }); 418 | }, 419 | destroyGPTSlots: function destroyGPTSlots() { 420 | var _this9 = this; 421 | 422 | for (var _len5 = arguments.length, slotsToDestroy = new Array(_len5), _key5 = 0; _key5 < _len5; _key5++) { 423 | slotsToDestroy[_key5] = arguments[_key5]; 424 | } 425 | 426 | if (slotsToDestroy.length === 0) { 427 | // eslint-disable-next-line no-param-reassign 428 | slotsToDestroy = Object.keys(registeredSlots); 429 | } 430 | 431 | return new Promise(function (resolve) { 432 | var slots = []; // eslint-disable-next-line guard-for-in,no-restricted-syntax 433 | 434 | for (var idx in slotsToDestroy) { 435 | var slotId = slotsToDestroy[idx]; 436 | var slot = registeredSlots[slotId]; 437 | slots.push(slot); 438 | } 439 | 440 | _this9.getGoogletag().then(function (googletag) { 441 | googletag.cmd.push(function () { 442 | if (managerAlreadyInitialized === true) { 443 | if (slotsToDestroy.length > 0) { 444 | // eslint-disable-next-line guard-for-in,no-restricted-syntax 445 | for (var _idx in slots) { 446 | var _slot = slots[_idx]; 447 | slots.push(_slot.gptSlot); 448 | delete _slot.gptSlot; 449 | } 450 | 451 | googletag.destroySlots(slots); 452 | } else { 453 | googletag.destroySlots(); 454 | } 455 | } 456 | 457 | resolve(slotsToDestroy); 458 | }); 459 | }); 460 | }); 461 | }, 462 | registerSlot: function registerSlot(_ref) { 463 | var _this10 = this; 464 | 465 | var slotId = _ref.slotId, 466 | dfpNetworkId = _ref.dfpNetworkId, 467 | adUnit = _ref.adUnit, 468 | sizes = _ref.sizes, 469 | renderOutOfThePage = _ref.renderOutOfThePage, 470 | sizeMapping = _ref.sizeMapping, 471 | adSenseAttributes = _ref.adSenseAttributes, 472 | targetingArguments = _ref.targetingArguments, 473 | slotShouldRefresh = _ref.slotShouldRefresh; 474 | var autoLoad = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true; 475 | 476 | if (!Object.prototype.hasOwnProperty.call(registeredSlots, slotId)) { 477 | registeredSlots[slotId] = { 478 | slotId: slotId, 479 | sizes: sizes, 480 | renderOutOfThePage: renderOutOfThePage, 481 | dfpNetworkId: dfpNetworkId, 482 | adUnit: adUnit, 483 | adSenseAttributes: adSenseAttributes, 484 | targetingArguments: targetingArguments, 485 | sizeMapping: sizeMapping, 486 | slotShouldRefresh: slotShouldRefresh, 487 | loading: false 488 | }; 489 | this.emit('slotRegistered', { 490 | slotId: slotId 491 | }); 492 | 493 | if (autoLoad === true && loadPromise !== null) { 494 | loadPromise = loadPromise.catch().then(function () { 495 | var slot = registeredSlots[slotId]; 496 | 497 | if (typeof slot !== 'undefined') { 498 | var loading = slot.loading, 499 | gptSlot = slot.gptSlot; 500 | 501 | if (loading === false && !gptSlot) { 502 | _this10.load(slotId); 503 | } 504 | } 505 | }); 506 | } 507 | } 508 | }, 509 | unregisterSlot: function unregisterSlot(_ref2) { 510 | var slotId = _ref2.slotId; 511 | this.destroyGPTSlots(slotId); 512 | delete registeredSlots[slotId]; 513 | }, 514 | getRegisteredSlots: function getRegisteredSlots() { 515 | return registeredSlots; 516 | }, 517 | attachSlotRenderEnded: function attachSlotRenderEnded(cb) { 518 | this.on('slotRenderEnded', cb); 519 | }, 520 | detachSlotRenderEnded: function detachSlotRenderEnded(cb) { 521 | this.removeListener('slotRenderEnded', cb); 522 | }, 523 | attachSlotVisibilityChanged: function attachSlotVisibilityChanged(cb) { 524 | this.on('slotVisibilityChanged', cb); 525 | }, 526 | detachSlotVisibilityChanged: function detachSlotVisibilityChanged(cb) { 527 | this.removeListener('slotVisibilityChanged', cb); 528 | }, 529 | attachSlotIsViewable: function attachSlotIsViewable(cb) { 530 | this.on('impressionViewable', cb); 531 | }, 532 | detachSlotIsViewable: function detachSlotIsViewable(cb) { 533 | this.removeListener('impressionViewable', cb); 534 | } 535 | }); 536 | var _default = DFPManager; 537 | exports.default = _default; -------------------------------------------------------------------------------- /lib/utils.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.loadGPTScript = loadGPTScript; 7 | var GPT_SRC = { 8 | standard: 'securepubads.g.doubleclick.net', 9 | limitedAds: 'pagead2.googlesyndication.com' 10 | }; 11 | 12 | function doloadGPTScript(resolve, reject, limitedAds) { 13 | window.googletag = window.googletag || {}; 14 | window.googletag.cmd = window.googletag.cmd || []; 15 | var scriptTag = document.createElement('script'); 16 | scriptTag.src = "".concat(document.location.protocol, "//").concat(limitedAds ? GPT_SRC.limitedAds : GPT_SRC.standard, "/tag/js/gpt.js"); 17 | scriptTag.async = true; 18 | scriptTag.type = 'text/javascript'; 19 | 20 | scriptTag.onerror = function scriptTagOnError(errs) { 21 | reject(errs); 22 | }; 23 | 24 | scriptTag.onload = function scriptTagOnLoad() { 25 | resolve(window.googletag); 26 | }; 27 | 28 | document.getElementsByTagName('head')[0].appendChild(scriptTag); 29 | } 30 | 31 | function loadGPTScript() { 32 | var limitedAds = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false; 33 | return new Promise(function (resolve, reject) { 34 | doloadGPTScript(resolve, reject, limitedAds); 35 | }); 36 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-dfp", 3 | "version": "0.21.0", 4 | "homepage": "https://github.com/jaanauati/react-dfp/", 5 | "author": { 6 | "name": "Jonatan Alexis Anauati", 7 | "email": "barakawins@gmail.com" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/jaanauati/react-dfp.git" 12 | }, 13 | "scripts": { 14 | "watch": "NODE_ENV=production babel --watch js --out-dir lib", 15 | "build": "NODE_ENV=production babel js --out-dir lib", 16 | "test": "karma start karma.conf.js 2> /dev/null", 17 | "clean": "rm ./lib/*.js", 18 | "eslint": "eslint js 2> /dev/null", 19 | "dist": "npm run eslint && npm run clean && npm run build && npm run test" 20 | }, 21 | "bugs": { 22 | "mail": "barakawins@gmail.com", 23 | "url": "https://github.com/jaanauati/react-dfp/issues/" 24 | }, 25 | "keywords": [ 26 | "react", 27 | "gpt", 28 | "dfp", 29 | "google dfp", 30 | "google doubleclick for publishers", 31 | "advertising", 32 | "react-component" 33 | ], 34 | "main": "lib/index.js", 35 | "peerDependencies": { 36 | "react": ">=0.14.0", 37 | "react-dom": ">=0.14.0" 38 | }, 39 | "devDependencies": { 40 | "@babel/cli": "^7.2.3", 41 | "@babel/core": "^7.2.2", 42 | "@babel/plugin-proposal-class-properties": "^7.3.0", 43 | "@babel/plugin-proposal-object-rest-spread": "^7.3.2", 44 | "@babel/polyfill": "^7.2.5", 45 | "@babel/preset-env": "^7.3.1", 46 | "@babel/preset-react": "^7.0.0", 47 | "array.from": "^1.0.1", 48 | "babel-eslint": "^8.2.2", 49 | "babel-loader": "^8.0.0-beta.3", 50 | "babel-preset-airbnb": "^2.2.3", 51 | "brfs": "^2.0.2", 52 | "chai": "^3.5.0", 53 | "escope": "^3.3.0", 54 | "eslint": "^4.19.1", 55 | "eslint-config-airbnb": "^14.1.0", 56 | "eslint-plugin-babel": "^5.1.0", 57 | "eslint-plugin-import": "^2.12.0", 58 | "eslint-plugin-jsx-a11y": "^4.0.0", 59 | "eslint-plugin-react": "^6.10.3", 60 | "estraverse": "^4.2.0", 61 | "jasmine-core": "^2.99.1", 62 | "jsdom": "^11.12.0", 63 | "karma": "^4.1.0", 64 | "karma-chai": "^0.1.0", 65 | "karma-jasmine": "^2.0.1", 66 | "karma-jsdom-launcher": "^6.1.3", 67 | "karma-mocha": "^1.3.0", 68 | "karma-mocha-reporter": "^2.2.5", 69 | "karma-script-launcher": "^1.0.0", 70 | "karma-sinon": "^1.0.4", 71 | "karma-webpack": "^3.0.5", 72 | "mocha": "^5.2.0", 73 | "react": "^16.4.2", 74 | "react-addons-test-utils": ">= 15.0.2", 75 | "react-dom": "^16.4.2", 76 | "sinon": "^2.3.1", 77 | "sinon-chai": "^2.14.0", 78 | "webpack": "^4.41.6" 79 | }, 80 | "engines": { 81 | "node": ">=6.0.0" 82 | }, 83 | "licenses": [ 84 | { 85 | "type": "MIT", 86 | "url": "http://github.com/jaanauati/react-dfp/raw/master/LICENSE" 87 | } 88 | ], 89 | "dependencies": { 90 | "babel-plugin-transform-object-assign": "^6.22.0", 91 | "prop-types": "^15.6.1" 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /spec/test-adslot.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | 4 | import ReactTestUtils from 'react-dom/test-utils'; 5 | import { expect } from 'chai'; 6 | import sinon from 'sinon'; 7 | 8 | import { AdSlot, DFPManager } from '../lib'; 9 | 10 | describe('AdSlot', () => { 11 | describe('Component markup', () => { 12 | const compProps = { 13 | dfpNetworkId: '1000', 14 | adUnit: 'foo/bar/baz', 15 | sizes: [[728, 90], 'fluid'], 16 | }; 17 | const compTwoProps = { 18 | ...compProps, 19 | slotId: 'testElement', 20 | }; 21 | 22 | it('renders an AdSlot with the given elementId', () => { 23 | const component = ReactTestUtils.renderIntoDocument(); 24 | const box = ReactTestUtils.findRenderedDOMComponentWithClass(component, 'adBox'); 25 | expect(box.id).to.equal('testElement'); 26 | }); 27 | 28 | it('renders two AdSlots and verify that those get different ids', () => { 29 | let componentOne = ReactTestUtils.renderIntoDocument(); 30 | let componentTwo = ReactTestUtils.renderIntoDocument(); 31 | componentOne = ReactTestUtils.findRenderedDOMComponentWithClass(componentOne, 'adBox'); 32 | componentTwo = ReactTestUtils.findRenderedDOMComponentWithClass(componentTwo, 'adBox'); 33 | expect(componentOne.id).to.not.equal(componentTwo.id); 34 | }); 35 | }); 36 | 37 | describe('DFPManager Interaction', () => { 38 | beforeEach(() => { 39 | DFPManager.registerSlot = sinon.spy(DFPManager, 'registerSlot'); 40 | DFPManager.unregisterSlot = sinon.spy(DFPManager, 'unregisterSlot'); 41 | }); 42 | 43 | it('Registers an AdSlot', () => { 44 | const compProps = { 45 | dfpNetworkId: '1000', 46 | adUnit: 'foo/bar/baz', 47 | slotId: 'testElement1', 48 | sizes: [[728, 90]], 49 | }; 50 | 51 | ReactTestUtils.renderIntoDocument(); 52 | 53 | sinon.assert.calledOnce(DFPManager.registerSlot); 54 | sinon.assert.calledWithMatch(DFPManager.registerSlot, compProps); 55 | }); 56 | 57 | it('Registers a refreshable AdSlot', () => { 58 | const compProps = { 59 | dfpNetworkId: '1000', 60 | adUnit: 'foo/bar/baz', 61 | slotId: 'testElement2', 62 | sizes: [[728, 90]], 63 | }; 64 | 65 | ReactTestUtils.renderIntoDocument(); 66 | 67 | expect(DFPManager.getRefreshableSlots()).to.contain.all.keys([compProps.slotId]); 68 | expect(DFPManager.getRefreshableSlots()[compProps.slotId]).to.contain.all.keys(compProps); 69 | }); 70 | 71 | it('Registers a non refreshable AdSlot', () => { 72 | const compProps = { 73 | dfpNetworkId: '1000', 74 | adUnit: 'foo/bar/baz', 75 | slotId: 'testElement3', 76 | sizes: [[728, 90]], 77 | shouldRefresh: () => false, 78 | }; 79 | 80 | ReactTestUtils.renderIntoDocument( 81 | , 82 | ); 83 | expect(Object.keys(DFPManager.getRefreshableSlots()).length).to.equal(0); 84 | }); 85 | 86 | it('Refreshes arbitrary ads', () => { 87 | const compProps = { 88 | dfpNetworkId: '1000', 89 | adUnit: 'foo/bar/baz', 90 | sizes: [[728, 90]], 91 | }; 92 | 93 | ReactTestUtils.renderIntoDocument( 94 |
95 | 96 | false} /> 97 | 98 |
, 99 | ); 100 | 101 | expect(DFPManager.getRefreshableSlots()).to.contain.all.keys( 102 | ['refreshable-1', 'non-refreshable'], 103 | ); 104 | expect( 105 | DFPManager.getRefreshableSlots( 106 | 'refreshable-1', 'refreshable-2', 'foo', 'bar', 107 | ), 108 | ).to.contain.all.keys(['refreshable-1', 'refreshable-2']); 109 | }); 110 | it('Registers an AdSlot with adSense attributes', () => { 111 | const compProps = { 112 | dfpNetworkId: '1000', 113 | adUnit: 'foo/bar/baz', 114 | slotId: 'testElement4', 115 | sizes: [[728, 90]], 116 | adSenseAttributes: { 117 | site_url: 'www.mysite.com', 118 | adsense_border_color: '#000000', 119 | }, 120 | }; 121 | const comp2Props = { 122 | dfpNetworkId: '1000', 123 | adUnit: 'foo/bar/baz', 124 | slotId: 'testElement4-2', 125 | sizes: [[728, 90]], 126 | }; 127 | 128 | ReactTestUtils.renderIntoDocument( 129 | , 130 | ); 131 | ReactTestUtils.renderIntoDocument( 132 | , 133 | ); 134 | expect(DFPManager.getSlotAdSenseAttributes(compProps.slotId)) 135 | .to.deep.equal(compProps.adSenseAttributes); 136 | // make sure there are not side effects 137 | expect(DFPManager.getSlotAdSenseAttributes(comp2Props.slotId)) 138 | .to.equal(null); 139 | }); 140 | 141 | it('Registers an AdSlot without any adSense attribute', () => { 142 | const compProps = { 143 | dfpNetworkId: '1000', 144 | adUnit: 'foo/bar/baz', 145 | slotId: 'testElement4', 146 | sizes: [[728, 90]], 147 | }; 148 | 149 | ReactTestUtils.renderIntoDocument( 150 | , 151 | ); 152 | expect(DFPManager.getSlotAdSenseAttributes(compProps.slotId)) 153 | .to.equal(null); 154 | }); 155 | 156 | it('Registers an AdSlot with custom targeting arguments', () => { 157 | const compProps = { 158 | dfpNetworkId: '1000', 159 | adUnit: 'foo/bar/baz', 160 | slotId: 'testElement4', 161 | sizes: [[728, 90]], 162 | targetingArguments: { team: 'river plate', player: 'pisculichi' }, 163 | }; 164 | 165 | ReactTestUtils.renderIntoDocument( 166 | , 167 | ); 168 | expect(DFPManager.getSlotTargetingArguments(compProps.slotId)) 169 | .to.contain.all.keys(compProps.targetingArguments); 170 | }); 171 | 172 | it('Registers an AdSlot without custom targeting arguments', () => { 173 | const compProps = { 174 | dfpNetworkId: '1000', 175 | adUnit: 'foo/bar/baz', 176 | slotId: 'testElement5', 177 | sizes: [[728, 90]], 178 | }; 179 | 180 | ReactTestUtils.renderIntoDocument( 181 | , 182 | ); 183 | expect(DFPManager.getSlotTargetingArguments(compProps.slotId)).to.equal(null); 184 | }); 185 | 186 | 187 | it('Unregisters an AdSlot', () => { 188 | const compProps = { 189 | dfpNetworkId: '1000', 190 | adUnit: 'foo/bar/baz', 191 | slotId: 'testElement6', 192 | sizes: [[728, 90]], 193 | }; 194 | 195 | const component = ReactTestUtils.renderIntoDocument( 196 | , 197 | ); 198 | 199 | ReactDOM.unmountComponentAtNode(ReactDOM.findDOMNode(component).parentNode); 200 | 201 | sinon.assert.calledOnce(DFPManager.unregisterSlot); 202 | sinon.assert.calledWithMatch(DFPManager.unregisterSlot, 203 | { slotId: compProps.slotId }); 204 | }); 205 | 206 | afterEach(() => { 207 | DFPManager.registerSlot.restore(); 208 | DFPManager.unregisterSlot.restore(); 209 | Object.keys(DFPManager.getRegisteredSlots()).forEach((slotId) => { 210 | DFPManager.unregisterSlot({ slotId }); 211 | }); 212 | }); 213 | }); 214 | }); 215 | -------------------------------------------------------------------------------- /spec/test-dfpslotsprovider.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import TestUtils from 'react-dom/test-utils'; 4 | import { expect } from 'chai'; 5 | import sinon from 'sinon'; 6 | 7 | import { DFPSlotsProvider, AdSlot, DFPManager } from '../lib'; 8 | 9 | describe('DFPSlotsProvider', () => { 10 | describe('GDPR', () => { 11 | beforeAll(() => { 12 | DFPManager.gptLoadAds = sinon.stub( 13 | DFPManager, 14 | 'gptLoadAds', 15 | ).resolves(true); 16 | DFPManager.load = sinon.spy(DFPManager, 'load'); 17 | }); 18 | 19 | it('Fetches personalized ads by default', () => { 20 | const otherProps = { 21 | dfpNetworkId: '1000', 22 | adUnit: 'foo/bar/baz', 23 | }; 24 | TestUtils.renderIntoDocument( 25 | 26 | 27 | , 28 | ); 29 | expect(DFPManager.personalizedAdsEnabled()).to.equal(true); 30 | }); 31 | 32 | it('Can disable personalized ads', () => { 33 | const otherProps = { 34 | dfpNetworkId: '1000', 35 | adUnit: 'foo/bar/baz', 36 | }; 37 | TestUtils.renderIntoDocument( 38 | 39 | 40 | , 41 | ); 42 | expect(DFPManager.personalizedAdsEnabled()).to.equal(false); 43 | }); 44 | 45 | it('Can enable personalized ads', () => { 46 | const otherProps = { 47 | dfpNetworkId: '1000', 48 | adUnit: 'foo/bar/baz', 49 | }; 50 | TestUtils.renderIntoDocument( 51 | 52 | 53 | , 54 | ); 55 | expect(DFPManager.personalizedAdsEnabled()).to.equal(true); 56 | }); 57 | 58 | it('Set cookies by default', () => { 59 | const otherProps = { 60 | dfpNetworkId: '1000', 61 | adUnit: 'foo/bar/baz', 62 | }; 63 | TestUtils.renderIntoDocument( 64 | 65 | 66 | , 67 | ); 68 | expect(DFPManager.cookiesEnabled()).to.equal(true); 69 | }); 70 | 71 | it('Can disable cookies', () => { 72 | const otherProps = { 73 | dfpNetworkId: '1000', 74 | adUnit: 'foo/bar/baz', 75 | }; 76 | TestUtils.renderIntoDocument( 77 | 78 | 79 | , 80 | ); 81 | expect(DFPManager.cookiesEnabled()).to.equal(false); 82 | }); 83 | 84 | it('Can enable cookies', () => { 85 | const otherProps = { 86 | dfpNetworkId: '1000', 87 | adUnit: 'foo/bar/baz', 88 | }; 89 | TestUtils.renderIntoDocument( 90 | 91 | 92 | , 93 | ); 94 | expect(DFPManager.cookiesEnabled()).to.equal(true); 95 | }); 96 | 97 | 98 | afterAll(() => { 99 | DFPManager.gptLoadAds.restore(); 100 | DFPManager.load.restore(); 101 | }); 102 | }); 103 | 104 | describe('Lazy Load', () => { 105 | beforeAll(() => { 106 | DFPManager.gptLoadAds = sinon.stub( 107 | DFPManager, 108 | 'gptLoadAds', 109 | ).resolves(true); 110 | DFPManager.load = sinon.spy(DFPManager, 'load'); 111 | }); 112 | 113 | it('Lazy load disabled by default', () => { 114 | const otherProps = { 115 | dfpNetworkId: '1000', 116 | adUnit: 'foo/bar/baz', 117 | }; 118 | TestUtils.renderIntoDocument( 119 | 120 | 121 | , 122 | ); 123 | expect(DFPManager.lazyLoadIsEnabled()).to.equal(false); 124 | expect(DFPManager.getLazyLoadConfig()).to.equal(null); 125 | }); 126 | 127 | it('Can enable lazy load', () => { 128 | const otherProps = { 129 | dfpNetworkId: '1000', 130 | adUnit: 'foo/bar/baz', 131 | }; 132 | TestUtils.renderIntoDocument( 133 | 134 | 135 | , 136 | ); 137 | expect(DFPManager.lazyLoadIsEnabled()).to.equal(true); 138 | expect(DFPManager.getLazyLoadConfig()).to.equal(null); 139 | }); 140 | 141 | it('Can pass arbitrary configs', () => { 142 | const otherProps = { 143 | dfpNetworkId: '1000', 144 | adUnit: 'foo/bar/baz', 145 | }; 146 | const lazyLoadConfig = { 147 | fetchMarginPercent: 1, 148 | renderMarginPercent: 1, 149 | mobileScaling: 1, 150 | }; 151 | TestUtils.renderIntoDocument( 152 | 153 | 154 | , 155 | ); 156 | expect(DFPManager.lazyLoadIsEnabled()).to.equal(true); 157 | expect(DFPManager.getLazyLoadConfig()).to.deep.equal(lazyLoadConfig); 158 | }); 159 | 160 | it('Can disable lazyLoad', () => { 161 | const otherProps = { 162 | dfpNetworkId: '1000', 163 | adUnit: 'foo/bar/baz', 164 | lazyLoad: false, 165 | }; 166 | TestUtils.renderIntoDocument( 167 | 168 | 169 | , 170 | ); 171 | expect(DFPManager.lazyLoadIsEnabled()).to.equal(false); 172 | expect(DFPManager.getLazyLoadConfig()).to.equal(null); 173 | }); 174 | 175 | afterAll(() => { 176 | DFPManager.gptLoadAds.restore(); 177 | DFPManager.load.restore(); 178 | }); 179 | }); 180 | 181 | describe('Component markup', () => { 182 | let component; 183 | 184 | beforeAll(() => { 185 | DFPManager.gptLoadAds = sinon.stub( 186 | DFPManager, 187 | 'gptLoadAds', 188 | ).resolves(true); 189 | DFPManager.load = sinon.spy(DFPManager, 'load'); 190 | }); 191 | 192 | beforeEach(() => { 193 | const providerProps = { 194 | dfpNetworkId: '1000', 195 | adUnit: 'foo/bar/baz', 196 | }; 197 | 198 | component = TestUtils.renderIntoDocument( 199 | 200 | 201 | , 202 | ); 203 | }); 204 | 205 | it('renders an adBox with the given elementId', () => { 206 | const box = TestUtils.findRenderedDOMComponentWithClass(component, 'adBox'); 207 | expect(box.id).to.equal('testElement3'); 208 | }); 209 | 210 | afterAll(() => { 211 | DFPManager.gptLoadAds.restore(); 212 | DFPManager.load.restore(); 213 | }); 214 | }); 215 | 216 | describe('DFPManager Api calls', () => { 217 | beforeAll(() => { 218 | DFPManager.gptLoadAds = sinon.stub( 219 | DFPManager, 220 | 'gptLoadAds', 221 | ).resolves(true); 222 | }); 223 | 224 | beforeEach(() => { 225 | DFPManager.registerSlot = sinon.spy(DFPManager, 'registerSlot'); 226 | DFPManager.unregisterSlot = sinon.spy(DFPManager, 'unregisterSlot'); 227 | DFPManager.setCollapseEmptyDivs = sinon.spy(DFPManager, 'setCollapseEmptyDivs'); 228 | DFPManager.load = sinon.spy(DFPManager, 'load'); 229 | DFPManager.reload = sinon.spy(DFPManager, 'reload'); 230 | DFPManager.configureLimitedAds = sinon.spy(DFPManager, 'configureLimitedAds'); 231 | }); 232 | 233 | it('Registers an AdSlot', () => { 234 | const providerProps = { 235 | dfpNetworkId: '1000', 236 | adUnit: 'foo/bar/baz', 237 | }; 238 | 239 | const compProps = { 240 | slotId: 'testElement5', 241 | sizes: [[728, 90]], 242 | }; 243 | 244 | TestUtils.renderIntoDocument( 245 | 246 | 247 | , 248 | ); 249 | 250 | sinon.assert.calledOnce(DFPManager.registerSlot); 251 | sinon.assert.calledWithMatch(DFPManager.registerSlot, { ...providerProps, ...compProps }); 252 | sinon.assert.calledOnce(DFPManager.load); 253 | sinon.assert.notCalled(DFPManager.reload); 254 | }); 255 | 256 | it('Does not reload ads when the prop dfpNetworkId is updated', () => { 257 | const providerProps = { 258 | dfpNetworkId: '1000', 259 | adUnit: 'foo/bar/baz', 260 | }; 261 | 262 | const compProps = { 263 | slotId: 'testElement5', 264 | sizes: [[728, 90]], 265 | }; 266 | 267 | 268 | const container = document.createElement('div'); 269 | ReactDOM.render( 270 | 271 | 272 | , 273 | container, 274 | ); 275 | 276 | ReactDOM.render( 277 | 278 | 279 | , 280 | container, 281 | ); 282 | 283 | sinon.assert.calledOnce(DFPManager.registerSlot); 284 | sinon.assert.calledWithMatch(DFPManager.registerSlot, { ...providerProps, ...compProps }); 285 | sinon.assert.calledOnce(DFPManager.load); 286 | sinon.assert.notCalled(DFPManager.reload); 287 | }); 288 | 289 | it('Does not reload ads when the prop personalizedAds is updated', () => { 290 | const providerProps = { 291 | dfpNetworkId: '1000', 292 | adUnit: 'foo/bar/baz', 293 | }; 294 | 295 | const compProps = { 296 | slotId: 'testElement5', 297 | sizes: [[728, 90]], 298 | }; 299 | 300 | const container = document.createElement('div'); 301 | ReactDOM.render( 302 | 303 | 304 | , 305 | container, 306 | ); 307 | 308 | ReactDOM.render( 309 | 310 | 311 | , 312 | container, 313 | ); 314 | 315 | sinon.assert.calledOnce(DFPManager.registerSlot); 316 | sinon.assert.calledWithMatch(DFPManager.registerSlot, { ...providerProps, ...compProps }); 317 | sinon.assert.calledOnce(DFPManager.load); 318 | sinon.assert.notCalled(DFPManager.reload); 319 | }); 320 | 321 | it('Reloads ads when any of the configured props is updated', () => { 322 | const providerProps = { 323 | dfpNetworkId: '1000', 324 | adUnit: 'foo/bar/baz', 325 | autoReload: { personalizedAds: true }, 326 | }; 327 | 328 | const compProps = { 329 | slotId: 'testElement5', 330 | sizes: [[728, 90]], 331 | }; 332 | 333 | 334 | const container = document.createElement('div'); 335 | ReactDOM.render( 336 | 337 | 338 | , 339 | container, 340 | ); 341 | 342 | ReactDOM.render( 343 | 344 | 345 | , 346 | container, 347 | ); 348 | 349 | sinon.assert.calledOnce(DFPManager.registerSlot); 350 | sinon.assert.calledOnce(DFPManager.load); 351 | sinon.assert.calledOnce(DFPManager.reload); 352 | }); 353 | 354 | it('Ads are not reloaded when any of these props is updated: ' 355 | + 'singleRequest, adUnit, sizeMapping, adSenseAttributes, ' 356 | + 'targetingArguments, collapseEmptyDivs, adSenseAttrs, lazyLoad.' 357 | , () => { 358 | const providerProps = { 359 | dfpNetworkId: '1000', 360 | adUnit: 'foo/bar/baz', 361 | singleRequest: false, 362 | lazyLoad: false, 363 | }; 364 | 365 | const compProps = { 366 | slotId: 'testElement5', 367 | sizes: [[728, 90]], 368 | }; 369 | 370 | 371 | const container = document.createElement('div'); 372 | ReactDOM.render( 373 | 374 | 375 | , 376 | container, 377 | ); 378 | const newProps = { 379 | singleRequest: true, 380 | adUnit: 'a/b', 381 | sizeMapping: [ 382 | { viewport: [1024, 768], sizes: [[728, 90], [300, 250]] }, 383 | { viewport: [900, 768], sizes: [[300, 250]] }, 384 | ], 385 | adSenseAttributes: { site_url: 'example.com' }, 386 | targetingArguments: { customKw: 'basic example' }, 387 | collapseEmptyDivs: true, 388 | lazyLoad: true, 389 | }; 390 | 391 | ReactDOM.render( 392 | 393 | 394 | , 395 | container, 396 | ); 397 | 398 | sinon.assert.calledOnce(DFPManager.registerSlot); 399 | sinon.assert.calledOnce(DFPManager.load); 400 | sinon.assert.notCalled(DFPManager.reload); 401 | }); 402 | 403 | it('Can dissable auto-refresh', () => { 404 | const providerProps = { 405 | dfpNetworkId: '1000', 406 | adUnit: 'foo/bar/baz', 407 | }; 408 | 409 | const compProps = { 410 | slotId: 'testElement5', 411 | sizes: [[728, 90]], 412 | }; 413 | 414 | 415 | const container = document.createElement('div'); 416 | ReactDOM.render( 417 | 418 | 419 | , 420 | container, 421 | ); 422 | 423 | ReactDOM.render( 424 | 428 | 429 | , 430 | container, 431 | ); 432 | 433 | sinon.assert.calledOnce(DFPManager.registerSlot); 434 | sinon.assert.calledWithMatch(DFPManager.registerSlot, { ...providerProps, ...compProps }); 435 | sinon.assert.calledOnce(DFPManager.load); 436 | sinon.assert.notCalled(DFPManager.reload); 437 | }); 438 | it('Gets singleRequest enabled by default', () => { 439 | const providerProps = { 440 | dfpNetworkId: '1000', 441 | adUnit: 'foo/bar/baz', 442 | }; 443 | 444 | const compProps = { 445 | slotId: 'testElement6', 446 | sizes: [[728, 90]], 447 | }; 448 | 449 | TestUtils.renderIntoDocument( 450 | 451 | 452 | , 453 | ); 454 | 455 | expect(DFPManager.singleRequestIsEnabled()).equal(true); 456 | }); 457 | 458 | it('Can disable singleRequest', () => { 459 | const providerProps = { 460 | dfpNetworkId: '1000', 461 | adUnit: 'foo/bar/baz', 462 | singleRequest: false, 463 | }; 464 | 465 | const compProps = { 466 | slotId: 'testElement7', 467 | sizes: [[728, 90]], 468 | }; 469 | 470 | TestUtils.renderIntoDocument( 471 | 472 | 473 | , 474 | ); 475 | 476 | expect(DFPManager.singleRequestIsEnabled()).equal(false); 477 | }); 478 | 479 | it('Can enable singleRequest', () => { 480 | const providerProps = { 481 | dfpNetworkId: '1000', 482 | adUnit: 'foo/bar/baz', 483 | singleRequest: true, 484 | }; 485 | 486 | const compProps = { 487 | slotId: 'testElement8', 488 | sizes: [[728, 90]], 489 | }; 490 | 491 | TestUtils.renderIntoDocument( 492 | 493 | 494 | , 495 | ); 496 | 497 | expect(DFPManager.singleRequestIsEnabled()).equal(true); 498 | }); 499 | 500 | it('Disables singleRequest', () => { 501 | const providerProps = { 502 | dfpNetworkId: '1000', 503 | adUnit: 'foo/bar/baz', 504 | singleRequest: false, 505 | }; 506 | 507 | const compProps = { 508 | slotId: 'testElement9', 509 | sizes: [[728, 90]], 510 | }; 511 | 512 | TestUtils.renderIntoDocument( 513 | 514 | 515 | , 516 | ); 517 | 518 | expect(DFPManager.singleRequestIsEnabled()).equal(false); 519 | }); 520 | 521 | it('disableInitialLoad is not enabled by default', () => { 522 | const providerProps = { 523 | dfpNetworkId: '1000', 524 | adUnit: 'foo/bar/baz', 525 | }; 526 | 527 | const compProps = { 528 | slotId: 'testElement8', 529 | sizes: [[728, 90]], 530 | }; 531 | 532 | TestUtils.renderIntoDocument( 533 | 534 | 535 | , 536 | ); 537 | 538 | expect(DFPManager.disableInitialLoadIsEnabled()).equal(false); 539 | }); 540 | 541 | it('Can turn on disableInitialLoad', () => { 542 | const providerProps = { 543 | dfpNetworkId: '1000', 544 | adUnit: 'foo/bar/baz', 545 | disableInitialLoad: true, 546 | }; 547 | 548 | const compProps = { 549 | slotId: 'testElement8', 550 | sizes: [[728, 90]], 551 | }; 552 | 553 | TestUtils.renderIntoDocument( 554 | 555 | 556 | , 557 | ); 558 | 559 | expect(DFPManager.disableInitialLoadIsEnabled()).equal(true); 560 | }); 561 | 562 | it('Can turn off disableInitialLoad', () => { 563 | const providerProps = { 564 | dfpNetworkId: '1000', 565 | adUnit: 'foo/bar/baz', 566 | disableInitialLoad: false, 567 | }; 568 | 569 | const compProps = { 570 | slotId: 'testElement8', 571 | sizes: [[728, 90]], 572 | }; 573 | 574 | TestUtils.renderIntoDocument( 575 | 576 | 577 | , 578 | ); 579 | 580 | expect(DFPManager.disableInitialLoadIsEnabled()).equal(false); 581 | }); 582 | 583 | it('Registers a refreshable AdSlot', () => { 584 | const providerProps = { 585 | dfpNetworkId: '1000', 586 | adUnit: 'foo/bar/baz', 587 | }; 588 | 589 | const compProps = { 590 | slotId: 'testElement10', 591 | sizes: [[728, 90]], 592 | }; 593 | 594 | TestUtils.renderIntoDocument( 595 | 596 | 597 | , 598 | ); 599 | 600 | expect(DFPManager.getRefreshableSlots()).to.contain.all.keys([compProps.slotId]); 601 | expect(DFPManager.getRefreshableSlots()[compProps.slotId]).to.contain.all.keys( 602 | { ...providerProps, ...compProps }, 603 | ); 604 | }); 605 | 606 | it('Registers a non refreshable AdSlot', () => { 607 | const providerProps = { 608 | dfpNetworkId: '1000', 609 | adUnit: 'foo/bar/baz', 610 | }; 611 | 612 | const compProps = { 613 | slotId: 'testElement11', 614 | sizes: [[728, 90]], 615 | shouldRefresh: () => false, 616 | }; 617 | 618 | TestUtils.renderIntoDocument( 619 | 620 | 621 | , 622 | ); 623 | expect(Object.keys(DFPManager.getRefreshableSlots()).length).to.equal(0); 624 | }); 625 | 626 | it('Registers global adSense attributes', () => { 627 | const providerProps = { 628 | dfpNetworkId: '1000', 629 | adUnit: 'foo/bar/baz', 630 | adSenseAttributes: { 631 | site_url: 'www.mysite.com', 632 | adsense_border_color: '#0000FF', 633 | }, 634 | }; 635 | const compProps = { 636 | slotId: 'testElement12', 637 | sizes: [[728, 90]], 638 | }; 639 | TestUtils.renderIntoDocument( 640 | 641 | 642 | , 643 | ); 644 | expect(DFPManager.getAdSenseAttributes()) 645 | .to.deep.equal(providerProps.adSenseAttributes); 646 | expect(DFPManager.getSlotAdSenseAttributes(compProps.slotId)) 647 | .to.deep.equal(null); 648 | }); 649 | 650 | it('Registers an AdSlot with adSense attributes', () => { 651 | const providerProps = { 652 | dfpNetworkId: '1000', 653 | adUnit: 'foo/bar/baz', 654 | adSenseAttributes: { 655 | site_url: 'www.mysite.com', 656 | adsense_border_color: '#0000FF', 657 | }, 658 | }; 659 | const compProps = { 660 | slotId: 'testElement13', 661 | sizes: [[728, 90]], 662 | adSenseAttributes: { 663 | site_url: 'www.mysite.com', 664 | adsense_border_color: '#000000', 665 | adsense_channel_ids: '271828183+314159265', 666 | }, 667 | }; 668 | const comp2Props = { 669 | slotId: 'testElement14', 670 | sizes: [[728, 90]], 671 | }; 672 | TestUtils.renderIntoDocument( 673 | 674 | 675 | 676 | , 677 | ); 678 | expect(DFPManager.getAdSenseAttributes()) 679 | .to.deep.equal(providerProps.adSenseAttributes); 680 | expect(DFPManager.getSlotAdSenseAttributes(compProps.slotId)) 681 | .to.deep.equal(compProps.adSenseAttributes); 682 | expect(DFPManager.getSlotAdSenseAttributes(comp2Props.slotId)) 683 | .to.deep.equal(null); 684 | }); 685 | 686 | it('Registers an AdSlot with custom targeting arguments', () => { 687 | const providerProps = { 688 | dfpNetworkId: '1000', 689 | adUnit: 'foo/bar/baz', 690 | targetingArguments: { team: 'river plate', player: 'pisculichi' }, 691 | }; 692 | const compProps = { 693 | slotId: 'testElement15', 694 | sizes: [[728, 90]], 695 | }; 696 | TestUtils.renderIntoDocument( 697 | 698 | 699 | , 700 | ); 701 | expect(DFPManager.getSlotTargetingArguments(compProps.slotId)) 702 | .to.contain.all.keys(providerProps.targetingArguments); 703 | }); 704 | 705 | it('Registers an AdSlot without custom targeting arguments', () => { 706 | const providerProps = { 707 | dfpNetworkId: '1000', 708 | adUnit: 'foo/bar/baz', 709 | }; 710 | const compProps = { 711 | slotId: 'testElement16', 712 | sizes: [[728, 90]], 713 | }; 714 | 715 | TestUtils.renderIntoDocument( 716 | 717 | 718 | , 719 | ); 720 | expect(DFPManager.getSlotTargetingArguments(compProps.slotId)).to.equal(null); 721 | }); 722 | 723 | 724 | it('Unregisters an AdSlot', () => { 725 | const providerProps = { 726 | dfpNetworkId: '1000', 727 | adUnit: 'foo/bar/baz', 728 | }; 729 | const compProps = { 730 | slotId: 'testElement17', 731 | sizes: [[728, 90]], 732 | }; 733 | 734 | 735 | const component = TestUtils.renderIntoDocument( 736 | 737 | 738 | , 739 | ); 740 | 741 | // eslint-disable-next-line react/no-find-dom-node 742 | ReactDOM.unmountComponentAtNode(ReactDOM.findDOMNode(component).parentNode); 743 | 744 | sinon.assert.calledOnce(DFPManager.unregisterSlot); 745 | sinon.assert.calledWithMatch( 746 | DFPManager.unregisterSlot, 747 | { slotId: compProps.slotId }, 748 | ); 749 | }); 750 | 751 | it('collapseEmptyDivs is disabled by default', () => { 752 | const providerProps = { 753 | dfpNetworkId: '1000', 754 | adUnit: 'foo/bar/baz', 755 | }; 756 | 757 | const compProps = { 758 | slotId: 'testElement18', 759 | sizes: [[728, 90]], 760 | }; 761 | 762 | TestUtils.renderIntoDocument( 763 | 764 | 765 | , 766 | ); 767 | 768 | sinon.assert.calledOnce(DFPManager.setCollapseEmptyDivs); 769 | sinon.assert.calledWith(DFPManager.setCollapseEmptyDivs, null); 770 | }); 771 | 772 | it('enable collapseEmptyDivs and set parameter to false', () => { 773 | const providerProps = { 774 | dfpNetworkId: '1000', 775 | adUnit: 'foo/bar/baz', 776 | collapseEmptyDivs: false, 777 | }; 778 | 779 | const compProps = { 780 | slotId: 'testElement19', 781 | sizes: [[728, 90]], 782 | }; 783 | 784 | TestUtils.renderIntoDocument( 785 | 786 | 787 | , 788 | ); 789 | 790 | sinon.assert.calledOnce(DFPManager.setCollapseEmptyDivs); 791 | sinon.assert.calledWith(DFPManager.setCollapseEmptyDivs, false); 792 | }); 793 | 794 | it('Does configureLimitedAds if prop is provided', () => { 795 | const providerProps = { 796 | dfpNetworkId: '1000', 797 | adUnit: 'foo/bar/baz', 798 | limitedAds: true, 799 | }; 800 | 801 | const container = document.createElement('div'); 802 | ReactDOM.render( 803 | , 804 | container, 805 | ); 806 | 807 | sinon.assert.calledOnce(DFPManager.configureLimitedAds); 808 | sinon.assert.calledWith(DFPManager.configureLimitedAds, true); 809 | }); 810 | 811 | afterEach(() => { 812 | DFPManager.registerSlot.restore(); 813 | DFPManager.unregisterSlot.restore(); 814 | DFPManager.setCollapseEmptyDivs.restore(); 815 | Object.keys(DFPManager.getRegisteredSlots()).forEach((slotId) => { 816 | DFPManager.unregisterSlot({ slotId }); 817 | }); 818 | DFPManager.load.restore(); 819 | DFPManager.reload.restore(); 820 | DFPManager.configureLimitedAds.restore(); 821 | }); 822 | 823 | afterAll(() => { 824 | DFPManager.gptLoadAds.restore(); 825 | }); 826 | }); 827 | }); 828 | -------------------------------------------------------------------------------- /spec/test-manager.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import sinon from 'sinon'; 3 | import { DFPManager } from '../lib'; 4 | 5 | describe('DFPManager', () => { 6 | describe('GDPR - personalized ads', () => { 7 | it('Fetches personalized ads by default', function registersAdSlot() { 8 | expect(DFPManager.personalizedAdsEnabled()).equal(true); 9 | }); 10 | it('Can disable personalized ads', function registersAdSlot() { 11 | DFPManager.configurePersonalizedAds(false); 12 | expect(DFPManager.personalizedAdsEnabled()).equal(false); 13 | }); 14 | it('Can enable personalized ads', function registersAdSlot() { 15 | DFPManager.configurePersonalizedAds(true); 16 | expect(DFPManager.personalizedAdsEnabled()).equal(true); 17 | }); 18 | }); 19 | 20 | describe('GDPR - cookies', () => { 21 | it('Sets cookies by default', function registersAdSlot() { 22 | expect(DFPManager.cookiesEnabled()).equal(true); 23 | }); 24 | it('Can disable cookies', function registersAdSlot() { 25 | DFPManager.configureCookieOption(false); 26 | expect(DFPManager.cookiesEnabled()).equal(false); 27 | }); 28 | it('Can enable cookies', function registersAdSlot() { 29 | DFPManager.configureCookieOption(true); 30 | expect(DFPManager.cookiesEnabled()).equal(true); 31 | }); 32 | }); 33 | 34 | describe('Lazy Loading', () => { 35 | it('Lazy load is disabled by default', function registersAdSlot() { 36 | expect(DFPManager.lazyLoadIsEnabled()).equal(false); 37 | expect(DFPManager.getLazyLoadConfig()).equal(null); 38 | }); 39 | it('Can enable lazy load', function canEnableLazyLoad() { 40 | DFPManager.configureLazyLoad(true); 41 | expect(DFPManager.lazyLoadIsEnabled()).equal(true); 42 | }); 43 | it('There isnt custom config by default', function noConfigByDefault() { 44 | DFPManager.configureLazyLoad(true); 45 | expect(DFPManager.getLazyLoadConfig()).equal(null); 46 | }); 47 | it('Can pass any arbitrary config', function noConfigByDefault() { 48 | DFPManager.configureLazyLoad(true, { renderMarginPercent: 1 }); 49 | expect(DFPManager.getLazyLoadConfig()).to.deep.equal({ 50 | renderMarginPercent: 1, 51 | }); 52 | }); 53 | it('Can disable lazy load', function canDisableLazyLoad() { 54 | DFPManager.configureLazyLoad(false); 55 | expect(DFPManager.lazyLoadIsEnabled()).equal(false); 56 | }); 57 | }); 58 | 59 | describe('Limited Ads', () => { 60 | it('should have Limited Ads disabled by default', function registersAdSlot() { 61 | expect(DFPManager.limitedAdsIsEnabled()).equal(false); 62 | }); 63 | it('should configure Limited Ads', function registersAdSlot() { 64 | DFPManager.configureLimitedAds(true); 65 | expect(DFPManager.limitedAdsIsEnabled()).equal(true); 66 | }); 67 | }); 68 | 69 | describe('Single Request', () => { 70 | it('Gets singleRequest enabled by default', function registersAdSlot() { 71 | expect(DFPManager.singleRequestIsEnabled()).equal(true); 72 | }); 73 | it('Can disable singleRequest', function registersAdSlot() { 74 | DFPManager.configureSingleRequest(false); 75 | expect(DFPManager.singleRequestIsEnabled()).equal(false); 76 | }); 77 | it('Can enable singleRequest', function registersAdSlot() { 78 | DFPManager.configureSingleRequest(true); 79 | expect(DFPManager.singleRequestIsEnabled()).equal(true); 80 | }); 81 | }); 82 | 83 | describe('Disable Initial Load', () => { 84 | it('disableInitiaLoad disabled by default', function testDisableInitialLoad1() { 85 | expect(DFPManager.disableInitialLoadIsEnabled()).equal(false); 86 | }); 87 | it('Can enable disableInitialLoad', function testDisableInitialLoad2() { 88 | DFPManager.configureDisableInitialLoad(true); 89 | expect(DFPManager.disableInitialLoadIsEnabled()).equal(true); 90 | }); 91 | it('Can disable disableInitialLoad', function testDisableInitialLoad3() { 92 | DFPManager.configureDisableInitialLoad(false); 93 | expect(DFPManager.disableInitialLoadIsEnabled()).equal(false); 94 | }); 95 | }); 96 | 97 | describe('AdSense attributes', () => { 98 | beforeEach(function beforeEach() { 99 | this.argsList1 = { 100 | page_url: 'www.mysite.com', 101 | adsense_url_color: '#000000', 102 | }; 103 | this.argsList2 = { adsense_ad_format: '250x250_as' }; 104 | DFPManager.setAdSenseAttributes(this.argsList1); 105 | DFPManager.setAdSenseAttribute('adsense_ad_format', '250x250_as'); 106 | }); 107 | 108 | it('Properly tracks global AdSense attributes', function registersAdSlot() { 109 | expect(DFPManager.getAdSenseAttributes()).to.contain.keys( 110 | { ...this.argsList1, ...this.argsList2 }, 111 | ); 112 | }); 113 | }); 114 | 115 | describe('Targeting arguments', () => { 116 | beforeEach(function beforeEach() { 117 | this.argsList1 = { k: 'yeah' }; 118 | this.argsList2 = { k: 'yeah' }; 119 | DFPManager.setTargetingArguments(this.argsList1); 120 | DFPManager.setTargetingArguments(this.argsList2); 121 | }); 122 | 123 | it('Registers global targeting arguments', function registersAdSlot() { 124 | expect(DFPManager.getTargetingArguments()).to.contain.keys( 125 | { ...this.argsList1, ...this.argsList2 }, 126 | ); 127 | }); 128 | }); 129 | 130 | describe('Creation of ad slots ', () => { 131 | beforeAll(function before() { 132 | DFPManager.gptLoadAds = sinon.stub( 133 | DFPManager, 134 | 'gptLoadAds', 135 | ).resolves(true); 136 | DFPManager.gptRefreshAds = sinon.stub( 137 | DFPManager, 138 | 'gptRefreshAds', 139 | ).resolves(true); 140 | DFPManager.destroyGPTSlots = sinon.stub( 141 | DFPManager, 142 | 'destroyGPTSlots', 143 | ).resolves(true); 144 | this.slotProps = { 145 | dfpNetworkId: '1000', 146 | adUnit: 'foo/bar/baz', 147 | sizes: [[728, 90]], 148 | adSenseAttributes: { 149 | site_url: 'www.mysite.com', 150 | adsense_border_color: '#000000', 151 | }, 152 | slotShouldRefresh: () => true, 153 | }; 154 | DFPManager.registerSlot({ ...this.slotProps, slotId: 'testElement1' }); 155 | DFPManager.registerSlot({ ...this.slotProps, slotId: 'testElement2' }); 156 | DFPManager.registerSlot({ ...this.slotProps, slotId: 'testElement3' }); 157 | DFPManager.load(); 158 | DFPManager.refresh(); 159 | }); 160 | 161 | it('Registers ad slots', function registersAdSlot() { 162 | expect(Object.keys(DFPManager.getRegisteredSlots()).length).to.equal(3); 163 | expect(DFPManager.getRegisteredSlots()).to.contain.all 164 | .keys(['testElement1', 'testElement2', 'testElement3']); 165 | expect(DFPManager.getRegisteredSlots().testElement1) 166 | .to.contain.all.keys({ ...this.slotProps, slotId: 'testElement1' }); 167 | expect(DFPManager.getRegisteredSlots().testElement2) 168 | .to.contain.all.keys({ ...this.slotProps, slotId: 'testElement2' }); 169 | expect(DFPManager.getRegisteredSlots().testElement3) 170 | .to.contain.all.keys({ ...this.slotProps, slotId: 'testElement3' }); 171 | expect(DFPManager.getRegisteredSlots().testElement1) 172 | .to.deep.include({ ...this.slotProps, slotId: 'testElement1' }); 173 | expect(DFPManager.getRegisteredSlots().testElement2) 174 | .to.deep.include({ ...this.slotProps, slotId: 'testElement2' }); 175 | expect(DFPManager.getRegisteredSlots().testElement3) 176 | .to.deep.include({ ...this.slotProps, slotId: 'testElement3' }); 177 | }); 178 | 179 | it('Loads all the ads by default', function adsLoaded() { 180 | sinon.assert.calledOnce(DFPManager.gptLoadAds); 181 | sinon.assert.calledWith( 182 | DFPManager.gptLoadAds, 183 | ['testElement1', 'testElement2', 'testElement3'], 184 | ); 185 | }); 186 | 187 | it('Refreshes all the ads by default', function adsLoaded() { 188 | sinon.assert.calledOnce(DFPManager.gptRefreshAds); 189 | sinon.assert.calledWith( 190 | DFPManager.gptRefreshAds, 191 | ['testElement1', 'testElement2', 'testElement3'], 192 | ); 193 | }); 194 | 195 | afterAll(function afterEach() { 196 | DFPManager.unregisterSlot({ ...this.slotProps, slotId: 'testElement1' }); 197 | DFPManager.unregisterSlot({ ...this.slotProps, slotId: 'testElement2' }); 198 | DFPManager.unregisterSlot({ ...this.slotProps, slotId: 'testElement3' }); 199 | DFPManager.gptLoadAds.restore(); 200 | DFPManager.gptRefreshAds.restore(); 201 | DFPManager.destroyGPTSlots.restore(); 202 | }); 203 | }); 204 | 205 | describe('Initalization of arbitrary slots ', () => { 206 | beforeAll(function before() { 207 | DFPManager.gptLoadAds = sinon.stub( 208 | DFPManager, 209 | 'gptLoadAds', 210 | ).resolves(true); 211 | DFPManager.gptRefreshAds = sinon.stub( 212 | DFPManager, 213 | 'gptRefreshAds', 214 | ).resolves(true); 215 | DFPManager.destroyGPTSlots = sinon.stub( 216 | DFPManager, 217 | 'destroyGPTSlots', 218 | ).resolves(true); 219 | this.slotProps = { 220 | dfpNetworkId: '1000', 221 | adUnit: 'foo/bar/baz', 222 | sizes: [[728, 90]], 223 | adSenseAttributes: { 224 | site_url: 'www.mysite.com', 225 | adsense_border_color: '#000000', 226 | }, 227 | slotShouldRefresh: () => true, 228 | }; 229 | DFPManager.registerSlot({ ...this.slotProps, slotId: 'testElement4' }, false); 230 | DFPManager.registerSlot({ ...this.slotProps, slotId: 'testElement5' }, false); 231 | DFPManager.registerSlot({ ...this.slotProps, slotId: 'testElement6' }, false); 232 | DFPManager.registerSlot({ ...this.slotProps, slotId: 'testElement7' }, false); 233 | DFPManager.load('testElement4', 'testElement6', 'testElement7'); 234 | DFPManager.refresh('testElement4', 'testElement7'); 235 | }); 236 | 237 | it('Loads arbitrary ads', function adsLoaded() { 238 | sinon.assert.calledOnce(DFPManager.gptLoadAds); 239 | sinon.assert.calledWith( 240 | DFPManager.gptLoadAds, 241 | ['testElement4', 'testElement6', 'testElement7'], 242 | ); 243 | }); 244 | 245 | it('Refreshes arbitrary ads', function adsLoaded() { 246 | sinon.assert.calledOnce(DFPManager.gptRefreshAds); 247 | sinon.assert.calledWith( 248 | DFPManager.gptRefreshAds, ['testElement4', 'testElement7'], 249 | ); 250 | }); 251 | 252 | afterAll(function afterEach() { 253 | DFPManager.unregisterSlot({ ...this.slotProps, slotId: 'testElement4' }); 254 | DFPManager.unregisterSlot({ ...this.slotProps, slotId: 'testElement5' }); 255 | DFPManager.unregisterSlot({ ...this.slotProps, slotId: 'testElement6' }); 256 | DFPManager.unregisterSlot({ ...this.slotProps, slotId: 'testElement7' }); 257 | DFPManager.gptLoadAds.restore(); 258 | DFPManager.gptRefreshAds.restore(); 259 | DFPManager.destroyGPTSlots.restore(); 260 | }); 261 | }); 262 | }); 263 | --------------------------------------------------------------------------------