GithubHelp home page GithubHelp logo

storipress / vue-advertising Goto Github PK

View Code? Open in Web Editor NEW
18.0 1.0 2.0 5.16 MB

Simplify programmatic ad-ops in Vue applications.

Home Page: https://storipress.com/features/engage-monetise#programmatic

TypeScript 21.68% JavaScript 49.46% Vue 28.86%
ads advertising library vue adops dfp doubleclick doubleclick-for-publishers gpt mit-license

vue-advertising's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar

Forkers

pochenkuo lapnd

vue-advertising's Issues

Dependency Dashboard

This issue lists Renovate updates and detected dependencies. Read the Dependency Dashboard docs to learn more.

Edited/Blocked

These updates have been manually edited so Renovate will no longer make changes. To discard all commits and start over, click on a checkbox.

Open

These updates have all been created already. Click a checkbox below to force a retry/rebase of any.

Detected dependencies

github-actions
.github/workflows/release-please.yml
  • google-github-actions/release-please-action v4
  • actions/checkout v4
  • actions/setup-node v4
.github/workflows/unit-test.yml
  • actions/checkout v4
  • actions/setup-node v4
npm
package.json
  • @guanghechen/fast-deep-equal ^2.3.7
  • @nuxt/kit ^3.3.2
  • @vueuse/core ^10.0.0
  • vue3-lazy-hydration ^1.2.1
  • @unhead/vue 1.9.13
  • happy-dom 13.10.1
  • sass 1.77.5
  • typescript 5.4.5
  • unbuild 2.0.0
  • vite 5.2.13
  • vitest 1.6.0
  • vue 3.4.27
  • vue ^3.0.0
  • yarn 4.1.0
playground/package.json
  • @guanghechen/fast-deep-equal ^2.3.7
  • @vueuse/core ^10.0.0
  • vue3-lazy-hydration ^1.2.1
  • @nuxt/kit 3.12.1
  • @unhead/vue 1.9.13
  • happy-dom 13.10.1
  • nuxt 3.12.1
  • sass 1.77.5
  • typescript 5.4.5
  • unbuild 2.0.0
  • vite 5.2.13
  • vitest 1.6.0
  • vue 3.4.27
  • vue ^3.0.0
  • yarn 3.5.1

  • Check this box to trigger a request for Renovate to run again on this repository

chore: break dwon the Advertising.js

const EXECUTE_PLUGINS_ACTION = {
  SETUP: 'setup',
  DISPLAYSLOTS: 'displaySlots',
  DISPLAYOUTOFPAGESLOT: 'displayOutOfPageSlot',
  REFRESHINTERSTITIALSLOT: 'refreshInterstitialSlot',
  SETUPPREBID: 'setupPrebid',
  TEARDOWNPREBID: 'teardownPrebid',
  SETUPGPT: 'setupGpt',
  TEARDOWNGPT: 'teardownGpt',
}

export default class Advertising {
  constructor(config, plugins = [], onError = () => { }) {
    this.config = config
    this.slots = {}
    this.outOfPageSlots = {}
    this.plugins = plugins
    this.onError = onError
    this.gptSizeMappings = {}
    this.customEventCallbacks = {}
    this.customEventHandlers = {}
    this.queue = []
    this.setDefaultConfig()
    this.defaultLazyLoadConfig = {
      marginPercent: 0,
      mobileScaling: 1,
    }

    this.requestManager = {
      aps: false,
      prebid: false,
    }
  }


  // ---------- PUBLIC METHODS ----------

