Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
308 changes: 308 additions & 0 deletions modules/imAnalyticsAdapter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,308 @@
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 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';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(完全に趣味ですが)Prebid にあわせて、bid にしませんか?


// Send status flags
const WON_SENT = 1;

// Default values
const EMPTY_CONSENT_DATA = {
gdprApplies: undefined,
gdpr: undefined,
usp: undefined,
};

const cache = {
auctions: {}
};

/**
* 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;
}

/**
* 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, auctionId) {
const cid = getCid(options);
return `${API_BASE_URL}/${cid}/${endpoint}/${auctionId}`;
}

/**
* Send data to API endpoint using sendBeacon
* @param {string} url - API endpoint URL
* @param {Object} payload - Data to send
*/
function sendToApi(url, payload) {
const data = JSON.stringify(payload);
const blob = new Blob([data], { type: 'application/json' });
sendBeacon(url, blob);
}

/**
* Clear timer if exists
* @param {number|null} timer - Timer ID
* @returns {null}
*/
function clearTimer(timer) {
if (timer) {
clearTimeout(timer);
}
return null;
}

/**
* Get consent data from bidder requests
* @param {Array} bidderRequests - Bidder requests array
* @returns {Object} Consent data object
*/
function getConsentData(bidderRequests) {
if (!bidderRequests || !bidderRequests[0]) {
return EMPTY_CONSENT_DATA;
}

Comment on lines +82 to +86
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bidderRequests から取るより、Adapter から取得した方が分かりやすいかなと思ったのですが、どっちがいいっすかね・・?(0番目から取るよりという意味で。他のAnalytics Adapter はどうですか?)

const request = bidderRequests[0];
const gdprConsent = request.gdprConsent || {};
const uspConsent = request.uspConsent;

return {
Copy link
Member

@fecker fecker Mar 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

アメリカ・カナダがそこそこ入っていたので、
ggp: gppDataHandler.getConsentData().applicableSections; (Number が入ります)
ggpString: gppDataHandler.getConsentData().gppString;
で取得をお願いします。
(真面目にやるとここを見るのが良さげ?)

gdprApplies: gdprConsent.gdprApplies,
gdpr: gdprConsent.consentString,
Comment on lines +92 to +93
Copy link
Member

@fecker fecker Mar 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

gdpr の consent はいらないかなと思ってきました・・・・(対応するつもりがほぼない)
他の adapter でやってたのですが gdpr に true / false (gdprApplies) を渡して、あげるのが幸せになれるかと思いました。

usp: uspConsent
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

coppa も念の為取得しておいて頂けるとうれしいです。
coppa = Number(coppaDataHandler.getCoppa());

};
}

/**
* Extract meta fields from bid won arguments
* @param {Object} meta - Meta object
* @returns {Object} Extracted meta fields
*/
function extractMetaFields(meta) {
return {
advertiserDomains: meta.advertiserDomains || [],
primaryCatId: meta.primaryCatId || '',
secondaryCatIds: meta.secondaryCatIds || [],
advertiserName: meta.advertiserName || '',
advertiserId: meta.advertiserId || '',
brandName: meta.brandName || '',
brandId: meta.brandId || ''
};
}

// 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.handleWonBidsData(args);
break;

case EVENTS.AUCTION_END:
logMessage('IM Analytics: AUCTION_END', args);
this.scheduleWonBidsSend(args.auctionId);
Copy link
Member

@fecker fecker Mar 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

細かいのですが、handleActionEnd() の方が良いのかなと思ったり。
で、その中で 2つの処理を schedule するのが良いのでは?って思いました。

別のAdapterでやってたのですが、
(あまりユースケースはないかもですが、)Cache の肥大化対策をしてるものがいました。

一定時間後(5秒 〜 10秒後)に、cache[auctions] をクリアする処理があってもいいかもす。

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
*/
handleAuctionInit(args) {
const consentData = getConsentData(args.bidderRequests);

cache.auctions[args.auctionId] = {
consentData: consentData,
sendStatus: 0,
wonBids: [],
wonBidsTimer: null,
auctionInitTimestamp: args.timestamp
};

this.handleAucInitData(args, consentData);
},

