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

144 lines (115 loc) 5.36 kB
import express from 'express'; import jsesc from 'jsesc'; import _ from 'lodash'; import { DisplayAd, DisplayAdResponse } from '../../../api/core/creative/index'; import { PluginProperty, PluginPropertyResponse } from '../../../api/core/plugin/PluginPropertyInterface'; import { BasePlugin, PropertiesWrapper } from '../../common/BasePlugin'; import { generateEncodedClickUrl } from '../utils/index'; import { AdRendererPluginResponse, AdRendererRequest, ClickUrlInfo } from './AdRendererInterface'; export class AdRendererBaseInstanceContext { properties: PropertiesWrapper; displayAd: DisplayAd; } export abstract class AdRendererBasePlugin<T extends AdRendererBaseInstanceContext> extends BasePlugin<T> { displayContextHeader = 'x-mics-display-context'; constructor(enableThrottling = false) { super(enableThrottling); this.initAdContentsRoute(); this.setErrorHandler(); } // Helper to fetch the Display Ad resource with caching async fetchDisplayAd(displayAdId: string, forceReload = false): Promise<DisplayAd> { const response = await super.requestGatewayHelper<DisplayAdResponse>( 'GET', `${this.outboundPlatformUrl}/v1/creatives/${displayAdId}`, undefined, { 'force-reload': forceReload }, ); this.logger.debug(`Fetched Creative: ${displayAdId} - ${JSON.stringify(response.data)}`); if (response.data.type !== 'DISPLAY_AD') { throw new Error(`crid: ${displayAdId} - When fetching DisplayAd, another creative type was returned!`); } return response.data; } // Helper to fetch the Display Ad properties resource with caching async fetchDisplayAdProperties(displayAdId: string, forceReload = false): Promise<PluginProperty[]> { const creativePropertyResponse = await super.requestGatewayHelper<PluginPropertyResponse>( 'GET', `${this.outboundPlatformUrl}/v1/creatives/${displayAdId}/renderer_properties`, undefined, { 'force-reload': forceReload }, ); this.logger.debug(`Fetched Creative Properties: ${displayAdId} - ${JSON.stringify(creativePropertyResponse.data)}`); return creativePropertyResponse.data; } // Method to build an instance context getEncodedClickUrl(redirectUrls: ClickUrlInfo[]) { return generateEncodedClickUrl(redirectUrls); } // To be overriden to get a custom behavior protected async instanceContextBuilder(creativeId: string, forceReload = false): Promise<T> { const displayAdP = this.fetchDisplayAd(creativeId, forceReload); const displayAdPropsP = this.fetchDisplayAdProperties(creativeId, forceReload); const results = await Promise.all([displayAdP, displayAdPropsP]); const displayAd = results[0]; const displayAdProps = results[1]; const context = { displayAd: displayAd, properties: new PropertiesWrapper(displayAdProps), } as T; return Promise.resolve(context); } protected abstract onAdContents(request: AdRendererRequest, instanceContext: T): Promise<AdRendererPluginResponse>; protected async getInstanceContext(creativeId: string, forceReload: boolean): Promise<T> { if (!this.pluginCache.get(creativeId) || forceReload) { void this.pluginCache.put( creativeId, this.instanceContextBuilder(creativeId, forceReload).catch((err) => { this.logger.error(`Error while caching instance context: ${(err as Error).message}`); this.pluginCache.del(creativeId); throw err; }), this.getInstanceContextCacheExpiration(), ); } return this.pluginCache.get(creativeId) as Promise<T>; } private initAdContentsRoute(): void { this.app.post( '/v1/ad_contents', this.asyncMiddleware(async (req: express.Request, res: express.Response) => { if (!this.httpIsReady()) { const msg = { error: 'Plugin not initialized', }; this.logger.error('POST /v1/ad_contents : %s', JSON.stringify(msg)); return res.status(500).json(msg); } else if (!req.body || _.isEmpty(req.body)) { const msg = { error: 'Missing request body', }; this.logger.error('POST /v1/ad_contents : %s', JSON.stringify(msg)); return res.status(500).json(msg); } else { this.logger.debug(`POST /v1/ad_contents ${JSON.stringify(req.body)}`); const adRendererRequest = req.body as AdRendererRequest; if (!this.onAdContents) { this.logger.error('POST /v1/ad_contents: No AdContents listener registered!'); const msg = { error: 'No AdContents listener registered!', }; return res.status(500).json(msg); } // We flush the Plugin Gateway cache during previews const forceReload = adRendererRequest.context === 'PREVIEW' || adRendererRequest.context === 'STAGE'; const instanceContext: T = await this.getInstanceContext(adRendererRequest.creative_id, forceReload); const adRendererResponse = await this.onAdContents(adRendererRequest, instanceContext); return res .header(this.displayContextHeader, jsesc(adRendererResponse.displayContext as string, { json: true })) .status(200) .send(adRendererResponse.html); } }), ); } }