  async setup() {
    this.isPrebidUsed =
      typeof this.config.usePrebid === 'undefined' ? typeof window.pbjs !== 'undefined' : this.config.usePrebid
    this.isAPSUsed =
      typeof this.config.useAPS === 'undefined' ? typeof window.apstag !== 'undefined' : this.config.useAPS
    this.executePlugins(EXECUTE_PLUGINS_ACTION.SETUP)
    const { slots, outOfPageSlots, queue, isPrebidUsed, isAPSUsed } = this
    this.setupCustomEvents()
    const setUpQueueItems = [Advertising.queueForGPT(this.setupGpt.bind(this), this.onError)]
    if (isAPSUsed) {
      this.initApstag()
    }
    if (isPrebidUsed) {
      setUpQueueItems.push(Advertising.queueForPrebid(this.setupPrebid.bind(this), this.onError))
    }

    await Promise.all(setUpQueueItems)
    if (queue.length === 0) {
      return
    }
    this.setCustomEventCallbackByQueue(queue)
    const { divIds, selectedSlots } = getDivIdsAndSlots(queue, outOfPageSlots, slots)

    if (isPrebidUsed) {
      Advertising.queueForPrebid(this.getPbjFetchBidsCallback(divIds, selectedSlots), this.onError)
    }

    if (this.isAPSUsed) {
      this.apstagFetchBids(selectedSlots, selectedSlots)
    }

    if (!isPrebidUsed && !isAPSUsed) {
      Advertising.queueForGPT(() => window.googletag.pubads().refresh(selectedSlots), this.onError)
    }
  }

  async teardown() {
    this.teardownCustomEvents()
    const teardownQueueItems = [Advertising.queueForGPT(this.teardownGpt.bind(this), this.onError)]
    if (this.isPrebidUsed) {
      teardownQueueItems.push(Advertising.queueForPrebid(this.teardownPrebid.bind(this), this.onError))
    }
    await Promise.all(teardownQueueItems)
    this.slots = {}
    this.gptSizeMappings = {}
    this.queue = []
  }

  activate(id, customEventHandlers = {}) {
    const { slots, isPrebidUsed } = this
    // check if have slots from configurations
    if (Object.values(slots).length === 0 && id) {
      this.queue.push({ id, customEventHandlers })
      return
    }
    this.setCustomEventCallback(id, customEventHandlers)

    if (isPrebidUsed) {
      Advertising.queueForPrebid(this.getPbjFetchBidsCallback([id], [slots[id].gpt]), this.onError)
    }

    if (this.isAPSUsed) {
      this.apstagFetchBids([slots[id]], [slots[id].gpt]);
    }

    if (!this.isPrebidUsed && !this.isAPSUsed) {
      Advertising.queueForGPT(() => window.googletag.pubads().refresh([slots[id].gpt]), this.onError)
    }
  }

  isConfigReady() {
    return Boolean(this.config)
  }

  setConfig(config) {
    this.config = config
    this.setDefaultConfig()
  }

  // ---------- PRIVATE METHODS ----------
  apstagFetchBids(selectedSlots, checkedSlots) {
    try {
      window.apstag.fetchBids(
        {
          slots: selectedSlots.map((slot) => slot.aps),
        },
        () => {
          Advertising.queueForGPT(() => {
            window.apstag.setDisplayBids()
            this.requestManager.aps = true // signals that APS request has completed
            this.refreshSlots(checkedSlots) // checks whether both APS and Prebid have returned
          }, this.onError)
        }
      )
    } catch (error) {
      this.onError(error)
    }
  }

  getPbjFetchBidsCallback(divIds, selectedSlots) {
    return () =>
      window.pbjs.requestBids({
        adUnitCodes: divIds,
        bidsBackHandler: () => {
          window.pbjs.setTargetingForGPTAsync(divIds)
          this.requestManager.prebid = true
          this.refreshSlots(selectedSlots)
        },
      })
  }

  getDivIdsAndSlots(queue, outOfPageSlots, slots) {
    const divIds = []
    const selectedSlots = []
    queue.forEach(({ id }) => {
      if (id) {
        divIds.push(id)
      }
      selectedSlots.push(slots[id]?.gpt || outOfPageSlots[id])
    })
    return { divIds, selectedSlots }
  }

  setCustomEventCallbackByQueue(queue) {
    for (let i = 0; i < queue.length; i++) {
      const { id, customEventHandlers } = queue[i]
      this.setCustomEventCallback(id, customEventHandlers)
    }
  }
  setCustomEventCallback(id, customEventHandlers) {
    Object.keys(customEventHandlers).forEach((customEventId) => {
      if (!this.customEventCallbacks[customEventId]) {
        this.customEventCallbacks[customEventId] = {}
      }
      this.customEventCallbacks[customEventId][id] = customEventHandlers[customEventId]
    })
  }

  initApstag() {
    try {
      window.apstag.init({
        ...this.config.aps,
        adServer: 'googletag',
      })
    } catch (error) {
      this.onError(error)
    }
  }