/**
* Handle auction init data - send immediately for PV tracking
* @param {Object} auctionArgs - Auction arguments
* @param {Object} consentData - Consent data object
*/
handleAucInitData(auctionArgs, consentData) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

consentData って、auction の中でキャッシュされてるので、それを使いまわした方が良いのではと思ったのですが、どうすか?

handleAuction で、引数があわないのが気持ちわるい感じがします。

const payload = {
pageUrl: window.location.href,
referrer: document.referrer || '',
Comment on lines +183 to +184
Copy link
Member

@fecker fecker Mar 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

細かいですが、url とか短くしちゃって良いかなと。少しでもネットワークコストを下げる的な。
(まぁ、payload なので圧縮かかるかなと思いつつ)

consentData,
...this.transformAucInitData(auctionArgs)
};

sendToApi(buildApiUrlWithOptions(this.options, 'pv', auctionArgs.auctionId), payload);
},

/**
* Transform auction data for auction init event
* @param {Object} auctionArgs - Auction arguments
* @returns {Object} Transformed auction data
*/
transformAucInitData(auctionArgs) {
return {
timestamp: auctionArgs.timestamp,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

細かいですが、ボリュームが増えるので、short にした方がエコかもです。
(僕なら ts とかにしちゃいます w)

adUnitCount: (auctionArgs.adUnits || []).length
};
},

/**
* Handle won bids data - batch first, then individual
* @param {Object} bidWonArgs - Bid won arguments
*/
handleWonBidsData(bidWonArgs) {
const auctionId = bidWonArgs.auctionId;
const auction = cache.auctions[auctionId];

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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

後続処理考えると、送るデータは全部一緒 sendWonBidsData() で送って頂くのが楽ちんかなと思います。

auctionId, timestamp で SORTして最新のものを表示されたデータとして処理できるので!

}
},

/**
* Send individual won bid immediately
* @param {string} auctionId - Auction ID
* @param {Object} bidWonArgs - Bid won arguments
* @param {Object} consentData - Consent data
*/
sendIndividualWonBid(auctionId, bidWonArgs, consentData) {
const wonBid = this.transformWonBidsData(bidWonArgs);
const auction = cache.auctions[auctionId];

sendToApi(buildApiUrlWithOptions(this.options, 'won', auctionId), {
consentData: consentData || getConsentData(null),
timestamp: auction.auctionInitTimestamp,
wonBids: [wonBid]
});
},

/**
* Cache won bid for batch send
* @param {string} auctionId - Auction ID
* @param {Object} bidWonArgs - Bid won arguments
*/
cacheWonBid(auctionId, bidWonArgs) {
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));
}
},

/**
* Transform bid won data for payload
* @param {Object} bidWonArgs - Bid won arguments
* @returns {Object} Transformed bid won data
*/
transformWonBidsData(bidWonArgs) {
const meta = bidWonArgs.meta || {};

return {
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(auctionId) {
const auction = cache.auctions[auctionId];
if (!auction || !auction.wonBids || auction.wonBids.length === 0 || (auction.sendStatus & WON_SENT)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

全部一緒に送ることを考えると最後の条件はいらないかなと。
(送れてきても、全部一緒に送ることで集計が楽になるはず)の

return;
}

const consentData = auction.consentData || getConsentData(null);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cache からとるだけで大丈夫なんじゃないかなって思いました。

const timestamp = auction.auctionInitTimestamp || Date.now();

sendToApi(buildApiUrlWithOptions(this.options, 'won', auctionId), {
consentData,
timestamp,
wonBids: auction.wonBids
Comment on lines +285 to +286
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

少しでも小さくと・・・ ts, bids あたりで良いかもです。

});

// Clear cached bids after sending to prevent duplicates
auction.sendStatus |= WON_SENT;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

細かいですが、sendToApi する前に、セットした方が良かったりしますか?
(時間がかかったたときに微妙なので)の

auction.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;
55 changes: 55 additions & 0 deletions modules/imAnalyticsAdapter.md
Original file line number Diff line number Diff line change
@@ -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
}
});
```
Loading
Loading