@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
199 lines (171 loc) • 6.38 kB
text/typescript
import * as Handlebars from "handlebars";
import { TemplatingEngine } from "../../core/interfaces/mediarithmics/plugin/TemplatingEngineInterface";
import {
AdRendererBaseInstanceContext,
AdRendererRequest,
Creative,
ItemProposal,
AdRendererTemplateInstanceContext
} from "../../core/index";
const handlebars = require("handlebars");
const numeral = require("numeral");
const _ = require("lodash");
// Handlebar Context for URLs (not all macros are available)
export interface URLHandlebarsRootContext {
REQUEST: AdRendererRequest;
CREATIVE: HandlebarsRootContextCreative;
// Viewability TAGs specific
IAS_CLIENT_ID?: string;
// Main mediarithmics macros
ORGANISATION_ID: string;
AD_GROUP_ID?: string;
MEDIA_ID?: string;
ENCODED_MEDIA_ID?: string;
CAMPAIGN_ID?: string;
CREATIVE_ID: string;
CACHE_BUSTER: string;
CB: string;
}
// Handlebar Context for the Template - without recommandations
export interface HandlebarsRootContext extends URLHandlebarsRootContext {
ENCODED_CLICK_URL: string;
CLICK_URL: string;
ADDITIONAL_HTML?: string;
}
// Handlebar Context for the Template - with recommendations
export interface RecommendationsHandlebarsRootContext
extends HandlebarsRootContext {
private: {
redirectUrls: string[];
clickableContents: ClickableContent[];
};
RECOMMENDATIONS: ItemProposal[];
}
export interface ClickableContent {
item_id?: string;
catalog_token: any;
$content_id: number;
}
export interface HandlebarsRootContextCreative {
CLICK_URL?: string;
WIDTH: string;
HEIGHT: string;
}
function formatPrice(price: string, pattern: string) {
const number = numeral(price);
return number.format(pattern);
}
const encodeClickUrl = () => (redirectUrls: string[], clickUrl: string) => {
let urls = redirectUrls.slice(0);
urls.push(clickUrl);
return urls.reduceRight((acc: string, current: string) => {
return current + encodeURIComponent(acc);
}, "");
};
const placeHolder = "{{MICS_AD_CONTENT_ID}}";
const uriEncodePlaceHolder = encodeURI(placeHolder);
const doubleEncodedUriPlaceHolder = encodeURI(encodeURI(placeHolder));
// Encode recommendation click url => contains the contentId of the recommendation that will be
// insrted into the campaign log
const encodeRecoClickUrlHelper = () => (
idx: number,
rootContext: RecommendationsHandlebarsRootContext,
recommendation: ItemProposal
) => {
rootContext.private.clickableContents.push({
item_id: recommendation.$id,
catalog_token: recommendation.$catalog_token,
$content_id: idx
});
// recommendation.url replace placeHolder by idx
const filledRedirectUrls = rootContext.private.redirectUrls.map(
(url: string) => {
const url1 = _.replace(url, placeHolder, idx);
const url2 = _.replace(url1, uriEncodePlaceHolder, idx);
return _.replace(url2, doubleEncodedUriPlaceHolder, idx);
}
);
const recommendationUrl = recommendation.$url ? recommendation.$url : "";
return encodeClickUrl()(filledRedirectUrls, recommendationUrl);
};
export function buildURLHandlebarsRootContext(
adRenderRequest: AdRendererRequest,
instanceContext: AdRendererTemplateInstanceContext
): URLHandlebarsRootContext {
return {
REQUEST: adRenderRequest,
CREATIVE: {
CLICK_URL: instanceContext.creative_click_url,
WIDTH: instanceContext.width,
HEIGHT: instanceContext.height
},
ORGANISATION_ID: instanceContext.displayAd.organisation_id, // Hack, it should come from the AdRendererRequest
AD_GROUP_ID: adRenderRequest.ad_group_id,
MEDIA_ID: adRenderRequest.media_id,
ENCODED_MEDIA_ID: adRenderRequest.media_id ? encodeURIComponent(adRenderRequest.media_id) : undefined,
CAMPAIGN_ID: adRenderRequest.campaign_id,
CREATIVE_ID: adRenderRequest.creative_id,
CACHE_BUSTER: Date.now().toString(),
IAS_CLIENT_ID: instanceContext.ias_client_id,
CB: Date.now().toString()
}
}
export class HandlebarsEngine
implements TemplatingEngine<void, string, HandlebarsTemplateDelegate<any>> {
engine: typeof Handlebars;
// Initialisation of the engine. Done once at every InstanceContext rebuild.
init(): void {
this.engine = Handlebars.create();
/* Generic Helpers */
this.engine.registerHelper("formatPrice", formatPrice);
this.engine.registerHelper("toJson", (object: any) =>
JSON.stringify(object)
);
}
compile(template: string) {
return this.engine.compile(template);
}
constructor() {}
}
export class RecommendationsHandlebarsEngine extends HandlebarsEngine {
// Initialisation of the engine. Done once at every InstanceContext rebuild.
init(): void {
super.init();
/* URL Encoding witchcraft */
// We need to have 2 elements when doing the URL encoding:
// 1. The "click tracking" array passed in the rootContext (for click tracking)
// 2. The final URL (landing page, etc.) passed as a parameter of the helper
//
// In order to have both, we need to play smart and use an Handlebar partial
// This handlebar partial is just a way to add "@root" as a parameter of the helper before calling it
//
// This is how the encodeClickUrl partial should be used in templates:
// {{> encodeClickUrl url="http://www.mediarithmics.com/en/"}}
const encodeClickUrlPartial =
"{{encodeClickUrlInternal @root.private.redirectUrls url}}";
this.engine.registerPartial("encodeClickUrl", encodeClickUrlPartial);
this.engine.registerHelper("encodeClickUrlInternal", encodeClickUrl());
// Same story than previously but this time the partial will inject:
// @index -> the index of the recommendation, which is used to include it in the URL
// @root -> the root context
// this -> the recommendation item
// Warning, this partial should only be used in a {{#each recommendations}}{{/each}} block
// The $url field of the recommendation will be used as the final URL
//
// This is how the partial should be used in templates:
// {{> encodeRecoClickUrl}}
const encodeRecoClickUrlPartial =
"{{encodeRecoClickUrlInternal @index @root this}}";
this.engine.registerPartial(
"encodeRecoClickUrl",
encodeRecoClickUrlPartial
);
this.engine.registerHelper(
"encodeRecoClickUrlInternal",
encodeRecoClickUrlHelper()
);
}
constructor() {
super();
}
}