diff --git a/libraries/nexx360Utils/index.js b/libraries/nexx360Utils/index.js deleted file mode 100644 index b7423148204..00000000000 --- a/libraries/nexx360Utils/index.js +++ /dev/null @@ -1,155 +0,0 @@ -import { deepAccess, deepSetValue, logInfo } from '../../src/utils.js'; -import {Renderer} from '../../src/Renderer.js'; -import { getCurrencyFromBidderRequest } from '../ortb2Utils/currency.js'; -import { INSTREAM, OUTSTREAM } from '../../src/video.js'; -import { BANNER, NATIVE } from '../../src/mediaTypes.js'; - -const OUTSTREAM_RENDERER_URL = 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js'; - -/** - * Register the user sync pixels which should be dropped after the auction. - * - /** - * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest - * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid - * @typedef {import('../src/adapters/bidderFactory.js').ServerResponse} ServerResponse - * @typedef {import('../src/adapters/bidderFactory.js').SyncOptions} SyncOptions - * @typedef {import('../src/adapters/bidderFactory.js').UserSync} UserSync - * @typedef {import('../src/adapters/bidderFactory.js').validBidRequests} validBidRequests - * - */ - -/** - * Register the user sync pixels which should be dropped after the auction. - * - * @param {SyncOptions} syncOptions Which user syncs are allowed? - * @param {ServerResponse[]} serverResponses List of server's responses. - * @return {UserSync[]} The user syncs which should be dropped. - */ -export function getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent) { - if (typeof serverResponses === 'object' && - serverResponses != null && - serverResponses.length > 0 && - serverResponses[0].hasOwnProperty('body') && - serverResponses[0].body.hasOwnProperty('ext') && - serverResponses[0].body.ext.hasOwnProperty('cookies') && - typeof serverResponses[0].body.ext.cookies === 'object') { - return serverResponses[0].body.ext.cookies.slice(0, 5); - } else { - return []; - } -}; - -function outstreamRender(response) { - response.renderer.push(() => { - window.ANOutstreamVideo.renderAd({ - sizes: [response.width, response.height], - targetId: response.divId, - adResponse: response.vastXml, - rendererOptions: { - showBigPlayButton: false, - showProgressBar: 'bar', - showVolume: false, - allowFullscreen: true, - skippable: false, - content: response.vastXml - } - }); - }); -}; - -export function createRenderer(bid, url) { - const renderer = Renderer.install({ - id: bid.id, - url: url, - loaded: false, - adUnitCode: bid.ext.adUnitCode, - targetId: bid.ext.divId, - }); - renderer.setRender(outstreamRender); - return renderer; -}; - -export function enrichImp(imp, bidRequest) { - deepSetValue(imp, 'tagid', bidRequest.adUnitCode); - deepSetValue(imp, 'ext.adUnitCode', bidRequest.adUnitCode); - const divId = bidRequest.params.divId || bidRequest.adUnitCode; - deepSetValue(imp, 'ext.divId', divId); - if (imp.video) { - const playerSize = deepAccess(bidRequest, 'mediaTypes.video.playerSize'); - const videoContext = deepAccess(bidRequest, 'mediaTypes.video.context'); - deepSetValue(imp, 'video.ext.playerSize', playerSize); - deepSetValue(imp, 'video.ext.context', videoContext); - } - return imp; -} - -export function enrichRequest(request, amxId, bidderRequest, pageViewId, bidderVersion) { - if (amxId) { - deepSetValue(request, 'ext.localStorage.amxId', amxId); - if (!request.user) request.user = {}; - if (!request.user.ext) request.user.ext = {}; - if (!request.user.ext.eids) request.user.ext.eids = []; - request.user.ext.eids.push({ - source: 'amxdt.net', - uids: [{ - id: `${amxId}`, - atype: 1 - }] - }); - } - deepSetValue(request, 'ext.version', '$prebid.version$'); - deepSetValue(request, 'ext.source', 'prebid.js'); - deepSetValue(request, 'ext.pageViewId', pageViewId); - deepSetValue(request, 'ext.bidderVersion', bidderVersion); - deepSetValue(request, 'cur', [getCurrencyFromBidderRequest(bidderRequest) || 'USD']); - if (!request.user) request.user = {}; - return request; -}; - -export function createResponse(bid, respBody) { - const response = { - requestId: bid.impid, - cpm: bid.price, - width: bid.w, - height: bid.h, - creativeId: bid.crid, - currency: respBody.cur, - netRevenue: true, - ttl: 120, - mediaType: [OUTSTREAM, INSTREAM].includes(bid.ext.mediaType) ? 'video' : bid.ext.mediaType, - meta: { - advertiserDomains: bid.adomain, - demandSource: bid.ext.ssp, - }, - }; - if (bid.dealid) response.dealid = bid.dealid; - - if (bid.ext.mediaType === BANNER) response.ad = bid.adm; - if ([INSTREAM, OUTSTREAM].includes(bid.ext.mediaType)) response.vastXml = bid.adm; - - if (bid.ext.mediaType === OUTSTREAM) { - response.renderer = createRenderer(bid, OUTSTREAM_RENDERER_URL); - if (bid.ext.divId) response.divId = bid.ext.divId - }; - - if (bid.ext.mediaType === NATIVE) { - try { - response.native = { ortb: JSON.parse(bid.adm) } - } catch (e) {} - } - return response; -} - -/** - * Get the AMX ID - * @return { string | false } false if localstorageNotEnabled - */ -export function getAmxId(storage, bidderCode) { - if (!storage.localStorageIsEnabled()) { - logInfo(`localstorage not enabled for ${bidderCode}`); - return false; - } - const amxId = storage.getDataFromLocalStorage('__amuidpb'); - return amxId || false; -} diff --git a/libraries/nexx360Utils/index.ts b/libraries/nexx360Utils/index.ts new file mode 100644 index 00000000000..dec3ce47056 --- /dev/null +++ b/libraries/nexx360Utils/index.ts @@ -0,0 +1,246 @@ +import { deepAccess, deepSetValue, generateUUID, logInfo } from '../../src/utils.js'; +import {Renderer} from '../../src/Renderer.js'; +import { getCurrencyFromBidderRequest } from '../ortb2Utils/currency.js'; +import { INSTREAM, OUTSTREAM } from '../../src/video.js'; +import { BANNER, MediaType, NATIVE, VIDEO } from '../../src/mediaTypes.js'; +import { BidResponse, VideoBidResponse } from '../../src/bidfactory.js'; +import { StorageManager } from '../../src/storageManager.js'; +import { BidRequest, ORTBRequest, ORTBResponse } from '../../src/prebid.public.js'; +import { AdapterResponse, ServerResponse } from '../../src/adapters/bidderFactory.js'; + +const OUTSTREAM_RENDERER_URL = 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js'; + +let sessionId:string | null = null; + +const getSessionId = ():string => { + if (!sessionId) { + sessionId = generateUUID(); + } + return sessionId; +} + +let lastPageUrl:string = ''; +let requestCounter:number = 0; + +const getRequestCount = ():number => { + if (lastPageUrl === window.location.pathname) { + return ++requestCounter; + } + lastPageUrl = window.location.pathname; + return 0; +} + +export const getLocalStorageFunctionGenerator = < + T extends Record +>( + storage: StorageManager, + bidderCode: string, + storageKey: string, + jsonKey: keyof T + ): (() => T | null) => { + return () => { + if (!storage.localStorageIsEnabled()) { + logInfo(`localstorage not enabled for ${bidderCode}`); + return null; + } + + const output = storage.getDataFromLocalStorage(storageKey); + if (output === null) { + const storageElement: T = { [jsonKey]: generateUUID() } as T; + storage.setDataInLocalStorage(storageKey, JSON.stringify(storageElement)); + return storageElement; + } + try { + return JSON.parse(output) as T; + } catch (e) { + logInfo(`failed to parse localstorage for ${bidderCode}:`, e); + return null; + } + }; +}; + +export function getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent) { + if (typeof serverResponses === 'object' && + serverResponses != null && + serverResponses.length > 0 && + serverResponses[0].hasOwnProperty('body') && + serverResponses[0].body.hasOwnProperty('ext') && + serverResponses[0].body.ext.hasOwnProperty('cookies') && + typeof serverResponses[0].body.ext.cookies === 'object') { + return serverResponses[0].body.ext.cookies.slice(0, 5); + } else { + return []; + } +}; + +const createOustreamRendererFunction = ( + divId: string, + width: number, + height: number +) => (bidResponse: VideoBidResponse) => { + bidResponse.renderer.push(() => { + (window as any).ANOutstreamVideo.renderAd({ + sizes: [width, height], + targetId: divId, + adResponse: bidResponse.vastXml, + rendererOptions: { + showBigPlayButton: false, + showProgressBar: 'bar', + showVolume: false, + allowFullscreen: true, + skippable: false, + content: bidResponse.vastXml + } + }); + }); +}; + +export type CreateRenderPayload = { + requestId: string, + vastXml: string, + divId: string, + width: number, + height: number +} + +export const createRenderer = ( + { requestId, vastXml, divId, width, height }: CreateRenderPayload +): Renderer | undefined => { + if (!vastXml) { + logInfo('No VAST in bidResponse'); + return; + } + const installPayload = { + id: requestId, + url: OUTSTREAM_RENDERER_URL, + loaded: false, + adUnitCode: divId, + targetId: divId, + }; + const renderer = Renderer.install(installPayload); + renderer.setRender(createOustreamRendererFunction(divId, width, height)); + return renderer; +}; + +export const enrichImp = (imp, bidRequest:BidRequest) => { + deepSetValue(imp, 'tagid', bidRequest.adUnitCode); + deepSetValue(imp, 'ext.adUnitCode', bidRequest.adUnitCode); + const divId = bidRequest.params.divId || bidRequest.adUnitCode; + deepSetValue(imp, 'ext.divId', divId); + if (imp.video) { + const playerSize = deepAccess(bidRequest, 'mediaTypes.video.playerSize'); + const videoContext = deepAccess(bidRequest, 'mediaTypes.video.context'); + deepSetValue(imp, 'video.ext.playerSize', playerSize); + deepSetValue(imp, 'video.ext.context', videoContext); + } + return imp; +} + +export const enrichRequest = ( + request: ORTBRequest, + amxId: string | null, + pageViewId: string, + bidderVersion: string):ORTBRequest => { + if (amxId) { + deepSetValue(request, 'ext.localStorage.amxId', amxId); + if (!request.user) request.user = {}; + if (!request.user.ext) request.user.ext = {}; + if (!request.user.ext.eids) request.user.ext.eids = []; + (request.user.ext.eids as any).push({ + source: 'amxdt.net', + uids: [{ + id: `${amxId}`, + atype: 1 + }] + }); + } + deepSetValue(request, 'ext.version', '$prebid.version$'); + deepSetValue(request, 'ext.source', 'prebid.js'); + deepSetValue(request, 'ext.pageViewId', pageViewId); + deepSetValue(request, 'ext.bidderVersion', bidderVersion); + deepSetValue(request, 'ext.sessionId', getSessionId()); + deepSetValue(request, 'ext.requestCounter', getRequestCount()); + deepSetValue(request, 'cur', [getCurrencyFromBidderRequest(request) || 'USD']); + if (!request.user) request.user = {}; + return request; +}; + +export function createResponse(bid:any, ortbResponse:any): BidResponse { + let mediaType: MediaType = BANNER; + if ([INSTREAM, OUTSTREAM].includes(bid.ext.mediaType as string)) mediaType = VIDEO; + if (bid.ext.mediaType === NATIVE) mediaType = NATIVE; + const response:any = { + requestId: bid.impid, + cpm: bid.price, + width: bid.w, + height: bid.h, + creativeId: bid.crid, + currency: ortbResponse.cur, + netRevenue: true, + ttl: 120, + mediaType, + meta: { + advertiserDomains: bid.adomain, + demandSource: bid.ext.ssp, + }, + }; + if (bid.dealid) response.dealid = bid.dealid; + + if (bid.ext.mediaType === BANNER) response.ad = bid.adm; + if ([INSTREAM, OUTSTREAM].includes(bid.ext.mediaType as string)) response.vastXml = bid.adm; + if (bid.ext.mediaType === OUTSTREAM && (bid.ext.divId || bid.ext.adUnitCode)) { + const renderer = createRenderer({ + requestId: response.requestId, + vastXml: response.vastXml, + divId: bid.ext.divId || bid.ext.adUnitCode, + width: response.width, + height: response.height + }); + if (renderer) { + response.renderer = renderer; + response.divId = bid.ext.divId; + } else { + logInfo('Could not create renderer for outstream bid'); + } + }; + + if (bid.ext.mediaType === NATIVE) { + try { + response.native = { ortb: JSON.parse(bid.adm) } + } catch (e) {} + } + return response as BidResponse; +} + +export const interpretResponse = (serverResponse: ServerResponse): AdapterResponse => { + if (!serverResponse.body) return []; + const respBody = serverResponse.body as ORTBResponse; + if (!respBody.seatbid || respBody.seatbid.length === 0) return []; + + const responses: BidResponse[] = []; + for (let i = 0; i < respBody.seatbid.length; i++) { + const seatbid = respBody.seatbid[i]; + for (let j = 0; j < seatbid.bid.length; j++) { + const bid = seatbid.bid[j]; + const response:BidResponse = createResponse(bid, respBody); + responses.push(response); + } + } + return responses; +} + +/** + * Get the AMX ID + * @return { string | false } false if localstorageNotEnabled + */ +export const getAmxId = ( + storage: StorageManager, + bidderCode: string +): string | null => { + if (!storage.localStorageIsEnabled()) { + logInfo(`localstorage not enabled for ${bidderCode}`); + return null; + } + const amxId = storage.getDataFromLocalStorage('__amuidpb'); + return amxId || null; +} diff --git a/modules/adgridBidAdapter.js b/modules/adgridBidAdapter.js deleted file mode 100644 index d1cccf21c52..00000000000 --- a/modules/adgridBidAdapter.js +++ /dev/null @@ -1,133 +0,0 @@ -import { deepSetValue, generateUUID, logInfo } from '../src/utils.js'; -import { getStorageManager } from '../src/storageManager.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER, VIDEO } from '../src/mediaTypes.js'; -import { ortbConverter } from '../libraries/ortbConverter/converter.js' -import { createResponse, enrichImp, enrichRequest, getAmxId, getUserSyncs } from '../libraries/nexx360Utils/index.js'; - -/** - * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest - * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid - * @typedef {import('../src/adapters/bidderFactory.js').ServerResponse} ServerResponse - * @typedef {import('../src/adapters/bidderFactory.js').SyncOptions} SyncOptions - * @typedef {import('../src/adapters/bidderFactory.js').UserSync} UserSync - * @typedef {import('../src/adapters/bidderFactory.js').validBidRequests} validBidRequests - */ - -const BIDDER_CODE = 'adgrid'; -const REQUEST_URL = 'https://fast.nexx360.io/adgrid'; -const PAGE_VIEW_ID = generateUUID(); -const BIDDER_VERSION = '2.0'; -const ADGRID_KEY = 'adgrid'; - -const ALIASES = []; - -// Define the storage manager for the Adgrid bidder -export const STORAGE = getStorageManager({ - bidderCode: BIDDER_CODE, -}); - -/** - * Get the agdridId from local storage - * @return {object | false } false if localstorageNotEnabled - */ -export function getLocalStorage() { - if (!STORAGE.localStorageIsEnabled()) { - logInfo(`localstorage not enabled for Adgrid`); - return false; - } - const output = STORAGE.getDataFromLocalStorage(ADGRID_KEY); - if (output === null) { - const adgridStorage = { adgridId: generateUUID() }; - STORAGE.setDataInLocalStorage(ADGRID_KEY, JSON.stringify(adgridStorage)); - return adgridStorage; - } - try { - return JSON.parse(output); - } catch (e) { - return false; - } -} - -const converter = ortbConverter({ - context: { - netRevenue: true, // or false if your adapter should set bidResponse.netRevenue = false - ttl: 90, // default bidResponse.ttl (when not specified in ORTB response.seatbid[].bid[].exp) - }, - imp(buildImp, bidRequest, context) { - let imp = buildImp(bidRequest, context); - imp = enrichImp(imp, bidRequest); - if (bidRequest.params.domainId) deepSetValue(imp, 'ext.adgrid.domainId', bidRequest.params.domainId); - if (bidRequest.params.placement) deepSetValue(imp, 'ext.adgrid.placement', bidRequest.params.placement); - return imp; - }, - request(buildRequest, imps, bidderRequest, context) { - let request = buildRequest(imps, bidderRequest, context); - const amxId = getAmxId(STORAGE, BIDDER_CODE); - request = enrichRequest(request, amxId, bidderRequest, PAGE_VIEW_ID, BIDDER_VERSION); - return request; - }, -}); - -/** - * Determines whether or not the given bid request is valid. - * - * @param {BidRequest} bid The bid params to validate. - * @return boolean True if this is a valid bid, and false otherwise. - */ -function isBidRequestValid(bid) { - if (!bid || !bid.params) return false; - if (typeof bid.params.domainId !== 'number') return false; - if (typeof bid.params.placement !== 'string') return false; - return true; -} - -/** - * Make a server request from the list of BidRequests. - * - * @return ServerRequest Info describing the request to the server. - */ -function buildRequests(bidRequests, bidderRequest) { - const data = converter.toORTB({ bidRequests, bidderRequest }) - return { - method: 'POST', - url: REQUEST_URL, - data, - } -} - -/** - * Unpack the response from the server into a list of bids. - * - * @param {ServerResponse} serverResponse A successful response from the server. - * @return {Bid[]} An array of bids which were nested inside the server. - */ -function interpretResponse(serverResponse) { - const respBody = serverResponse.body; - if (!respBody || !Array.isArray(respBody.seatbid)) { - return []; - } - - const responses = []; - for (let i = 0; i < respBody.seatbid.length; i++) { - const seatbid = respBody.seatbid[i]; - for (let j = 0; j < seatbid.bid.length; j++) { - const bid = seatbid.bid[j]; - const response = createResponse(bid, respBody); - responses.push(response); - } - } - return responses; -} - -export const spec = { - code: BIDDER_CODE, - aliases: ALIASES, - supportedMediaTypes: [BANNER, VIDEO], - isBidRequestValid, - buildRequests, - interpretResponse, - getUserSyncs, -}; - -registerBidder(spec); diff --git a/modules/adgridBidAdapter.ts b/modules/adgridBidAdapter.ts new file mode 100644 index 00000000000..e5ba2c8b672 --- /dev/null +++ b/modules/adgridBidAdapter.ts @@ -0,0 +1,102 @@ +import { deepSetValue, generateUUID } from '../src/utils.js'; +import { getStorageManager, StorageManager } from '../src/storageManager.js'; +import { AdapterRequest, BidderSpec, registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import { ortbConverter } from '../libraries/ortbConverter/converter.js' +import { BidRequest, ClientBidderRequest } from '../src/adapterManager.js'; +import { interpretResponse, enrichImp, enrichRequest, getAmxId, getLocalStorageFunctionGenerator, getUserSyncs } from '../libraries/nexx360Utils/index.js'; +import { ORTBRequest } from '../src/prebid.public.js'; + +const BIDDER_CODE = 'adgrid'; +const REQUEST_URL = 'https://fast.nexx360.io/adgrid'; +const PAGE_VIEW_ID = generateUUID(); +const BIDDER_VERSION = '2.0'; +const ADGRID_KEY = 'adgrid'; + +type RequireAtLeastOne = + Omit & { + [K in Keys]-?: Required> & + Partial>> + }[Keys]; + +type AdgridBidParams = RequireAtLeastOne<{ + domainId?: string; + placement?: string; + allBids?: boolean; + customId?: string; +}, "domainId" | "placement">; + +declare module '../src/adUnits' { + interface BidderParams { + [BIDDER_CODE]: AdgridBidParams; + } +} + +const ALIASES = []; + +// Define the storage manager for the Adgrid bidder +export const STORAGE: StorageManager = getStorageManager({ + bidderCode: BIDDER_CODE, +}); + +export const getAdgridLocalStorage = getLocalStorageFunctionGenerator<{ adgridId: string }>( + STORAGE, + BIDDER_CODE, + ADGRID_KEY, + 'adgridId' +); + +const converter = ortbConverter({ + context: { + netRevenue: true, // or false if your adapter should set bidResponse.netRevenue = false + ttl: 90, // default bidResponse.ttl (when not specified in ORTB response.seatbid[].bid[].exp) + }, + imp(buildImp, bidRequest, context) { + let imp = buildImp(bidRequest, context); + imp = enrichImp(imp, bidRequest); + const params = bidRequest.params as AdgridBidParams; + if (params.domainId) deepSetValue(imp, 'ext.adgrid.domainId', params.domainId); + if (params.placement) deepSetValue(imp, 'ext.adgrid.placement', params.placement); + if (params.allBids) deepSetValue(imp, 'ext.adgrid.allBids', params.allBids); + if (params.customId) deepSetValue(imp, 'ext.adgrid.customId', params.customId); + return imp; + }, + request(buildRequest, imps, bidderRequest, context) { + let request = buildRequest(imps, bidderRequest, context); + const amxId = getAmxId(STORAGE, BIDDER_CODE); + request = enrichRequest(request, amxId, PAGE_VIEW_ID, BIDDER_VERSION); + return request; + }, +}); + +const isBidRequestValid = (bid:BidRequest): boolean => { + if (!bid || !bid.params) return false; + if (typeof bid.params.domainId !== 'number') return false; + if (typeof bid.params.placement !== 'string') return false; + return true; +} + +const buildRequests = ( + bidRequests: BidRequest[], + bidderRequest: ClientBidderRequest, +): AdapterRequest => { + const data:ORTBRequest = converter.toORTB({bidRequests, bidderRequest}) + const adapterRequest:AdapterRequest = { + method: 'POST', + url: REQUEST_URL, + data, + } + return adapterRequest; +} + +export const spec:BidderSpec = { + code: BIDDER_CODE, + aliases: ALIASES, + supportedMediaTypes: [BANNER, VIDEO], + isBidRequestValid, + buildRequests, + interpretResponse, + getUserSyncs, +}; + +registerBidder(spec); diff --git a/modules/nexx360BidAdapter.js b/modules/nexx360BidAdapter.ts similarity index 55% rename from modules/nexx360BidAdapter.js rename to modules/nexx360BidAdapter.ts index c89238b9242..393bea2dbab 100644 --- a/modules/nexx360BidAdapter.js +++ b/modules/nexx360BidAdapter.ts @@ -1,29 +1,48 @@ -import { deepSetValue, generateUUID, logError, logInfo } from '../src/utils.js'; +import { deepSetValue, generateUUID, logError } from '../src/utils.js'; import {getStorageManager} from '../src/storageManager.js'; -import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {AdapterRequest, BidderSpec, registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; -import {getGlobal} from '../src/prebidGlobal.js'; import {ortbConverter} from '../libraries/ortbConverter/converter.js' -import { createResponse, enrichImp, enrichRequest, getAmxId, getUserSyncs } from '../libraries/nexx360Utils/index.js'; +import { interpretResponse, enrichImp, enrichRequest, getAmxId, getLocalStorageFunctionGenerator, getUserSyncs } from '../libraries/nexx360Utils/index.js'; import { getBoundingClientRect } from '../libraries/boundingClientRect/boundingClientRect.js'; - -/** - * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest - * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid - * @typedef {import('../src/adapters/bidderFactory.js').ServerResponse} ServerResponse - * @typedef {import('../src/adapters/bidderFactory.js').SyncOptions} SyncOptions - * @typedef {import('../src/adapters/bidderFactory.js').UserSync} UserSync - * @typedef {import('../src/adapters/bidderFactory.js').validBidRequests} validBidRequests - */ +import { BidRequest, ClientBidderRequest } from '../src/adapterManager.js'; +import { ORTBImp, ORTBRequest } from '../src/prebid.public.js'; +import { config } from '../src/config.js'; const BIDDER_CODE = 'nexx360'; const REQUEST_URL = 'https://fast.nexx360.io/booster'; const PAGE_VIEW_ID = generateUUID(); -const BIDDER_VERSION = '6.3'; +const BIDDER_VERSION = '7.0'; const GVLID = 965; const NEXXID_KEY = 'nexx360_storage'; +const DEFAULT_GZIP_ENABLED = false; + +type RequireAtLeastOne = + Omit & { + [K in Keys]-?: Required> & + Partial>> + }[Keys]; + +type Nexx360BidParams = RequireAtLeastOne<{ + tagId?: string; + placement?: string; + videoTagId?: string; + nativeTagId?: string; + adUnitPath?: string; + adUnitName?: string; + divId?: string; + allBids?: boolean; + customId?: string; +}, "tagId" | "placement">; + +declare module '../src/adUnits' { + interface BidderParams { + [BIDDER_CODE]: Nexx360BidParams; + } +} + const ALIASES = [ { code: 'revenuemaker' }, { code: 'first-id', gvlid: 1178 }, @@ -41,34 +60,26 @@ const ALIASES = [ { code: 'glomexbidder', gvlid: 967 }, { code: 'revnew', gvlid: 1468 }, { code: 'pubxai', gvlid: 1485 }, + { code: 'ybidder', gvlid: 1253 }, ]; export const STORAGE = getStorageManager({ bidderCode: BIDDER_CODE, }); -/** - * Get the NexxId - * @param - * @return {object | false } false if localstorageNotEnabled - */ - -export function getNexx360LocalStorage() { - if (!STORAGE.localStorageIsEnabled()) { - logInfo(`localstorage not enabled for Nexx360`); - return false; - } - const output = STORAGE.getDataFromLocalStorage(NEXXID_KEY); - if (output === null) { - const nexx360Storage = { nexx360Id: generateUUID() }; - STORAGE.setDataInLocalStorage(NEXXID_KEY, JSON.stringify(nexx360Storage)); - return nexx360Storage; - } - try { - return JSON.parse(output) - } catch (e) { - return false; +export const getNexx360LocalStorage = getLocalStorageFunctionGenerator<{ nexx360Id: string }>( + STORAGE, + BIDDER_CODE, + NEXXID_KEY, + 'nexx360Id' +); + +export const getGzipSetting = (): boolean => { + const getBidderConfig = config.getBidderConfig(); + if (getBidderConfig.nexx360?.gzipEnabled === 'true') { + return getBidderConfig.nexx360?.gzipEnabled === 'true'; } + return DEFAULT_GZIP_ENABLED; } const converter = ortbConverter({ @@ -77,10 +88,10 @@ const converter = ortbConverter({ ttl: 90, // default bidResponse.ttl (when not specified in ORTB response.seatbid[].bid[].exp) }, imp(buildImp, bidRequest, context) { - let imp = buildImp(bidRequest, context); + let imp:ORTBImp = buildImp(bidRequest, context); imp = enrichImp(imp, bidRequest); const divId = bidRequest.params.divId || bidRequest.adUnitCode; - const slotEl = document.getElementById(divId); + const slotEl:HTMLElement | null = typeof divId === 'string' ? document.getElementById(divId) : null; if (slotEl) { const { width, height } = getBoundingClientRect(slotEl); deepSetValue(imp, 'ext.dimensions.slotW', width); @@ -94,23 +105,19 @@ const converter = ortbConverter({ if (bidRequest.params.adUnitPath) deepSetValue(imp, 'ext.adUnitPath', bidRequest.params.adUnitPath); if (bidRequest.params.adUnitName) deepSetValue(imp, 'ext.adUnitName', bidRequest.params.adUnitName); if (bidRequest.params.allBids) deepSetValue(imp, 'ext.nexx360.allBids', bidRequest.params.allBids); + if (bidRequest.params.nativeTagId) deepSetValue(imp, 'ext.nexx360.nativeTagId', bidRequest.params.nativeTagId); + if (bidRequest.params.customId) deepSetValue(imp, 'ext.nexx360.customId', bidRequest.params.customId); return imp; }, request(buildRequest, imps, bidderRequest, context) { - let request = buildRequest(imps, bidderRequest, context); + let request:ORTBRequest = buildRequest(imps, bidderRequest, context); const amxId = getAmxId(STORAGE, BIDDER_CODE); - request = enrichRequest(request, amxId, bidderRequest, PAGE_VIEW_ID, BIDDER_VERSION); + request = enrichRequest(request, amxId, PAGE_VIEW_ID, BIDDER_VERSION); return request; }, }); -/** - * Determines whether or not the given bid request is valid. - * - * @param {BidRequest} bid The bid params to validate. - * @return boolean True if this is a valid bid, and false otherwise. - */ -function isBidRequestValid(bid) { +const isBidRequestValid = (bid:BidRequest): boolean => { if (bid.params.adUnitName && (typeof bid.params.adUnitName !== 'string' || bid.params.adUnitName === '')) { logError('bid.params.adUnitName needs to be a string'); return false; @@ -134,51 +141,23 @@ function isBidRequestValid(bid) { return true; }; -/** - * Make a server request from the list of BidRequests. - * - * @return ServerRequest Info describing the request to the server. - */ - -function buildRequests(bidRequests, bidderRequest) { - const data = converter.toORTB({bidRequests, bidderRequest}) - return { +const buildRequests = ( + bidRequests: BidRequest[], + bidderRequest: ClientBidderRequest, +): AdapterRequest => { + const data:ORTBRequest = converter.toORTB({bidRequests, bidderRequest}) + const adapterRequest:AdapterRequest = { method: 'POST', url: REQUEST_URL, data, + options: { + endpointCompression: getGzipSetting() + }, } + return adapterRequest; } -/** - * Unpack the response from the server into a list of bids. - * - * @param {ServerResponse} serverResponse A successful response from the server. - * @return {Bid[]} An array of bids which were nested inside the server. - */ - -function interpretResponse(serverResponse) { - const respBody = serverResponse.body; - if (!respBody || !Array.isArray(respBody.seatbid)) { - return []; - } - - const { bidderSettings } = getGlobal(); - const allowAlternateBidderCodes = bidderSettings && bidderSettings.standard ? bidderSettings.standard.allowAlternateBidderCodes : false; - - const responses = []; - for (let i = 0; i < respBody.seatbid.length; i++) { - const seatbid = respBody.seatbid[i]; - for (let j = 0; j < seatbid.bid.length; j++) { - const bid = seatbid.bid[j]; - const response = createResponse(bid, respBody); - if (allowAlternateBidderCodes) response.bidderCode = `n360_${bid.ext.ssp}`; - responses.push(response); - } - } - return responses; -} - -export const spec = { +export const spec:BidderSpec = { code: BIDDER_CODE, gvlid: GVLID, aliases: ALIASES, diff --git a/test/spec/modules/adgridBidAdapter_spec.js b/test/spec/modules/adgridBidAdapter_spec.js index 5b6d658e1ca..3dd5d1985f5 100644 --- a/test/spec/modules/adgridBidAdapter_spec.js +++ b/test/spec/modules/adgridBidAdapter_spec.js @@ -1,9 +1,8 @@ import { expect } from 'chai'; import { - spec, STORAGE, getLocalStorage, + spec, STORAGE, getAdgridLocalStorage, } from 'modules/adgridBidAdapter.js'; import sinon from 'sinon'; -import { getAmxId } from '../../../libraries/nexx360Utils/index.js'; const sandbox = sinon.createSandbox(); describe('adgrid bid adapter tests', () => { @@ -74,8 +73,8 @@ describe('adgrid bid adapter tests', () => { sandbox.stub(STORAGE, 'localStorageIsEnabled').callsFake(() => false); }); it('We test if we get the adgridId', () => { - const output = getLocalStorage(); - expect(output).to.be.eql(false); + const output = getAdgridLocalStorage(); + expect(output).to.be.eql(null); }); after(() => { sandbox.restore() @@ -89,7 +88,7 @@ describe('adgrid bid adapter tests', () => { sandbox.stub(STORAGE, 'getDataFromLocalStorage').callsFake((key) => null); }); it('We test if we get the adgridId', () => { - const output = getLocalStorage(); + const output = getAdgridLocalStorage(); expect(typeof output.adgridId).to.be.eql('string'); }); after(() => { @@ -104,8 +103,8 @@ describe('adgrid bid adapter tests', () => { sandbox.stub(STORAGE, 'getDataFromLocalStorage').callsFake((key) => '{"adgridId":"5ad89a6e-7801-48e7-97bb-fe6f251f6cb4",}'); }); it('We test if we get the adgridId', () => { - const output = getLocalStorage(); - expect(output).to.be.eql(false); + const output = getAdgridLocalStorage(); + expect(output).to.be.eql(null); }); after(() => { sandbox.restore() @@ -119,7 +118,7 @@ describe('adgrid bid adapter tests', () => { sandbox.stub(STORAGE, 'getDataFromLocalStorage').callsFake((key) => '{"adgridId":"5ad89a6e-7801-48e7-97bb-fe6f251f6cb4"}'); }); it('We test if we get the adgridId', () => { - const output = getLocalStorage(); + const output = getAdgridLocalStorage(); expect(output.adgridId).to.be.eql('5ad89a6e-7801-48e7-97bb-fe6f251f6cb4'); }); after(() => { @@ -299,6 +298,8 @@ describe('adgrid bid adapter tests', () => { source: 'prebid.js', pageViewId: requestContent.ext.pageViewId, bidderVersion: '2.0', + requestCounter: 0, + sessionId: requestContent.ext.sessionId, }, cur: [ 'USD', @@ -514,6 +515,7 @@ describe('adgrid bid adapter tests', () => { mediaType: 'outstream', ssp: 'test', adUnitCode: 'div-1', + divId: 'div-1', }, }, ], @@ -536,6 +538,7 @@ describe('adgrid bid adapter tests', () => { currency: 'USD', netRevenue: true, ttl: 120, + divId: 'div-1', mediaType: 'video', meta: { advertiserDomains: ['adgrid.com'], demandSource: 'test' }, vastXml: 'vast', diff --git a/test/spec/modules/nexx360BidAdapter_spec.js b/test/spec/modules/nexx360BidAdapter_spec.js index 7756e96bd99..a5e8ab03d1a 100644 --- a/test/spec/modules/nexx360BidAdapter_spec.js +++ b/test/spec/modules/nexx360BidAdapter_spec.js @@ -1,6 +1,6 @@ import { expect } from 'chai'; import { - spec, STORAGE, getNexx360LocalStorage, + spec, STORAGE, getNexx360LocalStorage, getGzipSetting, } from 'modules/nexx360BidAdapter.js'; import sinon from 'sinon'; import { getAmxId } from '../../../libraries/nexx360Utils/index.js'; @@ -33,6 +33,11 @@ describe('Nexx360 bid adapter tests', () => { }, }; + it('We test getGzipSettings', () => { + const output = getGzipSetting(); + expect(output).to.be.a('boolean'); + }); + describe('isBidRequestValid()', () => { let bannerBid; beforeEach(() => { @@ -95,12 +100,12 @@ describe('Nexx360 bid adapter tests', () => { }); it('We test if we get the nexx360Id', () => { const output = getNexx360LocalStorage(); - expect(output).to.be.eql(false); + expect(output).to.be.eql(null); }); after(() => { sandbox.restore() }); - }) + }); describe('getNexx360LocalStorage enabled but nothing', () => { before(() => { @@ -115,7 +120,7 @@ describe('Nexx360 bid adapter tests', () => { after(() => { sandbox.restore() }); - }) + }); describe('getNexx360LocalStorage enabled but wrong payload', () => { before(() => { @@ -125,7 +130,7 @@ describe('Nexx360 bid adapter tests', () => { }); it('We test if we get the nexx360Id', () => { const output = getNexx360LocalStorage(); - expect(output).to.be.eql(false); + expect(output).to.be.eql(null); }); after(() => { sandbox.restore() @@ -155,7 +160,7 @@ describe('Nexx360 bid adapter tests', () => { }); it('We test if we get the amxId', () => { const output = getAmxId(STORAGE, 'nexx360'); - expect(output).to.be.eql(false); + expect(output).to.be.eql(null); }); after(() => { sandbox.restore() @@ -335,8 +340,10 @@ describe('Nexx360 bid adapter tests', () => { version: requestContent.ext.version, source: 'prebid.js', pageViewId: requestContent.ext.pageViewId, - bidderVersion: '6.3', - localStorage: { amxId: 'abcdef'} + bidderVersion: '7.0', + localStorage: { amxId: 'abcdef'}, + sessionId: requestContent.ext.sessionId, + requestCounter: 0, }, cur: [ 'USD', @@ -564,6 +571,7 @@ describe('Nexx360 bid adapter tests', () => { mediaType: 'outstream', ssp: 'appnexus', adUnitCode: 'div-1', + divId: 'div-1', }, }, ], @@ -585,6 +593,7 @@ describe('Nexx360 bid adapter tests', () => { creativeId: '97517771', currency: 'USD', netRevenue: true, + divId: 'div-1', ttl: 120, mediaType: 'video', meta: { advertiserDomains: ['appnexus.com'], demandSource: 'appnexus' },