github.com/prebid/prebid-server/v2@v2.18.0/adapters/liftoff/liftoff.go (about) 1 package liftoff 2 3 import ( 4 "encoding/json" 5 "errors" 6 "fmt" 7 "net/http" 8 "strings" 9 10 "github.com/prebid/openrtb/v20/openrtb2" 11 "github.com/prebid/prebid-server/v2/adapters" 12 "github.com/prebid/prebid-server/v2/config" 13 "github.com/prebid/prebid-server/v2/openrtb_ext" 14 ) 15 16 const SupportedCurrency = "USD" 17 18 type adapter struct { 19 Endpoint string 20 } 21 22 type liftoffImpressionExt struct { 23 *adapters.ExtImpBidder 24 // Ext represents the vungle extension. 25 Ext openrtb_ext.ImpExtLiftoff `json:"vungle"` 26 } 27 28 // Builder builds a new instance of the Liftoff adapter for the given bidder with the given config. 29 func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { 30 return &adapter{Endpoint: config.Endpoint}, nil 31 } 32 33 // MakeRequests split impressions into bid requests and change them into the form that liftoff can handle. 34 func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { 35 var requests []*adapters.RequestData 36 var errs []error 37 requestCopy := *request 38 for _, imp := range request.Imp { 39 // Check if imp comes with bid floor amount defined in a foreign currency 40 if imp.BidFloor > 0 && imp.BidFloorCur != "" && strings.ToUpper(imp.BidFloorCur) != SupportedCurrency { 41 // Convert to US dollars 42 convertedValue, err := requestInfo.ConvertCurrency(imp.BidFloor, imp.BidFloorCur, SupportedCurrency) 43 if err != nil { 44 errs = append(errs, fmt.Errorf("failed to convert currency (err)%s", err.Error())) 45 continue 46 } 47 48 // Update after conversion. All imp elements inside request.Imp are shallow copies 49 // therefore, their non-pointer values are not shared memory and are safe to modify. 50 imp.BidFloorCur = SupportedCurrency 51 imp.BidFloor = convertedValue 52 } 53 54 var impExt liftoffImpressionExt 55 if err := json.Unmarshal(imp.Ext, &impExt); err != nil { 56 errs = append(errs, fmt.Errorf("failed unmarshalling imp ext (err)%s", err.Error())) 57 continue 58 } 59 60 // get placement_reference_id & pub_app_store_id 61 var bidderImpExt openrtb_ext.ImpExtLiftoff 62 if err := json.Unmarshal(impExt.Bidder, &bidderImpExt); err != nil { 63 errs = append(errs, fmt.Errorf("failed unmarshalling bidder imp ext (err)%s", err.Error())) 64 continue 65 } 66 67 bidderImpExt.BidToken = requestCopy.User.BuyerUID 68 impExt.Ext = bidderImpExt 69 if newImpExt, err := json.Marshal(impExt); err == nil { 70 imp.Ext = newImpExt 71 } else { 72 errs = append(errs, errors.New("failed re-marshalling imp ext")) 73 continue 74 } 75 76 imp.TagID = bidderImpExt.PlacementRefID 77 requestCopy.Imp = []openrtb2.Imp{imp} 78 // must make a shallow copy for pointers. 79 // If it is site object, need to construct an app with pub_store_id. 80 var requestAppCopy openrtb2.App 81 if request.App != nil { 82 requestAppCopy = *request.App 83 requestAppCopy.ID = bidderImpExt.PubAppStoreID 84 } else if request.Site != nil { 85 requestCopy.Site = nil 86 requestAppCopy = openrtb2.App{ 87 ID: bidderImpExt.PubAppStoreID, 88 } 89 } else { 90 errs = append(errs, errors.New("failed constructing app, must have app or site object in bid request")) 91 continue 92 } 93 94 requestCopy.App = &requestAppCopy 95 requestJSON, err := json.Marshal(requestCopy) 96 if err != nil { 97 errs = append(errs, err) 98 continue 99 } 100 101 requestData := &adapters.RequestData{ 102 Method: "POST", 103 Uri: a.Endpoint, 104 Body: requestJSON, 105 Headers: http.Header{ 106 "Content-Type": []string{"application/json"}, 107 "Accept": []string{"application/json"}, 108 "X-OpenRTB-Version": []string{"2.5"}, 109 }, 110 ImpIDs: openrtb_ext.GetImpIDs(requestCopy.Imp), 111 } 112 113 requests = append(requests, requestData) 114 } 115 116 return requests, errs 117 } 118 119 // MakeBids collect bid response from liftoff and change them into the form that Prebid Server can handle. 120 func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) { 121 if adapters.IsResponseStatusCodeNoContent(responseData) { 122 return nil, nil 123 } 124 125 if err := adapters.CheckResponseStatusCodeForErrors(responseData); err != nil { 126 return nil, []error{err} 127 } 128 129 var response openrtb2.BidResponse 130 if err := json.Unmarshal(responseData.Body, &response); err != nil { 131 return nil, []error{err} 132 } 133 134 var errs []error 135 bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(request.Imp)) 136 bidResponse.Currency = response.Cur 137 for _, seatBid := range response.SeatBid { 138 for i := range seatBid.Bid { 139 b := &adapters.TypedBid{ 140 Bid: &seatBid.Bid[i], 141 BidType: openrtb_ext.BidTypeVideo, 142 Seat: openrtb_ext.BidderName(seatBid.Seat), 143 } 144 145 bidResponse.Bids = append(bidResponse.Bids, b) 146 } 147 } 148 149 return bidResponse, errs 150 }