From e73e9187992b6db5b537839d5f8d12f30eeb94cb Mon Sep 17 00:00:00 2001 From: eknis Date: Wed, 3 Dec 2025 13:00:05 +0900 Subject: [PATCH 01/12] IntimateMerger Analytics Adapter : initial release --- modules/imAnalyticsAdapter.js | 292 ++++++++++++++++++++++++++++++++++ 1 file changed, 292 insertions(+) create mode 100644 modules/imAnalyticsAdapter.js diff --git a/modules/imAnalyticsAdapter.js b/modules/imAnalyticsAdapter.js new file mode 100644 index 00000000000..e8b287b59eb --- /dev/null +++ b/modules/imAnalyticsAdapter.js @@ -0,0 +1,292 @@ +import { logMessage } from '../src/utils.js'; +import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; +import adapterManager from '../src/adapterManager.js'; +import { EVENTS } from '../src/constants.js'; +import { ajax } from '../src/ajax.js'; + + +const DEBOUNCE_DELAY = 200; // 0.2 second +const DEFAULT_CID = 5126; +const API_BASE_URL = 'https://b6.im-apps.net/bids'; + +const cache = { + auctions: {}, + requestBidsData: null, + requestBidsTimer: null, + wonBidsData: null, + wonBidsTimer: null +}; + +/** + * Get current page URL + * @returns {string} Current page URL + */ +function getPageUrl() { + return window.location.href; +} + +/** + * Get CID from adapter options + * @param {Object} options - Adapter options + * @returns {string} CID or default value + */ +function getCid(options) { + return (options && options.cid) || DEFAULT_CID; +} + +/** + * Build API endpoint URL + * @param {string} cid - Customer ID + * @param {string} endpoint - Endpoint path + * @returns {string} Full API URL + */ +function buildApiUrl(cid, endpoint) { + return `${API_BASE_URL}/${cid}/${endpoint}`; +} + +/** + * Send data to API endpoint + * @param {string} url - API endpoint URL + * @param {Object} payload - Data to send + */ +function sendToApi(url, payload) { + ajax( + url, + null, + JSON.stringify(payload), + { method: 'POST', contentType: 'application/json' } + ); +} + +/** + * Transform adUnit data for payload + * @param {Object} adUnit - Ad unit object + * @returns {Object} Transformed ad unit data + */ +function transformAdUnit(adUnit) { + return { + code: adUnit.code, + mediaTypes: adUnit.mediaTypes, + sizes: adUnit.sizes, + bids: (adUnit.bids || []).map(bid => ({ + bidder: bid.bidder, + params: bid.params + })) + }; +} + +/** + * Transform auction data for payload + * @param {Object} auctionArgs - Auction arguments + * @returns {Object} Transformed auction data + */ +function transformAuctionData(auctionArgs) { + return { + auctionId: auctionArgs.auctionId, + timestamp: auctionArgs.timestamp, + timeout: auctionArgs.timeout, + adUnitCodes: auctionArgs.adUnitCodes || [], + adUnits: (auctionArgs.adUnits || []).map(transformAdUnit) + }; +} + +/** + * Transform bid won data for payload + * @param {Object} bidWonArgs - Bid won arguments + * @returns {Object} Transformed bid won data + */ +function transformBidWonData(bidWonArgs) { + return { + auctionId: bidWonArgs.auctionId, + adId: bidWonArgs.adId, + adUnitCode: bidWonArgs.adUnitCode, + bidder: bidWonArgs.bidder || bidWonArgs.bidderCode, + cpm: bidWonArgs.cpm, + currency: bidWonArgs.currency, + creativeId: bidWonArgs.creativeId, + dealId: bidWonArgs.dealId, + mediaType: bidWonArgs.mediaType, + size: bidWonArgs.size, + timeToRespond: bidWonArgs.timeToRespond, + meta: bidWonArgs.meta || {} + }; +} + +/** + * Clear timer if exists + * @param {number|null} timer - Timer ID + * @returns {null} + */ +function clearTimer(timer) { + if (timer) { + clearTimeout(timer); + } + return null; +} + +/** + * Initialize or reset page-level data cache + * @param {string} pageUrl - Current page URL + * @returns {Object} Initialized cache object + */ +function initializePageCache(pageUrl) { + return { + pageUrl, + timestamp: Date.now(), + auctions: [] + }; +} + +/** + * Initialize or reset won bids data cache + * @param {string} pageUrl - Current page URL + * @returns {Object} Initialized cache object + */ +function initializeWonBidsCache(pageUrl) { + return { + pageUrl, + timestamp: Date.now(), + wonBids: [] + }; +} + +// IM Analytics Adapter implementation +const imAnalyticsAdapter = Object.assign( + adapter({ analyticsType: 'endpoint' }), + { + /** + * Track Prebid.js events + * @param {Object} params - Event parameters + * @param {string} params.eventType - Type of event + * @param {Object} params.args - Event arguments + */ + track({ eventType, args }) { + switch (eventType) { + case EVENTS.AUCTION_INIT: + logMessage('IM Analytics: AUCTION_INIT', args); + this.handleAuctionInit(args); + break; + + case EVENTS.BID_WON: + logMessage('IM Analytics: BID_WON', args); + this.handleBidWon(args); + break; + } + }, + + /** + * Handle AUCTION_INIT event + * @param {Object} auctionArgs - Auction arguments + */ + handleAuctionInit(auctionArgs) { + cache.auctions[auctionArgs.auctionId] = { + bids: {}, + pv: { + cid: auctionArgs.cid, + name: auctionArgs.name, + action_id: auctionArgs.action_id, + adUnits: auctionArgs.adUnits, + } + }; + this.accumulateRequestBidsData(auctionArgs); + }, + + /** + * Handle BID_WON event + * @param {Object} bidWonArgs - Bid won arguments + */ + handleBidWon(bidWonArgs) { + this.accumulateWonBidsData(bidWonArgs); + }, + + /** + * Accumulate request bids data with debounce + * @param {Object} auctionArgs - Auction arguments + */ + accumulateRequestBidsData(auctionArgs) { + const pageUrl = getPageUrl(); + if (!cache.requestBidsData || cache.requestBidsData.pageUrl !== pageUrl) { + cache.requestBidsData = initializePageCache(pageUrl); + } + + cache.requestBidsData.auctions.push(transformAuctionData(auctionArgs)); + cache.requestBidsTimer = clearTimer(cache.requestBidsTimer); + cache.requestBidsTimer = setTimeout(() => { + this.sendRequestBidsData(); + }, DEBOUNCE_DELAY); + }, + + /** + * Send accumulated request bids data to API + */ + sendRequestBidsData() { + if (!cache.requestBidsData) return; + + const cid = getCid(this.options); + const payload = { + pageUrl: cache.requestBidsData.pageUrl, + timestamp: cache.requestBidsData.timestamp, + auctionCount: cache.requestBidsData.auctions.length, + auctions: cache.requestBidsData.auctions + }; + + sendToApi(buildApiUrl(cid, 'request'), payload); + + cache.requestBidsData = null; + cache.requestBidsTimer = null; + }, + + /** + * Accumulate won bids data with debounce + * @param {Object} bidWonArgs - Bid won arguments + */ + accumulateWonBidsData(bidWonArgs) { + const pageUrl = getPageUrl(); + + if (!cache.wonBidsData || cache.wonBidsData.pageUrl !== pageUrl) { + cache.wonBidsData = initializeWonBidsCache(pageUrl); + } + + cache.wonBidsData.wonBids.push(transformBidWonData(bidWonArgs)); + + cache.wonBidsTimer = clearTimer(cache.wonBidsTimer); + cache.wonBidsTimer = setTimeout(() => { + this.sendWonBidsData(); + }, DEBOUNCE_DELAY); + }, + + /** + * Send accumulated won bids data to API + */ + sendWonBidsData() { + if (!cache.wonBidsData) return; + + const cid = getCid(this.options); + const payload = { + pageUrl: cache.wonBidsData.pageUrl, + timestamp: cache.wonBidsData.timestamp, + wonBidCount: cache.wonBidsData.wonBids.length, + wonBids: cache.wonBidsData.wonBids + }; + + sendToApi(buildApiUrl(cid, 'won'), payload); + + cache.wonBidsData = null; + cache.wonBidsTimer = null; + } + } +); + +const originalEnableAnalytics = imAnalyticsAdapter.enableAnalytics; +imAnalyticsAdapter.enableAnalytics = function(config) { + this.options = (config && config.options) || {}; + logMessage('IM Analytics: enableAnalytics called with cid:', this.options.cid); + originalEnableAnalytics.call(this, config); +}; + +adapterManager.registerAnalyticsAdapter({ + adapter: imAnalyticsAdapter, + code: 'imAnalytics' +}); + +export default imAnalyticsAdapter; From 280a6d6c726465021d87d5d2adab23be8906dfc8 Mon Sep 17 00:00:00 2001 From: eknis Date: Wed, 3 Dec 2025 13:34:57 +0900 Subject: [PATCH 02/12] IntimateMerger Analytics Adapter : sendBeacon --- modules/imAnalyticsAdapter.js | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/modules/imAnalyticsAdapter.js b/modules/imAnalyticsAdapter.js index e8b287b59eb..d5f1c362bd7 100644 --- a/modules/imAnalyticsAdapter.js +++ b/modules/imAnalyticsAdapter.js @@ -2,8 +2,6 @@ import { logMessage } from '../src/utils.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; import { EVENTS } from '../src/constants.js'; -import { ajax } from '../src/ajax.js'; - const DEBOUNCE_DELAY = 200; // 0.2 second const DEFAULT_CID = 5126; @@ -45,17 +43,14 @@ function buildApiUrl(cid, endpoint) { } /** - * Send data to API endpoint + * Send data to API endpoint using sendBeacon * @param {string} url - API endpoint URL * @param {Object} payload - Data to send */ function sendToApi(url, payload) { - ajax( - url, - null, - JSON.stringify(payload), - { method: 'POST', contentType: 'application/json' } - ); + const data = JSON.stringify(payload); + const blob = new Blob([data], { type: 'application/json' }); + navigator.sendBeacon(url, blob); } /** @@ -229,9 +224,7 @@ const imAnalyticsAdapter = Object.assign( auctionCount: cache.requestBidsData.auctions.length, auctions: cache.requestBidsData.auctions }; - sendToApi(buildApiUrl(cid, 'request'), payload); - cache.requestBidsData = null; cache.requestBidsTimer = null; }, @@ -246,9 +239,7 @@ const imAnalyticsAdapter = Object.assign( if (!cache.wonBidsData || cache.wonBidsData.pageUrl !== pageUrl) { cache.wonBidsData = initializeWonBidsCache(pageUrl); } - cache.wonBidsData.wonBids.push(transformBidWonData(bidWonArgs)); - cache.wonBidsTimer = clearTimer(cache.wonBidsTimer); cache.wonBidsTimer = setTimeout(() => { this.sendWonBidsData(); @@ -268,9 +259,7 @@ const imAnalyticsAdapter = Object.assign( wonBidCount: cache.wonBidsData.wonBids.length, wonBids: cache.wonBidsData.wonBids }; - sendToApi(buildApiUrl(cid, 'won'), payload); - cache.wonBidsData = null; cache.wonBidsTimer = null; } From 0103771b2c4815bb1bf17d891d3f2acf763d10ce Mon Sep 17 00:00:00 2001 From: eknis Date: Wed, 3 Dec 2025 15:38:36 +0900 Subject: [PATCH 03/12] IntimateMerger Analytics Adapter : refactoring --- modules/imAnalyticsAdapter.js | 202 +++++++++++++--------------------- 1 file changed, 78 insertions(+), 124 deletions(-) diff --git a/modules/imAnalyticsAdapter.js b/modules/imAnalyticsAdapter.js index d5f1c362bd7..59c5754a240 100644 --- a/modules/imAnalyticsAdapter.js +++ b/modules/imAnalyticsAdapter.js @@ -9,20 +9,12 @@ const API_BASE_URL = 'https://b6.im-apps.net/bids'; const cache = { auctions: {}, - requestBidsData: null, - requestBidsTimer: null, + ReqBidsData: null, + ReqBidsTimer: null, wonBidsData: null, wonBidsTimer: null }; -/** - * Get current page URL - * @returns {string} Current page URL - */ -function getPageUrl() { - return window.location.href; -} - /** * Get CID from adapter options * @param {Object} options - Adapter options @@ -53,60 +45,6 @@ function sendToApi(url, payload) { navigator.sendBeacon(url, blob); } -/** - * Transform adUnit data for payload - * @param {Object} adUnit - Ad unit object - * @returns {Object} Transformed ad unit data - */ -function transformAdUnit(adUnit) { - return { - code: adUnit.code, - mediaTypes: adUnit.mediaTypes, - sizes: adUnit.sizes, - bids: (adUnit.bids || []).map(bid => ({ - bidder: bid.bidder, - params: bid.params - })) - }; -} - -/** - * Transform auction data for payload - * @param {Object} auctionArgs - Auction arguments - * @returns {Object} Transformed auction data - */ -function transformAuctionData(auctionArgs) { - return { - auctionId: auctionArgs.auctionId, - timestamp: auctionArgs.timestamp, - timeout: auctionArgs.timeout, - adUnitCodes: auctionArgs.adUnitCodes || [], - adUnits: (auctionArgs.adUnits || []).map(transformAdUnit) - }; -} - -/** - * Transform bid won data for payload - * @param {Object} bidWonArgs - Bid won arguments - * @returns {Object} Transformed bid won data - */ -function transformBidWonData(bidWonArgs) { - return { - auctionId: bidWonArgs.auctionId, - adId: bidWonArgs.adId, - adUnitCode: bidWonArgs.adUnitCode, - bidder: bidWonArgs.bidder || bidWonArgs.bidderCode, - cpm: bidWonArgs.cpm, - currency: bidWonArgs.currency, - creativeId: bidWonArgs.creativeId, - dealId: bidWonArgs.dealId, - mediaType: bidWonArgs.mediaType, - size: bidWonArgs.size, - timeToRespond: bidWonArgs.timeToRespond, - meta: bidWonArgs.meta || {} - }; -} - /** * Clear timer if exists * @param {number|null} timer - Timer ID @@ -124,7 +62,7 @@ function clearTimer(timer) { * @param {string} pageUrl - Current page URL * @returns {Object} Initialized cache object */ -function initializePageCache(pageUrl) { +function initializeReqBidsCache(pageUrl) { return { pageUrl, timestamp: Date.now(), @@ -159,107 +97,123 @@ const imAnalyticsAdapter = Object.assign( switch (eventType) { case EVENTS.AUCTION_INIT: logMessage('IM Analytics: AUCTION_INIT', args); - this.handleAuctionInit(args); + cache.auctions[args.auctionId] = { + bids: {}, + pv: { + cid: args.cid, + name: args.name, + action_id: args.action_id, + adUnits: args.adUnits, + } + }; + this.handleReqBidsData(args); break; case EVENTS.BID_WON: logMessage('IM Analytics: BID_WON', args); - this.handleBidWon(args); + this.handleWonBidsData(args); break; } }, /** - * Handle AUCTION_INIT event + * Handle request bids data with debounce * @param {Object} auctionArgs - Auction arguments */ - handleAuctionInit(auctionArgs) { - cache.auctions[auctionArgs.auctionId] = { - bids: {}, - pv: { - cid: auctionArgs.cid, - name: auctionArgs.name, - action_id: auctionArgs.action_id, - adUnits: auctionArgs.adUnits, - } - }; - this.accumulateRequestBidsData(auctionArgs); - }, - - /** - * Handle BID_WON event - * @param {Object} bidWonArgs - Bid won arguments - */ - handleBidWon(bidWonArgs) { - this.accumulateWonBidsData(bidWonArgs); + handleReqBidsData(auctionArgs) { + const pageUrl = window.location.href; + if (!cache.ReqBidsData || cache.ReqBidsData.pageUrl !== pageUrl) { + cache.ReqBidsData = initializeReqBidsCache(pageUrl); + } + cache.ReqBidsData.auctions.push(this.transformReqBidsData(auctionArgs)); + cache.ReqBidsTimer = clearTimer(cache.ReqBidsTimer); + cache.ReqBidsTimer = setTimeout(() => { + this.sendReqBidsData(); + }, DEBOUNCE_DELAY); }, /** - * Accumulate request bids data with debounce + * Transform auction data for payload * @param {Object} auctionArgs - Auction arguments + * @returns {Object} Transformed auction data */ - accumulateRequestBidsData(auctionArgs) { - const pageUrl = getPageUrl(); - if (!cache.requestBidsData || cache.requestBidsData.pageUrl !== pageUrl) { - cache.requestBidsData = initializePageCache(pageUrl); - } - - cache.requestBidsData.auctions.push(transformAuctionData(auctionArgs)); - cache.requestBidsTimer = clearTimer(cache.requestBidsTimer); - cache.requestBidsTimer = setTimeout(() => { - this.sendRequestBidsData(); - }, DEBOUNCE_DELAY); + transformReqBidsData(auctionArgs) { + return { + auctionId: auctionArgs.auctionId, + pv: auctionArgs.pv, + timestamp: auctionArgs.timestamp, + timeout: auctionArgs.timeout, + adUnitCodes: auctionArgs.adUnitCodes || [], + adUnits: (auctionArgs.adUnits || []).map(adUnit => { + return { + code: adUnit.code, + mediaTypes: adUnit.mediaTypes, + sizes: adUnit.sizes, + bids: (adUnit.bids || []).map(bid => ({ + bidder: bid.bidder, + params: bid.paramsr + })) + } + }) + }; }, /** * Send accumulated request bids data to API */ - sendRequestBidsData() { - if (!cache.requestBidsData) return; - + sendReqBidsData() { + if (!cache.ReqBidsData) return; const cid = getCid(this.options); - const payload = { - pageUrl: cache.requestBidsData.pageUrl, - timestamp: cache.requestBidsData.timestamp, - auctionCount: cache.requestBidsData.auctions.length, - auctions: cache.requestBidsData.auctions - }; - sendToApi(buildApiUrl(cid, 'request'), payload); - cache.requestBidsData = null; - cache.requestBidsTimer = null; + sendToApi(buildApiUrl(cid, 'request'), cache.ReqBidsData); + cache.ReqBidsData = null; + cache.ReqBidsTimer = null; }, /** - * Accumulate won bids data with debounce + * Handle won bids data with debounce * @param {Object} bidWonArgs - Bid won arguments */ - accumulateWonBidsData(bidWonArgs) { - const pageUrl = getPageUrl(); - + handleWonBidsData(bidWonArgs) { + const pageUrl = window.location.href; if (!cache.wonBidsData || cache.wonBidsData.pageUrl !== pageUrl) { cache.wonBidsData = initializeWonBidsCache(pageUrl); } - cache.wonBidsData.wonBids.push(transformBidWonData(bidWonArgs)); + cache.wonBidsData.wonBids.push(this.transformWonBidsData(bidWonArgs)); cache.wonBidsTimer = clearTimer(cache.wonBidsTimer); cache.wonBidsTimer = setTimeout(() => { this.sendWonBidsData(); }, DEBOUNCE_DELAY); }, + /** + * Transform won bids data for payload + * @param {Object} bidWonArgs - Bid won arguments + * @returns {Object} Transformed won bids data + */ + transformWonBidsData(bidWonArgs) { + return { + auctionId: bidWonArgs.auctionId, + adId: bidWonArgs.adId, + adUnitCode: bidWonArgs.adUnitCode, + bidder: bidWonArgs.bidder || bidWonArgs.bidderCode, + cpm: bidWonArgs.cpm, + currency: bidWonArgs.currency, + creativeId: bidWonArgs.creativeId, + dealId: bidWonArgs.dealId, + mediaType: bidWonArgs.mediaType, + size: bidWonArgs.size, + timeToRespond: bidWonArgs.timeToRespond, + meta: bidWonArgs.meta || {} + }; + }, + /** * Send accumulated won bids data to API */ sendWonBidsData() { if (!cache.wonBidsData) return; - const cid = getCid(this.options); - const payload = { - pageUrl: cache.wonBidsData.pageUrl, - timestamp: cache.wonBidsData.timestamp, - wonBidCount: cache.wonBidsData.wonBids.length, - wonBids: cache.wonBidsData.wonBids - }; - sendToApi(buildApiUrl(cid, 'won'), payload); + sendToApi(buildApiUrl(cid, 'won'), cache.wonBidsData); cache.wonBidsData = null; cache.wonBidsTimer = null; } From 92606c46d08f2d8b860390aa455b07b564a4f10c Mon Sep 17 00:00:00 2001 From: eknis Date: Wed, 3 Dec 2025 15:50:28 +0900 Subject: [PATCH 04/12] IntimateMerger Analytics Adapter : fix sendBeacon --- modules/imAnalyticsAdapter.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/imAnalyticsAdapter.js b/modules/imAnalyticsAdapter.js index 59c5754a240..07d0b15e032 100644 --- a/modules/imAnalyticsAdapter.js +++ b/modules/imAnalyticsAdapter.js @@ -2,6 +2,7 @@ import { logMessage } from '../src/utils.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; import { EVENTS } from '../src/constants.js'; +import { sendBeacon } from '../src/ajax.js'; const DEBOUNCE_DELAY = 200; // 0.2 second const DEFAULT_CID = 5126; @@ -42,7 +43,7 @@ function buildApiUrl(cid, endpoint) { function sendToApi(url, payload) { const data = JSON.stringify(payload); const blob = new Blob([data], { type: 'application/json' }); - navigator.sendBeacon(url, blob); + sendBeacon(url, blob); } /** From 0a7a285273bff69f2248e14b7eafa614548e033f Mon Sep 17 00:00:00 2001 From: eknis Date: Thu, 4 Dec 2025 16:54:34 +0900 Subject: [PATCH 05/12] IntimateMerger Analytics Adapter : update comment --- modules/imAnalyticsAdapter.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/imAnalyticsAdapter.js b/modules/imAnalyticsAdapter.js index 07d0b15e032..8fa16ea8fa9 100644 --- a/modules/imAnalyticsAdapter.js +++ b/modules/imAnalyticsAdapter.js @@ -134,7 +134,7 @@ const imAnalyticsAdapter = Object.assign( }, /** - * Transform auction data for payload + * Transform auction data for auction init event * @param {Object} auctionArgs - Auction arguments * @returns {Object} Transformed auction data */ @@ -187,7 +187,7 @@ const imAnalyticsAdapter = Object.assign( }, /** - * Transform won bids data for payload + * Transform won bids data for bid won event * @param {Object} bidWonArgs - Bid won arguments * @returns {Object} Transformed won bids data */ From 8d675357f123ca9a215b93a42bd2c319b5d2db0f Mon Sep 17 00:00:00 2001 From: eknis Date: Tue, 9 Dec 2025 17:23:56 +0900 Subject: [PATCH 06/12] IntimateMerger Analytics Adapter : refactoring --- modules/imAnalyticsAdapter.js | 253 ++++++++++++++++++++++------------ 1 file changed, 163 insertions(+), 90 deletions(-) diff --git a/modules/imAnalyticsAdapter.js b/modules/imAnalyticsAdapter.js index 8fa16ea8fa9..117c71590c2 100644 --- a/modules/imAnalyticsAdapter.js +++ b/modules/imAnalyticsAdapter.js @@ -4,15 +4,24 @@ import adapterManager from '../src/adapterManager.js'; import { EVENTS } from '../src/constants.js'; import { sendBeacon } from '../src/ajax.js'; -const DEBOUNCE_DELAY = 200; // 0.2 second +const BID_WON_TIMEOUT = 800; // 0.8 second for initial batch const DEFAULT_CID = 5126; const API_BASE_URL = 'https://b6.im-apps.net/bids'; +// Send status flags +const WON_SENT = 1; + +// Default values +const EMPTY_CONSENT_DATA = { + gdprApplies: undefined, + gdpr: undefined, + usp: undefined, + gpp: undefined +}; + const cache = { auctions: {}, - ReqBidsData: null, - ReqBidsTimer: null, - wonBidsData: null, + wonBidsData: {}, wonBidsTimer: null }; @@ -26,12 +35,13 @@ function getCid(options) { } /** - * Build API endpoint URL - * @param {string} cid - Customer ID + * Build API URL with CID from options + * @param {Object} options - Adapter options * @param {string} endpoint - Endpoint path * @returns {string} Full API URL */ -function buildApiUrl(cid, endpoint) { +function buildApiUrlWithOptions(options, endpoint) { + const cid = getCid(options); return `${API_BASE_URL}/${cid}/${endpoint}`; } @@ -59,31 +69,57 @@ function clearTimer(timer) { } /** - * Initialize or reset page-level data cache - * @param {string} pageUrl - Current page URL - * @returns {Object} Initialized cache object + * Get consent data from bidder requests + * @param {Array} bidderRequests - Bidder requests array + * @returns {Object} Consent data object */ -function initializeReqBidsCache(pageUrl) { +function getConsentData(bidderRequests) { + if (!bidderRequests || !bidderRequests[0]) { + return EMPTY_CONSENT_DATA; + } + + const request = bidderRequests[0]; + const gdprConsent = request.gdprConsent || {}; + const uspConsent = request.uspConsent; + const gppConsent = request.gppConsent || {}; + return { - pageUrl, - timestamp: Date.now(), - auctions: [] + gdprApplies: gdprConsent.gdprApplies, + gdpr: gdprConsent.consentString, + usp: uspConsent, + gpp: gppConsent.gppString }; } /** - * Initialize or reset won bids data cache - * @param {string} pageUrl - Current page URL - * @returns {Object} Initialized cache object + * Extract meta fields from bid won arguments + * @param {Object} meta - Meta object + * @returns {Object} Extracted meta fields */ -function initializeWonBidsCache(pageUrl) { +function extractMetaFields(meta) { return { - pageUrl, - timestamp: Date.now(), - wonBids: [] + advertiserDomains: meta.advertiserDomains || [], + primaryCatId: meta.primaryCatId || '', + secondaryCatIds: meta.secondaryCatIds || [], + advertiserName: meta.advertiserName || '', + advertiserId: meta.advertiserId || '', + brandName: meta.brandName || '', + brandId: meta.brandId || '' }; } +/** + * Mark auctions as sent + * @param {Array} auctionIds - Auction IDs to mark + */ +function markAuctionsAsSent(auctionIds) { + auctionIds.forEach(auctionId => { + if (cache.auctions[auctionId]) { + cache.auctions[auctionId].sendStatus |= WON_SENT; + } + }); +} + // IM Analytics Adapter implementation const imAnalyticsAdapter = Object.assign( adapter({ analyticsType: 'endpoint' }), @@ -98,39 +134,61 @@ const imAnalyticsAdapter = Object.assign( switch (eventType) { case EVENTS.AUCTION_INIT: logMessage('IM Analytics: AUCTION_INIT', args); - cache.auctions[args.auctionId] = { - bids: {}, - pv: { - cid: args.cid, - name: args.name, - action_id: args.action_id, - adUnits: args.adUnits, - } - }; - this.handleReqBidsData(args); + this.handleAuctionInit(args); break; case EVENTS.BID_WON: logMessage('IM Analytics: BID_WON', args); this.handleWonBidsData(args); break; + + case EVENTS.AUCTION_END: + logMessage('IM Analytics: AUCTION_END', args); + this.scheduleWonBidsSend(); + break; } }, /** - * Handle request bids data with debounce + * Handle auction init event + * @param {Object} args - Auction arguments + */ + handleAuctionInit(args) { + const consentData = getConsentData(args.bidderRequests); + + cache.auctions[args.auctionId] = { + consentData: consentData, + sendStatus: 0 + }; + + this.handleAucInitData(args, consentData); + }, + + /** + * Schedule won bids send after timeout + */ + scheduleWonBidsSend() { + cache.wonBidsTimer = clearTimer(cache.wonBidsTimer); + cache.wonBidsTimer = setTimeout(() => { + this.sendWonBidsData(); + }, BID_WON_TIMEOUT); + }, + + /** + * Handle auction init data - send immediately for PV tracking * @param {Object} auctionArgs - Auction arguments + * @param {Object} consentData - Consent data object */ - handleReqBidsData(auctionArgs) { - const pageUrl = window.location.href; - if (!cache.ReqBidsData || cache.ReqBidsData.pageUrl !== pageUrl) { - cache.ReqBidsData = initializeReqBidsCache(pageUrl); - } - cache.ReqBidsData.auctions.push(this.transformReqBidsData(auctionArgs)); - cache.ReqBidsTimer = clearTimer(cache.ReqBidsTimer); - cache.ReqBidsTimer = setTimeout(() => { - this.sendReqBidsData(); - }, DEBOUNCE_DELAY); + handleAucInitData(auctionArgs, consentData) { + const payload = { + pageUrl: window.location.href, + referrer: document.referrer || '', + consentData, + timestamp: Date.now(), + auction: this.transformAucInitData(auctionArgs) + }; + + sendToApi(buildApiUrlWithOptions(this.options, 'pv'), payload); }, /** @@ -138,84 +196,99 @@ const imAnalyticsAdapter = Object.assign( * @param {Object} auctionArgs - Auction arguments * @returns {Object} Transformed auction data */ - transformReqBidsData(auctionArgs) { + transformAucInitData(auctionArgs) { return { auctionId: auctionArgs.auctionId, pv: auctionArgs.pv, timestamp: auctionArgs.timestamp, - timeout: auctionArgs.timeout, adUnitCodes: auctionArgs.adUnitCodes || [], - adUnits: (auctionArgs.adUnits || []).map(adUnit => { - return { - code: adUnit.code, - mediaTypes: adUnit.mediaTypes, - sizes: adUnit.sizes, - bids: (adUnit.bids || []).map(bid => ({ - bidder: bid.bidder, - params: bid.paramsr - })) - } - }) + adUnitCount: (auctionArgs.adUnits || []).length }; }, /** - * Send accumulated request bids data to API + * Handle won bids data - batch first, then individual + * @param {Object} bidWonArgs - Bid won arguments */ - sendReqBidsData() { - if (!cache.ReqBidsData) return; - const cid = getCid(this.options); - sendToApi(buildApiUrl(cid, 'request'), cache.ReqBidsData); - cache.ReqBidsData = null; - cache.ReqBidsTimer = null; + handleWonBidsData(bidWonArgs) { + const auctionId = bidWonArgs.auctionId; + const auction = cache.auctions[auctionId]; + + if (!auction) return; + + // If initial batch has been sent, send immediately + if (auction.sendStatus & WON_SENT) { + this.sendIndividualWonBid(auctionId, bidWonArgs, auction.consentData); + } else { + this.cacheWonBid(auctionId, bidWonArgs); + } }, /** - * Handle won bids data with debounce + * Send individual won bid immediately + * @param {string} auctionId - Auction ID * @param {Object} bidWonArgs - Bid won arguments + * @param {Object} consentData - Consent data */ - handleWonBidsData(bidWonArgs) { - const pageUrl = window.location.href; - if (!cache.wonBidsData || cache.wonBidsData.pageUrl !== pageUrl) { - cache.wonBidsData = initializeWonBidsCache(pageUrl); + sendIndividualWonBid(auctionId, bidWonArgs, consentData) { + const wonBid = this.transformWonBidsData(bidWonArgs); + + sendToApi(buildApiUrlWithOptions(this.options, 'won'), { + consentData: consentData || getConsentData(null), + wonBids: { + [auctionId]: [wonBid] + } + }); + }, + + /** + * Cache won bid for batch send + * @param {string} auctionId - Auction ID + * @param {Object} bidWonArgs - Bid won arguments + */ + cacheWonBid(auctionId, bidWonArgs) { + if (!cache.wonBidsData[auctionId]) { + cache.wonBidsData[auctionId] = []; } - cache.wonBidsData.wonBids.push(this.transformWonBidsData(bidWonArgs)); - cache.wonBidsTimer = clearTimer(cache.wonBidsTimer); - cache.wonBidsTimer = setTimeout(() => { - this.sendWonBidsData(); - }, DEBOUNCE_DELAY); + cache.wonBidsData[auctionId].push(this.transformWonBidsData(bidWonArgs)); }, /** - * Transform won bids data for bid won event + * Transform bid won data for payload * @param {Object} bidWonArgs - Bid won arguments - * @returns {Object} Transformed won bids data + * @returns {Object} Transformed bid won data */ transformWonBidsData(bidWonArgs) { + const meta = bidWonArgs.meta || {}; + return { auctionId: bidWonArgs.auctionId, - adId: bidWonArgs.adId, - adUnitCode: bidWonArgs.adUnitCode, - bidder: bidWonArgs.bidder || bidWonArgs.bidderCode, - cpm: bidWonArgs.cpm, - currency: bidWonArgs.currency, - creativeId: bidWonArgs.creativeId, - dealId: bidWonArgs.dealId, - mediaType: bidWonArgs.mediaType, - size: bidWonArgs.size, - timeToRespond: bidWonArgs.timeToRespond, - meta: bidWonArgs.meta || {} + timestamp: Date.now(), + bidder: bidWonArgs.bidder, + bidderCode: bidWonArgs.bidderCode, + ...extractMetaFields(meta) }; }, /** - * Send accumulated won bids data to API + * Send accumulated won bids data to API - batch send after 800ms */ sendWonBidsData() { - if (!cache.wonBidsData) return; - const cid = getCid(this.options); - sendToApi(buildApiUrl(cid, 'won'), cache.wonBidsData); - cache.wonBidsData = null; + if (Object.keys(cache.wonBidsData).length === 0) return; + + const auctionIds = Object.keys(cache.wonBidsData); + const firstAuction = cache.auctions[auctionIds[0]]; + const consentData = firstAuction ? firstAuction.consentData : getConsentData(null); + + sendToApi(buildApiUrlWithOptions(this.options, 'won'), { + consentData, + wonBids: cache.wonBidsData + }); + + markAuctionsAsSent(auctionIds); + + // Clear cache + cache.wonBidsData = {}; cache.wonBidsTimer = null; } } From 18de1b7afc31726fb3f98a0b566507fa34d7e2f2 Mon Sep 17 00:00:00 2001 From: eknis Date: Thu, 18 Dec 2025 12:49:38 +0900 Subject: [PATCH 07/12] IntimateMerger Analytics Adapter : add test --- test/spec/modules/imAnalyticsAdapter_spec.js | 199 +++++++++++++++++++ 1 file changed, 199 insertions(+) create mode 100644 test/spec/modules/imAnalyticsAdapter_spec.js diff --git a/test/spec/modules/imAnalyticsAdapter_spec.js b/test/spec/modules/imAnalyticsAdapter_spec.js new file mode 100644 index 00000000000..739c1d92678 --- /dev/null +++ b/test/spec/modules/imAnalyticsAdapter_spec.js @@ -0,0 +1,199 @@ +import imAnalyticsAdapter from 'modules/imAnalyticsAdapter.js'; +import { expect } from 'chai'; +import { EVENTS } from 'src/constants.js'; +import * as utils from 'src/utils.js'; +import sinon from 'sinon'; + +describe('imAnalyticsAdapter', function() { + let sandbox; + let requests; + const BID_WON_TIMEOUT = 800; + + beforeEach(function() { + sandbox = sinon.createSandbox(); + requests = []; + + sandbox.stub(navigator, 'sendBeacon').callsFake((url, data) => { + requests.push({ + url, + data + }); + return true; + }); + + sandbox.stub(utils, 'logMessage'); + }); + + afterEach(function() { + sandbox.restore(); + imAnalyticsAdapter.disableAnalytics(); + requests = []; + }); + + describe('enableAnalytics', function() { + it('should catch the config options', function() { + imAnalyticsAdapter.enableAnalytics({ + provider: 'imAnalytics', + options: { + cid: 1234 + } + }); + expect(imAnalyticsAdapter.options.cid).to.equal(1234); + }); + + it('should use default cid if not provided', function() { + imAnalyticsAdapter.enableAnalytics({ + provider: 'imAnalytics' + }); + // Options doesn't get populated with default, but getCid uses it. + expect(imAnalyticsAdapter.options.cid).to.be.undefined; + + // We can also verify that a track call uses the default CID + const cid = (imAnalyticsAdapter.options && imAnalyticsAdapter.options.cid) || 5126; + expect(cid).to.equal(5126); + }); + }); + + describe('track', function() { + const bidWonArgs = { + auctionId: 'auc-1', + bidder: 'rubicon', + bidderCode: 'rubicon', + cpm: 1.5, + currency: 'USD', + originalCpm: 1.5, + originalCurrency: 'USD', + adUnitCode: 'div-1', + timeToRespond: 100, + meta: { + advertiserDomains: ['example.com'] + } + }; + + beforeEach(function() { + imAnalyticsAdapter.enableAnalytics({ + provider: 'imAnalytics', + options: { + cid: 5126 + } + }); + }); + + describe('AUCTION_INIT', function() { + it('should send pv event immediately', function() { + const args = { + auctionId: 'auc-1', + timestamp: 1234567890, + bidderRequests: [{ + gdprConsent: { + gdprApplies: true, + consentString: 'gdpr-string' + }, + uspConsent: 'usp-string', + gppConsent: { + gppString: 'gpp-string' + } + }], + adUnits: [{}, {}] + }; + + imAnalyticsAdapter.track({ + eventType: EVENTS.AUCTION_INIT, + args: args + }); + + expect(requests.length).to.equal(1); + expect(requests[0].url).to.include('/pv'); + }); + }); + + describe('BID_WON', function() { + it('should cache bid won events and send after timeout', function() { + const clock = sandbox.useFakeTimers(); + imAnalyticsAdapter.track({ + eventType: EVENTS.AUCTION_INIT, + args: { auctionId: 'auc-1', bidderRequests: [] } + }); + requests = []; + + imAnalyticsAdapter.track({ + eventType: EVENTS.BID_WON, + args: bidWonArgs + }); + + expect(requests.length).to.equal(0); + + imAnalyticsAdapter.track({ + eventType: EVENTS.AUCTION_END, + args: { auctionId: 'auc-1' } + }); + + clock.tick(10); + expect(requests.length).to.equal(0); + + clock.tick(BID_WON_TIMEOUT + 10); + + expect(requests.length).to.equal(1); + expect(requests[0].url).to.include('/won'); + }); + + it('should send subsequent won bids immediately', function() { + const clock = sandbox.useFakeTimers(); + + imAnalyticsAdapter.track({ + eventType: EVENTS.AUCTION_INIT, + args: { auctionId: 'auc-1', bidderRequests: [] } + }); + requests = []; + + imAnalyticsAdapter.track({ + eventType: EVENTS.BID_WON, + args: { ...bidWonArgs, requestId: 'req-1' } + }); + + imAnalyticsAdapter.track({ + eventType: EVENTS.AUCTION_END, + args: { auctionId: 'auc-1' } + }); + + clock.tick(BID_WON_TIMEOUT + 10); + expect(requests.length).to.equal(1); + + imAnalyticsAdapter.track({ + eventType: EVENTS.BID_WON, + args: { ...bidWonArgs, requestId: 'req-2' } + }); + + expect(requests.length).to.equal(2); + }); + }); + + describe('AUCTION_END', function() { + it('should schedule sending of won bids', function() { + const clock = sandbox.useFakeTimers(); + + imAnalyticsAdapter.track({ + eventType: EVENTS.AUCTION_INIT, + args: { auctionId: 'auc-1', bidderRequests: [] } + }); + requests = []; + + imAnalyticsAdapter.track({ + eventType: EVENTS.BID_WON, + args: { ...bidWonArgs, auctionId: 'auc-1' } + }); + + expect(requests.length).to.equal(0); + + imAnalyticsAdapter.track({ + eventType: EVENTS.AUCTION_END, + args: { auctionId: 'auc-1' } + }); + + clock.tick(BID_WON_TIMEOUT + 10); + expect(requests.length).to.equal(1); + expect(requests[0].url).to.include('/won'); + }); + }); + }); +}); From 6042b68de27afc52338cb1af6068e9c5ee31849b Mon Sep 17 00:00:00 2001 From: eknis Date: Thu, 18 Dec 2025 16:29:38 +0900 Subject: [PATCH 08/12] IntimateMerger Analytics Adapter : refactoring --- modules/imAnalyticsAdapter.js | 125 ++++++++++--------- test/spec/modules/imAnalyticsAdapter_spec.js | 2 + 2 files changed, 66 insertions(+), 61 deletions(-) diff --git a/modules/imAnalyticsAdapter.js b/modules/imAnalyticsAdapter.js index 117c71590c2..ee693a364a2 100644 --- a/modules/imAnalyticsAdapter.js +++ b/modules/imAnalyticsAdapter.js @@ -4,7 +4,7 @@ import adapterManager from '../src/adapterManager.js'; import { EVENTS } from '../src/constants.js'; import { sendBeacon } from '../src/ajax.js'; -const BID_WON_TIMEOUT = 800; // 0.8 second for initial batch +const DEFAULT_BID_WON_TIMEOUT = 800; // 0.8 second for initial batch const DEFAULT_CID = 5126; const API_BASE_URL = 'https://b6.im-apps.net/bids'; @@ -16,13 +16,10 @@ const EMPTY_CONSENT_DATA = { gdprApplies: undefined, gdpr: undefined, usp: undefined, - gpp: undefined }; const cache = { - auctions: {}, - wonBidsData: {}, - wonBidsTimer: null + auctions: {} }; /** @@ -34,15 +31,24 @@ function getCid(options) { return (options && options.cid) || DEFAULT_CID; } +/** + * Get Bid Won Timeout from adapter options + * @param {Object} options - Adapter options + * @returns {number} Timeout in ms or default value + */ +function getBidWonTimeout(options) { + return (options && options.bidWonTimeout) || DEFAULT_BID_WON_TIMEOUT; +} + /** * Build API URL with CID from options * @param {Object} options - Adapter options * @param {string} endpoint - Endpoint path * @returns {string} Full API URL */ -function buildApiUrlWithOptions(options, endpoint) { +function buildApiUrlWithOptions(options, endpoint, auctionId) { const cid = getCid(options); - return `${API_BASE_URL}/${cid}/${endpoint}`; + return `${API_BASE_URL}/${cid}/${endpoint}/${auctionId}`; } /** @@ -81,13 +87,11 @@ function getConsentData(bidderRequests) { const request = bidderRequests[0]; const gdprConsent = request.gdprConsent || {}; const uspConsent = request.uspConsent; - const gppConsent = request.gppConsent || {}; return { gdprApplies: gdprConsent.gdprApplies, gdpr: gdprConsent.consentString, - usp: uspConsent, - gpp: gppConsent.gppString + usp: uspConsent }; } @@ -108,17 +112,7 @@ function extractMetaFields(meta) { }; } -/** - * Mark auctions as sent - * @param {Array} auctionIds - Auction IDs to mark - */ -function markAuctionsAsSent(auctionIds) { - auctionIds.forEach(auctionId => { - if (cache.auctions[auctionId]) { - cache.auctions[auctionId].sendStatus |= WON_SENT; - } - }); -} + // IM Analytics Adapter implementation const imAnalyticsAdapter = Object.assign( @@ -144,11 +138,25 @@ const imAnalyticsAdapter = Object.assign( case EVENTS.AUCTION_END: logMessage('IM Analytics: AUCTION_END', args); - this.scheduleWonBidsSend(); + this.scheduleWonBidsSend(args.auctionId); break; } }, + /** + * Schedule won bids send for a specific auction + * @param {string} auctionId - Auction ID + */ + scheduleWonBidsSend(auctionId) { + const auction = cache.auctions[auctionId]; + if (auction) { + auction.wonBidsTimer = clearTimer(auction.wonBidsTimer); + auction.wonBidsTimer = setTimeout(() => { + this.sendWonBidsData(auctionId); + }, getBidWonTimeout(this.options)); + } + }, + /** * Handle auction init event * @param {Object} args - Auction arguments @@ -158,22 +166,15 @@ const imAnalyticsAdapter = Object.assign( cache.auctions[args.auctionId] = { consentData: consentData, - sendStatus: 0 + sendStatus: 0, + wonBids: [], + wonBidsTimer: null, + auctionInitTimestamp: args.timestamp }; this.handleAucInitData(args, consentData); }, - /** - * Schedule won bids send after timeout - */ - scheduleWonBidsSend() { - cache.wonBidsTimer = clearTimer(cache.wonBidsTimer); - cache.wonBidsTimer = setTimeout(() => { - this.sendWonBidsData(); - }, BID_WON_TIMEOUT); - }, - /** * Handle auction init data - send immediately for PV tracking * @param {Object} auctionArgs - Auction arguments @@ -184,11 +185,10 @@ const imAnalyticsAdapter = Object.assign( pageUrl: window.location.href, referrer: document.referrer || '', consentData, - timestamp: Date.now(), - auction: this.transformAucInitData(auctionArgs) + ...this.transformAucInitData(auctionArgs) }; - sendToApi(buildApiUrlWithOptions(this.options, 'pv'), payload); + sendToApi(buildApiUrlWithOptions(this.options, 'pv', auctionArgs.auctionId), payload); }, /** @@ -198,10 +198,7 @@ const imAnalyticsAdapter = Object.assign( */ transformAucInitData(auctionArgs) { return { - auctionId: auctionArgs.auctionId, - pv: auctionArgs.pv, timestamp: auctionArgs.timestamp, - adUnitCodes: auctionArgs.adUnitCodes || [], adUnitCount: (auctionArgs.adUnits || []).length }; }, @@ -232,12 +229,12 @@ const imAnalyticsAdapter = Object.assign( */ sendIndividualWonBid(auctionId, bidWonArgs, consentData) { const wonBid = this.transformWonBidsData(bidWonArgs); + const auction = cache.auctions[auctionId]; - sendToApi(buildApiUrlWithOptions(this.options, 'won'), { + sendToApi(buildApiUrlWithOptions(this.options, 'won', auctionId), { consentData: consentData || getConsentData(null), - wonBids: { - [auctionId]: [wonBid] - } + timestamp: auction.auctionInitTimestamp, + wonBids: [wonBid] }); }, @@ -247,10 +244,14 @@ const imAnalyticsAdapter = Object.assign( * @param {Object} bidWonArgs - Bid won arguments */ cacheWonBid(auctionId, bidWonArgs) { - if (!cache.wonBidsData[auctionId]) { - cache.wonBidsData[auctionId] = []; + const auction = cache.auctions[auctionId]; + if (auction) { + // Deduplicate based on requestId + if (auction.wonBids.some(bid => bid.requestId === bidWonArgs.requestId)) { + return; + } + auction.wonBids.push(this.transformWonBidsData(bidWonArgs)); } - cache.wonBidsData[auctionId].push(this.transformWonBidsData(bidWonArgs)); }, /** @@ -262,34 +263,36 @@ const imAnalyticsAdapter = Object.assign( const meta = bidWonArgs.meta || {}; return { - auctionId: bidWonArgs.auctionId, - timestamp: Date.now(), - bidder: bidWonArgs.bidder, + requestId: bidWonArgs.requestId, bidderCode: bidWonArgs.bidderCode, ...extractMetaFields(meta) }; }, + /** * Send accumulated won bids data to API - batch send after 800ms + * @param {string} auctionId - Auction ID to send data for */ - sendWonBidsData() { - if (Object.keys(cache.wonBidsData).length === 0) return; + sendWonBidsData(auctionId) { + const auction = cache.auctions[auctionId]; + if (!auction || !auction.wonBids || auction.wonBids.length === 0 || (auction.sendStatus & WON_SENT)) { + return; + } - const auctionIds = Object.keys(cache.wonBidsData); - const firstAuction = cache.auctions[auctionIds[0]]; - const consentData = firstAuction ? firstAuction.consentData : getConsentData(null); + const consentData = auction.consentData || getConsentData(null); + const timestamp = auction.auctionInitTimestamp || Date.now(); - sendToApi(buildApiUrlWithOptions(this.options, 'won'), { + sendToApi(buildApiUrlWithOptions(this.options, 'won', auctionId), { consentData, - wonBids: cache.wonBidsData + timestamp, + wonBids: auction.wonBids }); - markAuctionsAsSent(auctionIds); - - // Clear cache - cache.wonBidsData = {}; - cache.wonBidsTimer = null; + // Clear cached bids after sending to prevent duplicates + auction.wonBids = []; + auction.sendStatus |= WON_SENT; + auction.wonBidsTimer = null; } } ); diff --git a/test/spec/modules/imAnalyticsAdapter_spec.js b/test/spec/modules/imAnalyticsAdapter_spec.js index 739c1d92678..891a87313d9 100644 --- a/test/spec/modules/imAnalyticsAdapter_spec.js +++ b/test/spec/modules/imAnalyticsAdapter_spec.js @@ -166,6 +166,8 @@ describe('imAnalyticsAdapter', function() { expect(requests.length).to.equal(2); }); + + }); describe('AUCTION_END', function() { From a627e776df4f2eb6c8bd6433e813cd4de17cec23 Mon Sep 17 00:00:00 2001 From: eknis Date: Fri, 19 Dec 2025 11:52:51 +0900 Subject: [PATCH 09/12] IntimateMerger Analytics Adapter : update cache --- modules/imAnalyticsAdapter.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/modules/imAnalyticsAdapter.js b/modules/imAnalyticsAdapter.js index ee693a364a2..778ef4d66fa 100644 --- a/modules/imAnalyticsAdapter.js +++ b/modules/imAnalyticsAdapter.js @@ -213,11 +213,11 @@ const imAnalyticsAdapter = Object.assign( if (!auction) return; + this.cacheWonBid(auctionId, bidWonArgs); + // If initial batch has been sent, send immediately if (auction.sendStatus & WON_SENT) { this.sendIndividualWonBid(auctionId, bidWonArgs, auction.consentData); - } else { - this.cacheWonBid(auctionId, bidWonArgs); } }, @@ -290,7 +290,6 @@ const imAnalyticsAdapter = Object.assign( }); // Clear cached bids after sending to prevent duplicates - auction.wonBids = []; auction.sendStatus |= WON_SENT; auction.wonBidsTimer = null; } From 75c5bd7319a6a4ea82276f5f6e55bf19090544cb Mon Sep 17 00:00:00 2001 From: eknis Date: Fri, 19 Dec 2025 11:58:05 +0900 Subject: [PATCH 10/12] IntimateMerger Analytics Adapter : fix test --- test/spec/modules/imAnalyticsAdapter_spec.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/spec/modules/imAnalyticsAdapter_spec.js b/test/spec/modules/imAnalyticsAdapter_spec.js index 891a87313d9..739c1d92678 100644 --- a/test/spec/modules/imAnalyticsAdapter_spec.js +++ b/test/spec/modules/imAnalyticsAdapter_spec.js @@ -166,8 +166,6 @@ describe('imAnalyticsAdapter', function() { expect(requests.length).to.equal(2); }); - - }); describe('AUCTION_END', function() { From 7fe1ad887bedefd2b6e49deb3447feaa0492b55d Mon Sep 17 00:00:00 2001 From: eknis Date: Fri, 19 Dec 2025 12:49:54 +0900 Subject: [PATCH 11/12] IntimateMerger Analytics Adapter : fix space --- modules/imAnalyticsAdapter.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/modules/imAnalyticsAdapter.js b/modules/imAnalyticsAdapter.js index 778ef4d66fa..90104d5ffd2 100644 --- a/modules/imAnalyticsAdapter.js +++ b/modules/imAnalyticsAdapter.js @@ -112,8 +112,6 @@ function extractMetaFields(meta) { }; } - - // IM Analytics Adapter implementation const imAnalyticsAdapter = Object.assign( adapter({ analyticsType: 'endpoint' }), @@ -269,7 +267,6 @@ const imAnalyticsAdapter = Object.assign( }; }, - /** * Send accumulated won bids data to API - batch send after 800ms * @param {string} auctionId - Auction ID to send data for From e1ce4920e9e887f920fd81a42e1cb53b26809b4a Mon Sep 17 00:00:00 2001 From: eknis Date: Wed, 24 Dec 2025 17:22:29 +0900 Subject: [PATCH 12/12] IntimateMerger Analytics Adapter :add md --- modules/imAnalyticsAdapter.md | 55 +++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 modules/imAnalyticsAdapter.md diff --git a/modules/imAnalyticsAdapter.md b/modules/imAnalyticsAdapter.md new file mode 100644 index 00000000000..231c18d9631 --- /dev/null +++ b/modules/imAnalyticsAdapter.md @@ -0,0 +1,55 @@ +# Overview + +``` +Module Name: IM Analytics Adapter +Module Type: Analytics Adapter +``` + +# Description + +Analytics adapter for Intimate Merger platform. This adapter tracks auction events and bid won data for analytics purposes. + +The adapter monitors the following Prebid.js events: +- `AUCTION_INIT`: Tracks page views and auction initialization +- `BID_WON`: Tracks winning bids with metadata +- `AUCTION_END`: Triggers batch sending of won bids data + +# Configuration Options + +| Parameter | Type | Required | Default | Description | +|-----------|------|----------|---------|-------------| +| `cid` | number | No | 5126 | Client ID for API endpoint | +| `bidWonTimeout` | number | No | 800 | Timeout in milliseconds before sending batched won bids | + +# Example Configuration + +## Basic Configuration + +```javascript +pbjs.enableAnalytics({ + provider: 'imAnalytics' +}); +``` + +## Configuration with Custom CID + +```javascript +pbjs.enableAnalytics({ + provider: 'imAnalytics', + options: { + cid: 1234 + } +}); +``` + +## Configuration with Custom Timeout + +```javascript +pbjs.enableAnalytics({ + provider: 'imAnalytics', + options: { + cid: 1234, + bidWonTimeout: 1000 // 1 second + } +}); +```