forked from prebid/Prebid.js
-
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
Open
eknis
wants to merge
12
commits into
master
Choose a base branch
from
im-analytics-adapter
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
e73e918
IntimateMerger Analytics Adapter : initial release
eknis 280a6d6
IntimateMerger Analytics Adapter : sendBeacon
eknis 0103771
IntimateMerger Analytics Adapter : refactoring
eknis 92606c4
IntimateMerger Analytics Adapter : fix sendBeacon
eknis 0a7a285
IntimateMerger Analytics Adapter : update comment
eknis 8d67535
IntimateMerger Analytics Adapter : refactoring
eknis 18de1b7
IntimateMerger Analytics Adapter : add test
eknis 6042b68
IntimateMerger Analytics Adapter : refactoring
eknis a627e77
IntimateMerger Analytics Adapter : update cache
eknis 75c5bd7
IntimateMerger Analytics Adapter : fix test
eknis 7fe1ad8
IntimateMerger Analytics Adapter : fix space
eknis e1ce492
IntimateMerger Analytics Adapter :add md
eknis 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,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; | ||
| } | ||
|
|
||
| const request = bidderRequests[0]; | ||
| const gdprConsent = request.gdprConsent || {}; | ||
| const uspConsent = request.uspConsent; | ||
|
|
||
| return { | ||
| gdprApplies: gdprConsent.gdprApplies, | ||
| gdpr: gdprConsent.consentString, | ||
| usp: uspConsent | ||
| }; | ||
| } | ||
|
|
||
| /** | ||
| * 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); | ||
| 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) { | ||
| const payload = { | ||
| pageUrl: window.location.href, | ||
| referrer: document.referrer || '', | ||
| 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, | ||
| 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); | ||
| } | ||
| }, | ||
|
|
||
| /** | ||
| * 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)) { | ||
| return; | ||
| } | ||
|
|
||
| const consentData = auction.consentData || getConsentData(null); | ||
| const timestamp = auction.auctionInitTimestamp || Date.now(); | ||
|
|
||
| sendToApi(buildApiUrlWithOptions(this.options, 'won', auctionId), { | ||
| consentData, | ||
| timestamp, | ||
| wonBids: auction.wonBids | ||
| }); | ||
|
|
||
| // Clear cached bids after sending to prevent duplicates | ||
| auction.sendStatus |= WON_SENT; | ||
| 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; | ||
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,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 | ||
| } | ||
| }); | ||
| ``` |
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.
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.
後続処理考えると、送るデータは全部一緒 sendWonBidsData() で送って頂くのが楽ちんかなと思います。
auctionId, timestamp で SORTして最新のものを表示されたデータとして処理できるので!