import { Campaign, ChoiceRequest, RequestInventorySource, RequestType } from "../models"; import { bid, openrtb } from "../beeswax-protos/beeswax"; import _ from "lodash"; import nilMergeDeep from "../utils/nilmerge"; import iso3166Conversion from "../utils/iso-3166-1-conversion"; import { Device } from "../models/device.model"; import { User } from "../models/user.model"; import constants from "../utils/constants"; import { toUuid } from "../utils/helper"; import { Context } from "../models/context.model"; import { App } from "../models/app.model"; export function toChoiceRequest(openRtbRequest: bid.BidAgentRequest): ChoiceRequest { // defaults are set here so that the extraction methods // would not have to implement null / undefined checks on each property setDefaults(openRtbRequest); const bidRequest = openRtbRequest.bid_request; if (!bidRequest) { throw new Error("Bid request not found"); } return { id: toUuid(bidRequest.ext?.auctionid_str), type: RequestType.AD, inventory_source: RequestInventorySource.EXTERNAL, environment_type: "in-app", context: getContextFromRequest(bidRequest), user: getUser(), device: getDeviceFromRequest(bidRequest), app: getAppFromRequest(bidRequest), campaigns: getCampaignsFromRequest(openRtbRequest), bidfloor: openRtbRequest?.bid_request?.imp?.[0]?.bidfloor, decodedRequest: openRtbRequest, // init temp fields isWeb: false, stepsStatuses: [], metadata: {}, metadataTmp: {}, campaignData: {}, allMetricsLabels: {}, globalRequestLabels: {}, groupedCampaignIds: { managed: new Set(), programmatic: new Set(), marketplace: new Set(), fallback: new Set(), }, }; } export function toOpenRtbResponse( openRtbRequest: bid.BidAgentRequest, choiceRequest: ChoiceRequest | null ): bid.IBidAgentResponse { const bids = []; const adcandidates: bid.IAdcandidate[] = openRtbRequest.adcandidates; const campaign: Campaign | null = choiceRequest?.campaigns.length ? choiceRequest.campaigns[0] : null; for (const adcandidate of adcandidates) { let bidPrice: number; if (campaign && adcandidate.line_item_alternative_id === campaign.id) { bidPrice = campaign.metadata?.bid_price || 0; } else { bidPrice = 0; } bids.push({ line_item_id: parseInt(adcandidate.line_item_id?.toString() ?? ""), creative: { id: parseInt(adcandidate.creative_ids?.[0].toString() ?? ""), }, bid_price_micros: bidPrice * 1000, }); } return { bids: bids, }; } function setDefaults(openRtbRequest: bid.BidAgentRequest) { // merge with is used instead of defaultsDeep such that null values attributed to // missing properties by protobufjs is overridden, // any refactoring to this mathod should take into account null properties unless // protobufs or / protobufjs is no longer used for a library that does not set null to properties. _.mergeWith( openRtbRequest, { bid_request: { device: { geo: { city: "", country: "", }, ifa: "", ua: "", ip: "0.0.0.0", ipv6: "", connectiontype: 0, os: "", }, app: { name: "", bundle: "-1", cat: "", content: { language: "", }, }, ext: { inventory_source: "", }, }, adcandidates: [], }, nilMergeDeep ); } function getContextFromRequest(bidRequest: openrtb.IBidRequest): Context { const geo = bidRequest.device?.geo || {}; const currentDate = new Date(); return { day_of_week: currentDate.getUTCDay(), hour: currentDate.getUTCHours(), city: geo.city, ssp: getSSPString(bidRequest.ext?.inventory_source), country: iso3166Conversion[geo.country ?? "UNKNOWN"], }; } function getSSPString(inventorySource: openrtb.Enums.Inventory.Source | null | undefined): string { if (!inventorySource) { return "unknown"; } return openrtb.Enums.Inventory.Source[inventorySource].toLowerCase() ?? "unknown"; } function getDeviceFromRequest(bidRequest: openrtb.IBidRequest): Device { const device = bidRequest.device ?? {}; return { name: device.model, language: device.language, os_version: device.osv, os: device.os, connectivity: getConnectionTypeInDeliveryFormat(device.connectiontype), isp: device.carrier, ip: device.ip, ipv6: device.ipv6, ua: device.ua, }; } function getConnectionTypeInDeliveryFormat( connectionType: openrtb.Enums.BidRequest.Device.ConnectionType | null | undefined ): string { const rtbNormsForConnectionTypes = ["unknown", "ethernet", "wifi", "cellular", "2g", "3g", "4g"]; return rtbNormsForConnectionTypes[connectionType ?? 0] ?? ""; } function getAppFromRequest(bidRequest: openrtb.IBidRequest): App { const app = bidRequest.app ?? {}; return { bundle_id: app.bundle, app_id: app.bundle, name: app.name, }; } function getUser(): User { return { data_optin: true, odid: constants.DEFAULT_ODID, }; } function getCampaignsFromRequest(openRtbRequest: bid.BidAgentRequest): Campaign[] { const listOfCampaignsFromRequest = openRtbRequest.adcandidates; return listOfCampaignsFromRequest.map((campaign) => { const id = parseInt(campaign.line_item_alternative_id ?? "").toString(); return { id: id, category_iab: [], metadata: {}, metadataTmp: {}, stepsPassedStatus: {}, }; }); }