github.com/prebid/prebid-server/v2@v2.18.0/exchange/events.go (about)

     1  package exchange
     2  
     3  import (
     4  	"time"
     5  
     6  	"github.com/prebid/prebid-server/v2/exchange/entities"
     7  	jsonpatch "gopkg.in/evanphx/json-patch.v4"
     8  
     9  	"github.com/prebid/prebid-server/v2/analytics"
    10  	"github.com/prebid/prebid-server/v2/config"
    11  	"github.com/prebid/prebid-server/v2/endpoints/events"
    12  	"github.com/prebid/prebid-server/v2/openrtb_ext"
    13  	"github.com/prebid/prebid-server/v2/util/jsonutil"
    14  )
    15  
    16  // eventTracking has configuration fields needed for adding event tracking to an auction response
    17  type eventTracking struct {
    18  	accountID          string
    19  	enabledForAccount  bool
    20  	enabledForRequest  bool
    21  	auctionTimestampMs int64
    22  	integrationType    string
    23  	bidderInfos        config.BidderInfos
    24  	externalURL        string
    25  }
    26  
    27  // getEventTracking creates an eventTracking object from the different configuration sources
    28  func getEventTracking(requestExtPrebid *openrtb_ext.ExtRequestPrebid, ts time.Time, account *config.Account, bidderInfos config.BidderInfos, externalURL string) *eventTracking {
    29  	return &eventTracking{
    30  		accountID:          account.ID,
    31  		enabledForAccount:  account.Events.Enabled,
    32  		enabledForRequest:  requestExtPrebid != nil && requestExtPrebid.Events != nil,
    33  		auctionTimestampMs: ts.UnixNano() / 1e+6,
    34  		integrationType:    getIntegrationType(requestExtPrebid),
    35  		bidderInfos:        bidderInfos,
    36  		externalURL:        externalURL,
    37  	}
    38  }
    39  
    40  func getIntegrationType(requestExtPrebid *openrtb_ext.ExtRequestPrebid) string {
    41  	if requestExtPrebid != nil {
    42  		return requestExtPrebid.Integration
    43  	}
    44  	return ""
    45  }
    46  
    47  // modifyBidsForEvents adds bidEvents and modifies VAST AdM if necessary.
    48  func (ev *eventTracking) modifyBidsForEvents(seatBids map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid) map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid {
    49  	for bidderName, seatBid := range seatBids {
    50  		modifyingVastXMLAllowed := ev.isModifyingVASTXMLAllowed(bidderName.String())
    51  		for _, pbsBid := range seatBid.Bids {
    52  			if modifyingVastXMLAllowed {
    53  				ev.modifyBidVAST(pbsBid, bidderName)
    54  			}
    55  			pbsBid.BidEvents = ev.makeBidExtEvents(pbsBid, bidderName)
    56  		}
    57  	}
    58  	return seatBids
    59  }
    60  
    61  // isModifyingVASTXMLAllowed returns true if this bidder config allows modifying VAST XML for event tracking
    62  func (ev *eventTracking) isModifyingVASTXMLAllowed(bidderName string) bool {
    63  	return ev.bidderInfos[bidderName].ModifyingVastXmlAllowed && ev.isEventAllowed()
    64  }
    65  
    66  // modifyBidVAST injects event Impression url if needed, otherwise returns original VAST string
    67  func (ev *eventTracking) modifyBidVAST(pbsBid *entities.PbsOrtbBid, bidderName openrtb_ext.BidderName) {
    68  	bid := pbsBid.Bid
    69  	if pbsBid.BidType != openrtb_ext.BidTypeVideo || len(bid.AdM) == 0 && len(bid.NURL) == 0 {
    70  		return
    71  	}
    72  	vastXML := makeVAST(bid)
    73  	bidID := bid.ID
    74  	if len(pbsBid.GeneratedBidID) > 0 {
    75  		bidID = pbsBid.GeneratedBidID
    76  	}
    77  	if newVastXML, ok := events.ModifyVastXmlString(ev.externalURL, vastXML, bidID, bidderName.String(), ev.accountID, ev.auctionTimestampMs, ev.integrationType); ok {
    78  		bid.AdM = newVastXML
    79  	}
    80  }
    81  
    82  // modifyBidJSON injects "wurl" (win) event url if needed, otherwise returns original json
    83  func (ev *eventTracking) modifyBidJSON(pbsBid *entities.PbsOrtbBid, bidderName openrtb_ext.BidderName, jsonBytes []byte) ([]byte, error) {
    84  	if !ev.isEventAllowed() || pbsBid.BidType == openrtb_ext.BidTypeVideo {
    85  		return jsonBytes, nil
    86  	}
    87  	var winEventURL string
    88  	if pbsBid.BidEvents != nil { // All bids should have already been updated with win/imp event URLs
    89  		winEventURL = pbsBid.BidEvents.Win
    90  	} else {
    91  		winEventURL = ev.makeEventURL(analytics.Win, pbsBid, bidderName)
    92  	}
    93  	// wurl attribute is not in the schema, so we have to patch
    94  	patch, err := jsonutil.Marshal(map[string]string{"wurl": winEventURL})
    95  	if err != nil {
    96  		return jsonBytes, err
    97  	}
    98  	modifiedJSON, err := jsonpatch.MergePatch(jsonBytes, patch)
    99  	if err != nil {
   100  		return jsonBytes, err
   101  	}
   102  	return modifiedJSON, nil
   103  }
   104  
   105  // makeBidExtEvents make the data for bid.ext.prebid.events if needed, otherwise returns nil
   106  func (ev *eventTracking) makeBidExtEvents(pbsBid *entities.PbsOrtbBid, bidderName openrtb_ext.BidderName) *openrtb_ext.ExtBidPrebidEvents {
   107  	if !ev.isEventAllowed() || pbsBid.BidType == openrtb_ext.BidTypeVideo {
   108  		return nil
   109  	}
   110  	return &openrtb_ext.ExtBidPrebidEvents{
   111  		Win: ev.makeEventURL(analytics.Win, pbsBid, bidderName),
   112  		Imp: ev.makeEventURL(analytics.Imp, pbsBid, bidderName),
   113  	}
   114  }
   115  
   116  // makeEventURL returns an analytics event url for the requested type (win or imp)
   117  func (ev *eventTracking) makeEventURL(evType analytics.EventType, pbsBid *entities.PbsOrtbBid, bidderName openrtb_ext.BidderName) string {
   118  	bidId := pbsBid.Bid.ID
   119  	if len(pbsBid.GeneratedBidID) > 0 {
   120  		bidId = pbsBid.GeneratedBidID
   121  	}
   122  	return events.EventRequestToUrl(ev.externalURL,
   123  		&analytics.EventRequest{
   124  			Type:        evType,
   125  			BidID:       bidId,
   126  			Bidder:      string(bidderName),
   127  			AccountID:   ev.accountID,
   128  			Timestamp:   ev.auctionTimestampMs,
   129  			Integration: ev.integrationType,
   130  		})
   131  }
   132  
   133  // isEventAllowed checks if events are enabled by default or on account/request level
   134  func (ev *eventTracking) isEventAllowed() bool {
   135  	return ev.enabledForAccount || ev.enabledForRequest
   136  }