  setupCustomEvents() {
    if (!this.config.customEvents) {
      return
    }
    Object.keys(this.config.customEvents).forEach((customEventId) =>
      this.setupCustomEvent(customEventId, this.config.customEvents[customEventId])
    )
  }

  setupCustomEvent(customEventId, { eventMessagePrefix, divIdPrefix }) {
    const { customEventCallbacks } = this
    this.customEventHandlers[customEventId] = ({ data }) => {
      if (typeof data !== 'string' || !data.startsWith(`${eventMessagePrefix}`)) {
        return
      }
      const divId = `${divIdPrefix || ''}${data.substr(eventMessagePrefix.length)}`
      const callbacks = customEventCallbacks[customEventId]
      if (!callbacks) {
        return
      }
      const callback = callbacks[divId]
      if (callback) {
        callback()
      }
    }
    window.addEventListener('message', this.customEventHandlers[customEventId])
  }

  teardownCustomEvents() {
    if (!this.config.customEvents) {
      return
    }
    Object.keys(this.config.customEvents).forEach((customEventId) =>
      window.removeEventListener('message', this.customEventHandlers[customEventId])
    )
  }

  defineGptSizeMappings() {
    if (!this.config.sizeMappings) {
      return
    }
    const entries = Object.entries(this.config.sizeMappings)
    for (let i = 0; i < entries.length; i++) {
      const [key, value] = entries[i]
      const sizeMapping = window.googletag.sizeMapping()
      for (let q = 0; q < value.length; q++) {
        const { viewPortSize, sizes } = value[q]
        sizeMapping.addSize(viewPortSize, sizes)
      }
      this.gptSizeMappings[key] = sizeMapping.build()
    }
  }

  getGptSizeMapping(sizeMappingName) {
    return sizeMappingName && this.gptSizeMappings[sizeMappingName] ? this.gptSizeMappings[sizeMappingName] : null
  }

  defineSlots() {
    if (!this.config.slots) {
      return
    }
    this.config.slots.forEach(({ id, path, collapseEmptyDiv, targeting = {}, sizes, sizeMappingName }) => {
      const gptSlot = window.googletag.defineSlot(path || this.config.path, sizes, id)

      const sizeMapping = this.getGptSizeMapping(sizeMappingName)
      if (sizeMapping) {
        gptSlot.defineSizeMapping(sizeMapping)
      }

      if (collapseEmptyDiv && collapseEmptyDiv.length && collapseEmptyDiv.length > 0) {
        gptSlot.setCollapseEmptyDiv(...collapseEmptyDiv)
      }

      const entries = Object.entries(targeting)
      for (let i = 0; i < entries.length; i++) {
        const [key, value] = entries[i]
        gptSlot.setTargeting(key, value)
      }

      gptSlot.addService(window.googletag.pubads())

      const apsSlot = {
        slotID: id,
        slotName: path,
        sizes: sizes.filter(
          // APS requires sizes to have type number[][]. Each entry in sizes
          // should be an array containing height and width.
          (size) => typeof size === 'object' && typeof size[0] === 'number' && typeof size[1] === 'number'
        ),
      }

      this.slots[id] = { gpt: gptSlot, aps: apsSlot }
    })
  }

  defineOutOfPageSlots() {
    if (this.config.outOfPageSlots) {
      this.config.outOfPageSlots.forEach(({ id, path }) => {
        const slot = window.googletag.defineOutOfPageSlot(path || this.config.path, id)
        slot.addService(window.googletag.pubads())
        this.outOfPageSlots[id] = slot
      })
    }
  }

  defineInterstitialSlot() {
    if (this.config.interstitialSlot) {
      const { path, targeting } = this.config.interstitialSlot
      const slot = window.googletag.defineOutOfPageSlot(
        path || this.config.path,
        window.googletag.enums.OutOfPageFormat.INTERSTITIAL
      )
      if (slot) {
        const entries = Object.entries(targeting || [])
        for (let i = 0; i < entries.length; i++) {
          const [key, value] = entries[i]
          slot.setTargeting(key, value)
        }
        slot.addService(window.googletag.pubads())
        this.interstitialSlot = slot
      }
    }
  }

  displaySlots() {
    this.executePlugins(EXECUTE_PLUGINS_ACTION.DISPLAYSLOTS)
    this.config.slots.forEach(({ id }) => {
      window.googletag.display(id)
    })
  }

