-
Notifications
You must be signed in to change notification settings - Fork 2.4k
AdSmartx Bid Adapter : New Bidder Adapter #14559
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
22 commits
Select commit
Hold shift + click to select a range
cbd3b73
RM-1476 : Prebid adapter for adsmartx
pritishmd-talentica 2088c35
Merge pull request #2 from prebid/master
sushant-dey-talentica f184a06
RM-1476 : Improved unit test coverage
pritishmd-talentica 9723093
RM-1476 : Updated bidder documentation description
pritishmd-talentica 9c9b88f
RM-1476 : Review comments handled
pritishmd-talentica 595a234
RM-1476 : Minor changes
pritishmd-talentica d894733
RM-1476 : Dedupe code for adapter risemediatech
pritishmd-talentica a80a4eb
Added only ssp_id for user sync flow
pritishmd-talentica f228d14
Fixed bugs
pritishmd-talentica 0b044a5
RM-1476 : handled all copilot review comments
pritishmd-talentica a81a61a
RM-1476 : Handled copilot review comments
pritishmd-talentica c4fe9ea
Merge pull request #4 from smart-exchange-ai-digital/RM-1476-prebid-j…
pritishmd-talentica 81a2480
RM-1476 : Handled prebid js PR review comments
pritishmd-talentica 550551c
Merge remote-tracking branch 'upstream/master' into RM-1476-prebid-js…
pritishmd-talentica 6a92720
Merge branch 'master' into RM-1476-prebid-js-adapter-smart-exchange
pritishmd-talentica 0ed1c9a
Merge pull request #5 from smart-exchange-ai-digital/RM-1476-prebid-j…
pritishmd-talentica dbdb1f2
Merge pull request #6 from prebid/master
pritishmd-talentica 089ee2a
RM-1476 : Handled review comment to log a warning that risemediatech …
pritishmd-talentica 34fc43a
RM-1476: Added a function to disable the adapter.
pritishmd-talentica c5cb672
Merge pull request #7 from smart-exchange-ai-digital/RM-1476-prebid-j…
pritishmd-talentica 7844adc
RM-1476 : Updated unit tests
pritishmd-talentica f3b8946
Merge pull request #8 from smart-exchange-ai-digital/RM-1476-prebid-j…
pritishmd-talentica File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,263 @@ | ||
| import { BANNER, VIDEO } from '../../src/mediaTypes.js'; | ||
| import { ortbConverter } from '../ortbConverter/converter.js'; | ||
| import { deepAccess, logInfo, logWarn } from '../../src/utils.js'; | ||
|
|
||
| const DEFAULT_CURRENCY = 'USD'; | ||
| const DEFAULT_TTL = 60; | ||
|
|
||
| /** | ||
| * Get publisher user ID with priority: | ||
| * 1. Bid params (sspUserId) | ||
| * 2. ORTB2 first party data (ortb2.user.id) | ||
| * @param {Object} bidParams - Bid parameters from first bid | ||
| * @param {Object} bidderRequest - Bidder request object containing ortb2 | ||
| * @returns {string|null} Publisher user ID if found, null otherwise | ||
| */ | ||
| export function getPublisherUserId(bidParams, bidderRequest) { | ||
| if (bidParams?.sspUserId) { | ||
| logInfo('Using SSP user ID from bid params:', bidParams.sspUserId); | ||
| return bidParams.sspUserId; | ||
| } | ||
| const ortb2UserId = deepAccess(bidderRequest, 'ortb2.user.id'); | ||
| if (ortb2UserId) { | ||
| logInfo('Using SSP user ID from ORTB2 user.id:', ortb2UserId); | ||
| return ortb2UserId; | ||
| } | ||
| logInfo('No SSP user ID found in bid params or ORTB2'); | ||
| return null; | ||
| } | ||
|
|
||
| /** | ||
| * Creates ORTB converter with shared imp/request logic. | ||
| * @param {Object} config - { defaultCurrency, defaultTtl } | ||
| * @returns {Object} ortbConverter instance | ||
| */ | ||
| export function createConverter(config = {}) { | ||
| const currency = config.defaultCurrency ?? DEFAULT_CURRENCY; | ||
| const ttl = config.defaultTtl ?? DEFAULT_TTL; | ||
|
|
||
| return ortbConverter({ | ||
| context: { | ||
| netRevenue: true, | ||
| ttl, | ||
| currency, | ||
| }, | ||
| imp(buildImp, bidRequest, context) { | ||
| logInfo('Building impression object for bidRequest:', bidRequest); | ||
| const imp = buildImp(bidRequest, context); | ||
| const { mediaTypes } = bidRequest; | ||
| if (bidRequest.params?.bidfloor) { | ||
| logInfo('Setting bid floor for impression:', bidRequest.params.bidfloor); | ||
| imp.bidfloor = bidRequest.params.bidfloor; | ||
| } | ||
| if (mediaTypes[BANNER]) { | ||
| logInfo('Adding banner media type to impression:', mediaTypes[BANNER]); | ||
| imp.banner = { ...(imp.banner || {}), format: mediaTypes[BANNER].sizes.map(([w, h]) => ({ w, h })) }; | ||
| } else if (mediaTypes[VIDEO]) { | ||
| logInfo('Adding video media type to impression:', mediaTypes[VIDEO]); | ||
| imp.video = { ...(imp.video || {}), ...mediaTypes[VIDEO] }; | ||
| } | ||
| return imp; | ||
| }, | ||
| request(buildRequest, imps, bidderRequest, context) { | ||
| logInfo('Building server request with impressions:', imps); | ||
| const request = buildRequest(imps, bidderRequest, context); | ||
| request.cur = [currency]; | ||
| request.tmax = bidderRequest.timeout; | ||
| request.test = bidderRequest.test || 0; | ||
|
|
||
| if (Array.isArray(bidderRequest.bids)) { | ||
| const hasTestMode = bidderRequest.bids.some(bid => bid.params?.testMode === 1); | ||
| if (hasTestMode) { | ||
| request.ext = request.ext || {}; | ||
| request.ext.test = 1; | ||
| logInfo('Test mode detected in bid params, setting test flag in request:', request.ext.test); | ||
| } | ||
| const sspIdBid = bidderRequest.bids.find(bid => bid.params?.sspId); | ||
| if (sspIdBid) { | ||
| request.ext = request.ext || {}; | ||
| request.ext.sspId = sspIdBid.params.sspId; | ||
| logInfo('sspId detected in bid params, setting sspId in request:', request.ext.sspId); | ||
| } | ||
| const siteIdBid = bidderRequest.bids.find(bid => bid.params?.siteId); | ||
| if (siteIdBid) { | ||
| request.ext = request.ext || {}; | ||
| request.ext.siteId = siteIdBid.params.siteId; | ||
| logInfo('siteId detected in bid params, setting siteId in request:', request.ext.siteId); | ||
| } | ||
| } | ||
|
|
||
| if (bidderRequest.gdprConsent || bidderRequest.uspConsent) { | ||
| request.regs = request.regs || {}; | ||
| request.user = request.user || {}; | ||
| } | ||
| if (bidderRequest.gdprConsent) { | ||
| logInfo('Adding GDPR consent information to request:', bidderRequest.gdprConsent); | ||
| request.regs.gdpr = bidderRequest.gdprConsent.gdprApplies ? 1 : 0; | ||
| request.user.consent = bidderRequest.gdprConsent.consentString; | ||
|
pritishmd-talentica marked this conversation as resolved.
pritishmd-talentica marked this conversation as resolved.
|
||
| } | ||
| if (bidderRequest.uspConsent) { | ||
| logInfo('Adding USP consent information to request:', bidderRequest.uspConsent); | ||
| request.regs.ext = request.regs.ext || {}; | ||
| request.regs.ext.us_privacy = bidderRequest.uspConsent; | ||
| } | ||
| return request; | ||
| }, | ||
| }); | ||
| } | ||
|
|
||
| /** | ||
| * Validates the bid request (video mimes/sizes, etc.). | ||
| * @param {Object} bid - The bid request object. | ||
| * @returns {boolean} True if the bid request is valid. | ||
| */ | ||
| export function isBidRequestValid(bid) { | ||
| logInfo('Validating bid request:', bid); | ||
| const { mediaTypes } = bid; | ||
|
|
||
| if (mediaTypes?.[VIDEO]) { | ||
| const video = mediaTypes[VIDEO]; | ||
| if (!video.mimes || !Array.isArray(video.mimes) || video.mimes.length === 0) { | ||
| logWarn('Invalid video bid request: Missing or invalid mimes.'); | ||
| return false; | ||
| } | ||
| // w and h are optional; if provided they must be positive | ||
| if (video.w != null && video.w <= 0) { | ||
| logWarn('Invalid video bid request: Invalid width.'); | ||
| return false; | ||
| } | ||
| if (video.h != null && video.h <= 0) { | ||
| logWarn('Invalid video bid request: Invalid height.'); | ||
| return false; | ||
| } | ||
| } | ||
| return true; | ||
| } | ||
|
|
||
| /** | ||
| * Builds buildRequests function that uses the given converter and endpoint. | ||
| * @param {Object} config - { converter, endpointUrl } | ||
| * @returns {function(Array, Object): Object} | ||
| */ | ||
| export function createBuildRequests(config) { | ||
| const { converter, endpointUrl } = config; | ||
|
|
||
| return function buildRequests(validBidRequests, bidderRequest) { | ||
| logInfo('Building server request for valid bid requests:', validBidRequests); | ||
|
|
||
| const request = converter.toORTB({ bidRequests: validBidRequests, bidderRequest }); | ||
| logInfo('Converted to ORTB request:', request); | ||
| return { | ||
| method: 'POST', | ||
| url: endpointUrl, | ||
| data: request, | ||
| options: { endpointCompression: true }, | ||
| }; | ||
| }; | ||
| } | ||
|
|
||
| /** | ||
| * Interprets the server response and extracts bid information. | ||
| * @param {Object} serverResponse - The response from the server. | ||
| * @param {Object} request - The original request sent to the server. | ||
| * @param {Object} config - { defaultCurrency, defaultTtl } | ||
| * @returns {Array} Array of bid objects. | ||
| */ | ||
| export function interpretResponse(serverResponse, request, config = {}) { | ||
| const defaultCurrency = config.defaultCurrency ?? DEFAULT_CURRENCY; | ||
| const defaultTtl = config.defaultTtl ?? DEFAULT_TTL; | ||
|
|
||
| logInfo('Interpreting server response:', serverResponse); | ||
| const bidResp = serverResponse?.body; | ||
| if (!bidResp || !Array.isArray(bidResp.seatbid)) { | ||
| logWarn('Server response is empty, invalid, or does not contain seatbid array.'); | ||
| return []; | ||
| } | ||
|
|
||
| const responses = []; | ||
| bidResp.seatbid.forEach(seatbid => { | ||
| if (!Array.isArray(seatbid.bid) || seatbid.bid.length === 0) return; | ||
| const bid = seatbid.bid[0]; | ||
|
pritishmd-talentica marked this conversation as resolved.
|
||
| if (!bid.impid || bid.price == null) { | ||
| logWarn('Skipping bid with missing impid or price, bidId:', bid.id); | ||
| return; | ||
| } | ||
| logInfo('Processing bid response:', bid); | ||
| const bidResponse = { | ||
| requestId: bid.impid, | ||
| cpm: bid.price, | ||
| currency: bidResp.cur || defaultCurrency, | ||
| width: bid.w, | ||
| height: bid.h, | ||
| ad: bid.adm, | ||
| creativeId: bid.crid, | ||
| netRevenue: true, | ||
| ttl: defaultTtl, | ||
| meta: { advertiserDomains: bid.adomain || [] }, | ||
| }; | ||
|
pritishmd-talentica marked this conversation as resolved.
|
||
|
|
||
| switch (bid.mtype) { | ||
| case 1: | ||
| bidResponse.mediaType = BANNER; | ||
| break; | ||
| case 2: | ||
| bidResponse.mediaType = VIDEO; | ||
| bidResponse.vastXml = bid.adm; | ||
| break; | ||
| default: | ||
| if (bid.mtype != null) { | ||
| logWarn('Unknown media type: ', bid.mtype, ' for bidId: ', bid.id); | ||
| } else { | ||
| logWarn('Bid response does not contain media type for bidId: ', bid.id); | ||
| } | ||
| bidResponse.mediaType = BANNER; | ||
| break; | ||
| } | ||
|
|
||
| if (bid.dealid) bidResponse.dealId = bid.dealid; | ||
| logInfo('Interpreted response:', bidResponse, ' for bidId: ', bid.id); | ||
| responses.push(bidResponse); | ||
|
pritishmd-talentica marked this conversation as resolved.
|
||
| }); | ||
|
|
||
| logInfo('Interpreted bid responses:', responses); | ||
| return responses; | ||
| } | ||
|
|
||
| /** | ||
| * Creates getUserSyncs function that builds sync URL with privacy params. | ||
| * @param {string} syncUrl - Base sync URL (e.g. 'https://sync.adsmartx.com/sync') | ||
| * @returns {function(Object, Array, Object, string, Object): Array} | ||
| */ | ||
| export function createGetUserSyncs(syncUrl) { | ||
| return function getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent, gppConsent) { | ||
| logInfo('getUserSyncs called with options:', syncOptions); | ||
| if (!syncOptions.iframeEnabled && !syncOptions.pixelEnabled) { | ||
| logWarn('User sync disabled: neither iframe nor pixel is enabled'); | ||
| return []; | ||
| } | ||
|
|
||
| const params = []; | ||
| if (gdprConsent) { | ||
| params.push('gdpr=' + (gdprConsent.gdprApplies ? 1 : 0)); | ||
| params.push('gdpr_consent=' + encodeURIComponent(gdprConsent.consentString || '')); | ||
| } | ||
| if (uspConsent) { | ||
| params.push('us_privacy=' + encodeURIComponent(uspConsent)); | ||
| } | ||
| if (gppConsent?.gppString && gppConsent?.applicableSections?.length) { | ||
| params.push('gpp=' + encodeURIComponent(gppConsent.gppString)); | ||
| params.push('gpp_sid=' + encodeURIComponent(gppConsent.applicableSections.join(','))); | ||
| } | ||
|
|
||
| params.push('ssp_id=630141'); | ||
| params.push('iframe_enabled=' + (syncOptions.iframeEnabled ? 'true' : 'false')); | ||
|
|
||
| const queryString = params.length ? '?' + params.join('&') : ''; | ||
| const syncs = [{ | ||
| type: syncOptions.iframeEnabled ? 'iframe' : 'image', | ||
| url: syncUrl + queryString, | ||
| }]; | ||
| logInfo('Returning user syncs, type:', syncs[0]?.type); | ||
| return syncs; | ||
| }; | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| import { registerBidder } from '../src/adapters/bidderFactory.js'; | ||
| import { BANNER, VIDEO } from '../src/mediaTypes.js'; | ||
| import { | ||
| createConverter, | ||
| isBidRequestValid as validateBidRequest, | ||
| createBuildRequests, | ||
| interpretResponse as interpretResponseUtil, | ||
| createGetUserSyncs, | ||
| } from '../libraries/adsmartxUtils/bidderUtils.js'; | ||
|
|
||
| const BIDDER_CODE = 'adsmartx'; | ||
| const ENDPOINT_URL = 'https://ads.adsmartx.com/ads/rtb/prebid/js'; | ||
| const SYNC_URL = 'https://sync.adsmartx.com/sync'; | ||
| const DEFAULT_CURRENCY = 'USD'; | ||
| const DEFAULT_TTL = 60; | ||
|
|
||
| const converter = createConverter({ defaultCurrency: DEFAULT_CURRENCY, defaultTtl: DEFAULT_TTL }); | ||
|
|
||
| const isBidRequestValid = validateBidRequest; | ||
| const buildRequests = createBuildRequests( | ||
| { converter, endpointUrl: ENDPOINT_URL } | ||
| ); | ||
| const getUserSyncs = createGetUserSyncs(SYNC_URL); | ||
|
|
||
| const interpretResponse = (serverResponse, request) => { | ||
| return interpretResponseUtil(serverResponse, request, { | ||
| defaultCurrency: DEFAULT_CURRENCY, | ||
| defaultTtl: DEFAULT_TTL, | ||
| }); | ||
| }; | ||
|
|
||
| export const spec = { | ||
| code: BIDDER_CODE, | ||
| // TODO: set gvlid once confirmed with AI Digital / AdSmartX team | ||
| gvlid: undefined, | ||
|
pritishmd-talentica marked this conversation as resolved.
|
||
| supportedMediaTypes: [BANNER, VIDEO], | ||
| isBidRequestValid, | ||
| buildRequests, | ||
| interpretResponse, | ||
| getUserSyncs, | ||
| }; | ||
|
|
||
| registerBidder(spec); | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,72 @@ | ||
| # Overview | ||
|
|
||
| Module Name : AdSmartX Bidder Adapter | ||
| Module Type : Bid Adapter | ||
| Maintainer : prebid@aidigital.com | ||
|
|
||
| # Description | ||
| Connects to AdSmartX Exchange for bids | ||
| AdSmartX supports Display & Video(Instream) currently. | ||
|
|
||
| This adapter is maintained by Smart Exchange, the legal entity behind this implementation. Our official domain is [AI Digital](https://www.aidigital.com/). | ||
|
pritishmd-talentica marked this conversation as resolved.
|
||
| # Sample Ad Unit : Banner | ||
| ``` | ||
| var adUnits = [ | ||
| { | ||
| code: 'test-banner-div', | ||
| mediaTypes: { | ||
| banner: { | ||
| sizes:[ | ||
| [320,50] | ||
| ] | ||
| } | ||
| }, | ||
| bids:[ | ||
| { | ||
| bidder: 'adsmartx', | ||
| params: { | ||
| bidfloor: 0.001, | ||
| testMode: 1, | ||
| sspId: 123456, | ||
| siteId: 987654, | ||
| sspUserId: 'u1234' | ||
| } | ||
| } | ||
| ] | ||
| } | ||
| ] | ||
| ``` | ||
|
|
||
| # Sample Ad Unit : Video | ||
| ``` | ||
| var videoAdUnit = [ | ||
| { | ||
| code: 'adsmartx', | ||
| mediaTypes: { | ||
| video: { | ||
| playerSize: [640, 480], // required | ||
| context: 'instream', | ||
| mimes: ['video/mp4','video/webm'], | ||
| minduration: 5, | ||
| maxduration: 30, | ||
| startdelay: 30, | ||
| maxseq: 2, | ||
| poddur: 30, | ||
| protocols: [1,3,4], | ||
| } | ||
| }, | ||
| bids:[ | ||
| { | ||
| bidder: 'adsmartx', | ||
| params: { | ||
| bidfloor: 0.001, | ||
| testMode: 1, | ||
| sspId: 123456, | ||
| siteId: 987654, | ||
| sspUserId: 'u1234' | ||
| } | ||
| } | ||
| ] | ||
| } | ||
| ] | ||
| ``` | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.