UNPKG

@mediarithmics/plugins-nodejs-sdk

Version:

This is the mediarithmics nodejs to help plugin developers bootstrapping their plugin without having to deal with most of the plugin boilerplate

210 lines (186 loc) 6.2 kB
import * as express from "express"; import * as _ from "lodash"; import * as cache from "memory-cache"; import { BasePlugin, PluginProperty, BidOptimizerBaseInstanceContext, BidOptimizer, BidOptimizerRequest, BidOptimizerPluginResponse, SaleCondition } from "../../../index"; export abstract class BidOptimizerPlugin extends BasePlugin { instanceContext: Promise<BidOptimizerBaseInstanceContext>; /** * * @param bidOptimizerId */ async fetchBidOptimizer(bidOptimizerId: string): Promise<BidOptimizer> { const bidOptimizerResponse = await super.requestGatewayHelper( "GET", `${this.outboundPlatformUrl}/v1/bid_optimizers/${bidOptimizerId}` ); this.logger.debug( `Fetched Bid Optimizer: ${bidOptimizerId} - ${JSON.stringify( bidOptimizerResponse.data )}` ); return bidOptimizerResponse.data; } /** * * @param bidOptimizerId */ async fetchBidOptimizerProperties( bidOptimizerId: string ): Promise<PluginProperty[]> { const bidOptimizerPropertyResponse = await super.requestGatewayHelper( "GET", `${this.outboundPlatformUrl}/v1/bid_optimizers/${ bidOptimizerId }/properties` ); this.logger.debug( `Fetched Creative Properties: ${bidOptimizerId} - ${JSON.stringify( bidOptimizerPropertyResponse.data )}` ); return bidOptimizerPropertyResponse.data; } findBestSalesConditions( bidPrice: number, salesConditions: SaleCondition[] ): SaleCondition { // Optimization, we only do the stringify if we are really on debug / silly mode if (this.logger.level === "debug" || this.logger.level === "silly") { this.logger.debug( `Looking to find the best sale condition for CPM: ${ bidPrice } in: ${JSON.stringify(salesConditions, null, 4)}` ); } const eligibleSalesConditions = salesConditions.filter(sc => { return sc.floor_price <= bidPrice; }); // Optimization, we only do the stringify if we are really on debug / silly mode if (this.logger.level === "debug" || this.logger.level === "silly") { this.logger.debug( `Found eligible sales condition for CPM: ${ bidPrice } in: ${JSON.stringify(eligibleSalesConditions, null, 4)}` ); } const sortedEligibleSalesConditions = eligibleSalesConditions.sort( (a, b) => { return a.floor_price - b.floor_price; } ); // Optimization, we only do the stringify if we are really on debug / silly mode if (this.logger.level === "debug" || this.logger.level === "silly") { this.logger.debug( `Sorted eligible sales condition for CPM: ${ bidPrice } in: ${JSON.stringify(sortedEligibleSalesConditions, null, 4)}` ); } return sortedEligibleSalesConditions[0]; } /** * Method to build an instance context * To be overriden to get a cutom behavior * This is a default provided implementation * @param bidOptimizerId */ protected async instanceContextBuilder( bidOptimizerId: string ): Promise<BidOptimizerBaseInstanceContext> { const bidOptimizerP = this.fetchBidOptimizer(bidOptimizerId); const bidOptimizerPropsP = this.fetchBidOptimizerProperties(bidOptimizerId); const results = await Promise.all([bidOptimizerP, bidOptimizerPropsP]); const bidOptimizer = results[0]; const bidOptimizerProps = results[1]; const context = { bidOptimizer: bidOptimizer, bidOptimizerProperties: bidOptimizerProps }; return context; } /** * * @param request * @param instanceContext */ protected abstract onBidDecisions( request: BidOptimizerRequest, instanceContext: BidOptimizerBaseInstanceContext ): Promise<BidOptimizerPluginResponse>; private initBidDecisions(): void { this.app.post( "/v1/bid_decisions", this.asyncMiddleware( async (req: express.Request, res: express.Response) => { if (!req.body || _.isEmpty(req.body)) { const msg = { error: "Missing request body" }; this.logger.error( "POST /v1/bid_decisions : %s", JSON.stringify(msg) ); return res.status(500).json(msg); } else { if ( this.logger.level === "debug" || this.logger.level === "silly" ) { this.logger.debug( `POST /v1/bid_decisions ${JSON.stringify(req.body)}` ); } const bidOptimizerRequest = req.body as BidOptimizerRequest; if (!this.onBidDecisions) { const errMsg = "No BidOptimizer listener registered!"; this.logger.error(errMsg); return res.status(500).json({ error: errMsg }); } if ( !this.pluginCache.get( bidOptimizerRequest.campaign_info.bid_optimizer_id ) ) { this.pluginCache.put( bidOptimizerRequest.campaign_info.bid_optimizer_id, this.instanceContextBuilder( bidOptimizerRequest.campaign_info.bid_optimizer_id ), this.INSTANCE_CONTEXT_CACHE_EXPIRATION ); } // We init the specific route to listen for bid decisions requests const instanceContext = await this.pluginCache.get( bidOptimizerRequest.campaign_info.bid_optimizer_id ); const bidOptimizerResponse = await this.onBidDecisions( bidOptimizerRequest, instanceContext ); if ( this.logger.level === "debug" || this.logger.level === "silly" ) { this.logger.debug( `Returning: ${JSON.stringify(bidOptimizerResponse)}` ); } return res.status(200).send(JSON.stringify(bidOptimizerResponse)); } } ) ); } constructor() { super(); this.initBidDecisions(); this.setErrorHandler(); } }