From a6badb6ba417e562b4c2fee4204370a82aed9afe Mon Sep 17 00:00:00 2001 From: James Rosewell Date: Wed, 10 Jul 2024 19:30:49 +0100 Subject: [PATCH] Appnexus bid adapter add ortb2 device (#11788) * AppNexus Bid Adapter: Add full ORTB2 device data to request payload * AppNexus Bid Adapter: Add test to verify presence of ORTB2 device data in request * AppNexus Bid Adapter: Convert ORTB2 device data to AppNexus format --------- Co-authored-by: Bohdan V <25197509+BohdanVV@users.noreply.github.com> --- modules/appnexusBidAdapter.js | 56 +++++++++++++++++++- test/spec/modules/appnexusBidAdapter_spec.js | 36 +++++++++++++ 2 files changed, 91 insertions(+), 1 deletion(-) diff --git a/modules/appnexusBidAdapter.js b/modules/appnexusBidAdapter.js index 7589681f2fd..62b62d20216 100644 --- a/modules/appnexusBidAdapter.js +++ b/modules/appnexusBidAdapter.js @@ -15,7 +15,8 @@ import { logError, logInfo, logMessage, - logWarn + logWarn, + mergeDeep } from '../src/utils.js'; import {Renderer} from '../src/Renderer.js'; import {config} from '../src/config.js'; @@ -103,6 +104,28 @@ const VIEWABILITY_URL_START = /\/\/cdn\.adnxs\.com\/v|\/\/cdn\.adnxs\-simple\.co const VIEWABILITY_FILE_NAME = 'trk.js'; const GVLID = 32; const storage = getStorageManager({bidderCode: BIDDER_CODE}); +// ORTB2 device types according to the OpenRTB specification +const ORTB2_DEVICE_TYPE = { + MOBILE_TABLET: 1, + PERSONAL_COMPUTER: 2, + CONNECTED_TV: 3, + PHONE: 4, + TABLET: 5, + CONNECTED_DEVICE: 6, + SET_TOP_BOX: 7, + OOH_DEVICE: 8 +}; +// Map of ORTB2 device types to AppNexus device types +const ORTB2_DEVICE_TYPE_MAP = new Map([ + [ORTB2_DEVICE_TYPE.MOBILE_TABLET, 'Mobile/Tablet - General'], + [ORTB2_DEVICE_TYPE.PERSONAL_COMPUTER, 'Personal Computer'], + [ORTB2_DEVICE_TYPE.CONNECTED_TV, 'Connected TV'], + [ORTB2_DEVICE_TYPE.PHONE, 'Phone'], + [ORTB2_DEVICE_TYPE.TABLET, 'Tablet'], + [ORTB2_DEVICE_TYPE.CONNECTED_DEVICE, 'Connected Device'], + [ORTB2_DEVICE_TYPE.SET_TOP_BOX, 'Set Top Box'], + [ORTB2_DEVICE_TYPE.OOH_DEVICE, 'OOH Device'], +]); export const spec = { code: BIDDER_CODE, @@ -247,6 +270,12 @@ export const spec = { payload.app = appIdObj; } + // if present, convert and merge device object from ortb2 into `payload.device` + if (bidderRequest?.ortb2?.device) { + payload.device = payload.device || {}; + mergeDeep(payload.device, convertORTB2DeviceDataToAppNexusDeviceObject(bidderRequest.ortb2.device)); + } + // grab the ortb2 keyword data (if it exists) and convert from the comma list string format to object format let ortb2 = deepClone(bidderRequest && bidderRequest.ortb2); @@ -1196,4 +1225,29 @@ function getBidFloor(bid) { return null; } +// Convert device data to a format that AppNexus expects +function convertORTB2DeviceDataToAppNexusDeviceObject(ortb2DeviceData) { + const _device = { + useragent: ortb2DeviceData.ua, + devicetype: ORTB2_DEVICE_TYPE_MAP.get(ortb2DeviceData.devicetype), + make: ortb2DeviceData.make, + model: ortb2DeviceData.model, + os: ortb2DeviceData.os, + os_version: ortb2DeviceData.osv, + w: ortb2DeviceData.w, + h: ortb2DeviceData.h, + ppi: ortb2DeviceData.ppi, + pxratio: ortb2DeviceData.pxratio, + }; + + // filter out any empty values and return the object + return Object.keys(_device) + .reduce((r, key) => { + if (_device[key]) { + r[key] = _device[key]; + } + return r; + }, {}); +} + registerBidder(spec); diff --git a/test/spec/modules/appnexusBidAdapter_spec.js b/test/spec/modules/appnexusBidAdapter_spec.js index c8be99f7161..511a9a42a26 100644 --- a/test/spec/modules/appnexusBidAdapter_spec.js +++ b/test/spec/modules/appnexusBidAdapter_spec.js @@ -431,6 +431,42 @@ describe('AppNexusAdapter', function () { expect(payload.tags[0].video_frameworks).to.deep.equal([1, 4]) }); + it('should convert and include ORTB2 device data when available', function () { + const bidRequest = deepClone(bidRequests[0]); + const bidderRequest = { + ortb2: { + device: { + w: 980, + h: 1720, + dnt: 0, + ua: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/125.0.6422.80 Mobile/15E148 Safari/604.1', + language: 'en', + devicetype: 1, + make: 'Apple', + model: 'iPhone 12 Pro Max', + os: 'iOS', + osv: '17.4', + }, + }, + }; + + const expectedDeviceResult = { + useragent: bidderRequest.ortb2.device.ua, + devicetype: 'Mobile/Tablet - General', + make: bidderRequest.ortb2.device.make, + model: bidderRequest.ortb2.device.model, + os: bidderRequest.ortb2.device.os, + os_version: bidderRequest.ortb2.device.osv, + w: bidderRequest.ortb2.device.w, + h: bidderRequest.ortb2.device.h, + }; + + const request = spec.buildRequests([bidRequest], bidderRequest); + const payload = JSON.parse(request.data); + + expect(payload.device).to.deep.equal(expectedDeviceResult); + }); + it('should add video property when adUnit includes a renderer', function () { const videoData = { mediaTypes: {