  displayOutOfPageSlots() {
    this.executePlugins(EXECUTE_PLUGINS_ACTION.DISPLAYOUTOFPAGESLOT)
    if (this.config.outOfPageSlots) {
      this.config.outOfPageSlots.forEach(({ id }) => {
        window.googletag.display(id)
      })
    }
  }

  refreshInterstitialSlot() {
    this.executePlugins(EXECUTE_PLUGINS_ACTION.REFRESHINTERSTITIALSLOT)
    if (this.interstitialSlot) {
      window.googletag.pubads().refresh([this.interstitialSlot])
    }
  }

  getAdUnits(slots) {
    return slots.reduce(
      (acc, currSlot) =>
        acc.concat(
          currSlot.prebid.map((currPrebid) => ({
            code: currSlot.id,
            mediaTypes: currPrebid.mediaTypes,
            bids: currPrebid.bids,
          }))
        ),
      []
    )
  }

  setupPrebid() {
    this.executePlugins(EXECUTE_PLUGINS_ACTION.SETUPPREBID)
    const adUnits = this.getAdUnits(this.config.slots)
    window.pbjs.addAdUnits(adUnits)
    window.pbjs.setConfig(this.config.prebid)
  }

  teardownPrebid() {
    this.executePlugins(EXECUTE_PLUGINS_ACTION.TEARDOWNPREBID)
    this.getAdUnits(this.config.slots).forEach(({ code }) => window.pbjs.removeAdUnit(code))
  }

  setupGpt() {
    this.executePlugins(EXECUTE_PLUGINS_ACTION.SETUPGPT)
    const pubads = window.googletag.pubads()
    const { targeting } = this.config
    this.defineGptSizeMappings()
    this.defineSlots()
    this.defineOutOfPageSlots()
    this.defineInterstitialSlot()
    const entries = Object.entries(targeting)
    for (let i = 0; i < entries.length; i++) {
      const [key, value] = entries[i]
      pubads.setTargeting(key, value)
    }
    pubads.disableInitialLoad()
    pubads.enableSingleRequest()

    window.googletag.enableServices()
    this.displaySlots()
    this.displayOutOfPageSlots()
    this.refreshInterstitialSlot()
  }

  teardownGpt() {
    this.executePlugins(EXECUTE_PLUGINS_ACTION.TEARDOWNGPT)
    window.googletag.destroySlots()
  }

  setDefaultConfig() {
    if (!this.config) {
      return
    }
    if (!this.config.prebid) {
      this.config.prebid = {}
    }
    if (!this.config.metaData) {
      this.config.metaData = {}
    }
    if (!this.config.targeting) {
      this.config.targeting = {}
    }
    if (this.config.enableLazyLoad === true) {
      this.config.enableLazyLoad = this.defaultLazyLoadConfig
    }
    if (this.config.slots) {
      this.config.slots = this.config.slots.map((slot) =>
        slot.enableLazyLoad === true ? { ...slot, enableLazyLoad: this.defaultLazyLoadConfig } : slot
      )
    }
  }

  executePlugins(method) {
    for (let i = 0; i < this.plugins.length; i++) {
      const func = this.plugins[i][method]
      if (func) {
        func.call(this)
      }
    }
  }

  // when both APS and Prebid have returned, initiate ad request
  refreshSlots(selectedSlots) {
    // If using APS, we need to check that we got a bid from APS.
    // If using Prebid, we need to check that we got a bid from Prebid.
    if (this.isAPSUsed !== this.requestManager.aps || this.isPrebidUsed !== this.requestManager.prebid) {
      return
    }

    Advertising.queueForGPT(() => {
      window.googletag.pubads().refresh(selectedSlots)
    }, this.onError)

    this.requestManager.aps = false
    this.requestManager.prebid = false
  }

  static queueForGPT(func, onError) {
    return Advertising.withQueue(window.googletag.cmd, func, onError)
  }

  static queueForPrebid(func, onError) {
    return Advertising.withQueue(window.pbjs.que, func, onError)
  }

  static withQueue(queue, func, onError) {
    return new Promise((resolve) =>
      queue.push(() => {
        try {
          func()
          resolve()
        } catch (error) {
          onError(error)
        }
      })
    )
  }
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.