prebid/Prebid.js

View on GitHub
modules/afpBidAdapter.js

Summary

Maintainability
B
7 hrs
Test Coverage
import {includes} from '../src/polyfill.js';
import {registerBidder} from '../src/adapters/bidderFactory.js';
import {Renderer} from '../src/Renderer.js';
import {BANNER, VIDEO} from '../src/mediaTypes.js';

export const IS_DEV = location.hostname === 'localhost'
export const BIDDER_CODE = 'afp'
export const SSP_ENDPOINT = 'https://ssp.afp.ai/api/prebid'
export const REQUEST_METHOD = 'POST'
// TODO: test code should be kept in tests
export const TEST_PAGE_URL = 'https://rtbinsight.ru/smiert-bolshikh-dannykh-kto-na-novienkogo/'
const SDK_PATH = 'https://cdn.afp.ai/ssp/sdk.js?auto_initialization=false&deploy_to_parent_window=true'
const TTL = 60
export const IN_IMAGE_BANNER_TYPE = 'In-image'
export const IN_IMAGE_MAX_BANNER_TYPE = 'In-image Max'
export const IN_CONTENT_BANNER_TYPE = 'In-content Banner'
export const IN_CONTENT_VIDEO_TYPE = 'In-content Video'
export const OUT_CONTENT_VIDEO_TYPE = 'Out-content Video'
export const IN_CONTENT_STORY_TYPE = 'In-content Stories'
export const ACTION_SCROLLER_TYPE = 'Action Scroller'
export const ACTION_SCROLLER_LIGHT_TYPE = 'Action Scroller Light'
export const JUST_BANNER_TYPE = 'Just Banner'

export const mediaTypeByPlaceType = {
  [IN_IMAGE_BANNER_TYPE]: BANNER,
  [IN_IMAGE_MAX_BANNER_TYPE]: BANNER,
  [IN_CONTENT_BANNER_TYPE]: BANNER,
  [IN_CONTENT_STORY_TYPE]: BANNER,
  [ACTION_SCROLLER_TYPE]: BANNER,
  [ACTION_SCROLLER_LIGHT_TYPE]: BANNER,
  [JUST_BANNER_TYPE]: BANNER,
  [IN_CONTENT_VIDEO_TYPE]: VIDEO,
  [OUT_CONTENT_VIDEO_TYPE]: VIDEO,
}

const wrapAd = (dataToCreatePlace) => {
  return `<!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <style>html, body {width: 100%; height: 100%; margin: 0;}</style>
        <script src="${SDK_PATH}"></script>
    </head>
    <body>
        <script>
            window.afp.createPlaceByData(JSON.parse(decodeURIComponent("${encodeURIComponent(JSON.stringify(dataToCreatePlace))}")))
        </script>
    </body>
  </html>`
}

const bidRequestMap = {}

const createRenderer = (bid, dataToCreatePlace) => {
  const renderer = new Renderer({
    targetId: bid.adUnitCode,
    url: SDK_PATH,
    callback() {
      renderer.loaded = true
      window.afp.createPlaceByData(dataToCreatePlace)
    }
  })

  return renderer
}

export const spec = {
  code: BIDDER_CODE,
  supportedMediaTypes: [BANNER, VIDEO],
  isBidRequestValid({mediaTypes, params}) {
    if (typeof params !== 'object' || typeof mediaTypes !== 'object') {
      return false
    }

    const {placeId, placeType, imageUrl, imageWidth, imageHeight} = params
    const media = mediaTypes[mediaTypeByPlaceType[placeType]]

    if (placeId && media) {
      if (mediaTypeByPlaceType[placeType] === VIDEO) {
        if (!media.playerSize) {
          return false
        }
      } else if (mediaTypeByPlaceType[placeType] === BANNER) {
        if (!media.sizes) {
          return false
        }
      }
      if (includes([IN_IMAGE_BANNER_TYPE, IN_IMAGE_MAX_BANNER_TYPE], placeType)) {
        if (imageUrl && imageWidth && imageHeight) {
          return true
        }
      } else {
        return true
      }
    }
    return false
  },
  buildRequests(validBidRequests, {refererInfo, gdprConsent}) {
    const payload = {
      pageUrl: IS_DEV ? TEST_PAGE_URL : refererInfo.page,
      gdprConsent: gdprConsent,
      bidRequests: validBidRequests.map(validBidRequest => {
        const {bidId, ortb2Imp, sizes, params: {
          placeId, placeType, imageUrl, imageWidth, imageHeight
        }} = validBidRequest
        bidRequestMap[bidId] = validBidRequest
        const bidRequest = {
          bidId,
          transactionId: ortb2Imp?.ext?.tid,
          sizes,
          placeId,
        }
        if (includes([IN_IMAGE_BANNER_TYPE, IN_IMAGE_MAX_BANNER_TYPE], placeType)) {
          Object.assign(bidRequest, {
            imageUrl,
            imageWidth: Math.floor(imageWidth),
            imageHeight: Math.floor(imageHeight),
          })
        }
        return bidRequest
      })
    }

    return {
      method: REQUEST_METHOD,
      url: SSP_ENDPOINT,
      data: payload,
      options: {
        contentType: 'application/json'
      }
    }
  },
  interpretResponse(serverResponse) {
    let bids = serverResponse.body && serverResponse.body.bids
    bids = Array.isArray(bids) ? bids : []

    return bids.map(({bidId, cpm, width, height, creativeId, currency, netRevenue, adSettings, placeSettings}, index) => {
      const bid = {
        requestId: bidId,
        cpm,
        width,
        height,
        creativeId,
        currency,
        netRevenue,
        meta: {
          mediaType: mediaTypeByPlaceType[placeSettings.placeType],
        },
        ttl: TTL
      }

      const bidRequest = bidRequestMap[bidId]
      const placeContainer = bidRequest.params.placeContainer
      const dataToCreatePlace = { adSettings, placeSettings, placeContainer, isPrebid: true }

      if (mediaTypeByPlaceType[placeSettings.placeType] === BANNER) {
        bid.ad = wrapAd(dataToCreatePlace)
      } else if (mediaTypeByPlaceType[placeSettings.placeType] === VIDEO) {
        bid.vastXml = adSettings.content
        bid.renderer = createRenderer(bid, dataToCreatePlace)
      }
      return bid
    })
  }
}

registerBidder(spec);