-
Notifications
You must be signed in to change notification settings - Fork 0
IntimateMerger Analytics Adapter : initial release #21
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
base: master
Are you sure you want to change the base?
Changes from all commits
e73e918
280a6d6
0103771
92606c4
0a7a285
8d67535
18de1b7
6042b68
a627e77
75c5bd7
7fe1ad8
e1ce492
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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'; | ||
|
|
||
| // 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
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. アメリカ・カナダがそこそこ入っていたので、 |
||
| gdprApplies: gdprConsent.gdprApplies, | ||
| gdpr: gdprConsent.consentString, | ||
|
Comment on lines
+92
to
+93
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. gdpr の consent はいらないかなと思ってきました・・・・(対応するつもりがほぼない) |
||
| usp: uspConsent | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. coppa も念の為取得しておいて頂けるとうれしいです。 |
||
| }; | ||
| } | ||
|
|
||
| /** | ||
| * 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); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 細かいのですが、handleActionEnd() の方が良いのかなと思ったり。 別のAdapterでやってたのですが、 一定時間後(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) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 細かいですが、url とか短くしちゃって良いかなと。少しでもネットワークコストを下げる的な。 |
||
| 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, | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 細かいですが、ボリュームが増えるので、short にした方がエコかもです。 |
||
| 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); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) { | ||
eknis marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| 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) { | ||
eknis marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| 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)) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 全部一緒に送ることを考えると最後の条件はいらないかなと。 |
||
| return; | ||
| } | ||
|
|
||
| const consentData = auction.consentData || getConsentData(null); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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; | ||
| 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 | ||
| } | ||
| }); | ||
| ``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(完全に趣味ですが)Prebid にあわせて、bid にしませんか?