@swrve/smarttv-sdk
Version:
Swrve marketing engagement platform SDK for SmartTV OTT devices
561 lines (560 loc) • 29.7 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.CampaignManager = void 0;
const StorageManager_1 = require("../Storage/StorageManager");
const SwrveConstants_1 = require("../utils/SwrveConstants");
const SwrveMessageDisplayManager_1 = require("./Messages/SwrveMessageDisplayManager");
const AssetManager_1 = require("./AssetManager");
const SwrveLogger_1 = __importDefault(require("../utils/SwrveLogger"));
const SwrveButton_1 = require("../UIElements/SwrveButton");
const SwrveImage_1 = require("../UIElements/SwrveImage");
const DateHelper_1 = __importDefault(require("../utils/DateHelper"));
class CampaignManager {
constructor(profileManager, platform, config, resourceManager, personalizationProperties) {
this.profileManager = profileManager;
this.campaigns = [];
this.campaignState = {};
this.onMessageListener = null;
this.onEmbeddedListener = null;
//global rules
this._maxMessagesPerSession = 99999;
this._minDelay = 55;
this._delayFirstMessage = 150;
this.MAX_MESSAGES_PER_SESSION = 99999;
this.MIN_DELAY = 55;
this.DELAY_FIRST_MESSAGE = 150;
//session counter
this.messagesShownCount = 0;
this.lastShownMessageTime = 0;
this.assetManager = new AssetManager_1.AssetManager();
this.loadStoredCampaigns(this.profileManager.currentUser.userId);
this.messageDisplayManager = new SwrveMessageDisplayManager_1.SwrveMessageDisplayManager(platform, config, resourceManager);
this.platform = platform;
}
storeCampaigns(response, personalizationProperties, onAssetsLoaded) {
if (response === undefined) {
return;
}
if (response.campaigns) {
this.handleCDN(response);
if (response.campaigns.campaigns) {
this.campaigns = this.filterUnsupportedCampaigns(response);
}
else {
this.campaigns = [];
}
this.parseGlobalRules(response.campaigns);
this.synchronizeCampaignState();
this.handleAssets(personalizationProperties, onAssetsLoaded);
StorageManager_1.StorageManager.saveData(SwrveConstants_1.CAMPAIGNS + this.profileManager.currentUser.userId, JSON.stringify(response));
}
}
refreshAssets(personalizationProperties, onAssetsLoaded) {
this.handleAssets(personalizationProperties, onAssetsLoaded);
}
resetCampaignState(clearCampaigns = true) {
if (clearCampaigns) {
this.campaigns = [];
}
this.campaignState = {};
this.messagesShownCount = 0;
this.lastShownMessageTime = 0;
}
//this is called when the SDK is initialized and when the user is switched
loadStoredCampaigns(userId) {
SwrveLogger_1.default.debug("Load stored campaigns: " + userId);
// don't clear campaigns array, they will be replaced below, if in storage for user.
this.resetCampaignState(false);
const storedCampaigns = StorageManager_1.StorageManager.getData(SwrveConstants_1.CAMPAIGNS + userId);
if (storedCampaigns) {
const response = JSON.parse(storedCampaigns);
//check it has expected structure
if (response && response.campaigns && response.campaigns.campaigns !== undefined) {
this.handleCDN(response);
if (response.campaigns.campaigns) {
this.campaigns = this.filterUnsupportedCampaigns(response);
}
else {
this.campaigns = [];
}
if (this.campaigns) {
this.parseGlobalRules(response.campaigns);
}
}
else {
SwrveLogger_1.default.debug("Campaign JSON empty, no campaigns downloaded");
}
}
else {
SwrveLogger_1.default.debug("Unable to load stored campaigns");
}
const campaignState = StorageManager_1.StorageManager.getData(SwrveConstants_1.CAMPAIGN_STATE + userId);
if (campaignState) {
this.campaignState = JSON.parse(campaignState);
}
}
synchronizeCampaignState() {
// only keep state for campaigns that are sent down
const origCampaignState = this.campaignState;
this.campaignState = {};
this.campaigns.forEach(({ id }) => {
// ensure new campaigns have a default state
this.campaignState[id] = origCampaignState[id] || {
status: SwrveConstants_1.SWRVE_CAMPAIGN_STATUS_UNSEEN,
impressions: 0,
next: 0,
lastShownTime: 0,
};
});
StorageManager_1.StorageManager.saveData(SwrveConstants_1.CAMPAIGN_STATE + this.profileManager.currentUser.userId, JSON.stringify(this.campaignState));
}
getCampaignIDs() {
return this.campaigns
.map(campaign => ({
id: campaign.id,
type: campaign.messages ? "iam" : "unknown",
variant_id: this.getCampaignVariantID(campaign),
}));
}
getCampaignVariantID(campaign) {
return campaign.messages && campaign.messages[0] ? campaign.messages[0].id : 0;
}
getSentPageViewEvents() {
return this.messageDisplayManager.getSentPageViewEvents();
}
getSentNavigationEvents() {
return this.messageDisplayManager.getSentNavigationEvents();
}
getCampaignState(campaignId) {
return this.campaignState[campaignId];
}
onEmbeddedMessage(onEmbeddedMessageListener) {
this.onEmbeddedListener = onEmbeddedMessageListener;
}
onMessage(onMessageListener) {
this.onMessageListener = onMessageListener;
}
onPageViewed(callback) {
this.messageDisplayManager.onPageViewed(callback);
}
onButtonClicked(callback) {
this.messageDisplayManager.onButtonClicked(callback);
}
onBackButtonClicked(callback) {
this.messageDisplayManager.onBackButtonClicked(callback);
}
showCampaign(campaign, personalizationProperties, impressionCallback) {
if (campaign.messages && campaign.messages.length > 0) {
const message = campaign.messages[0];
message.parentCampaign = campaign.id;
this.messageDisplayManager.showMessage(campaign.messages[0], campaign, this.assetManager.ImagesCDN, this.platform, personalizationProperties);
if (impressionCallback) {
impressionCallback(message);
}
this.updateCampaignState(message);
return true;
}
else if (campaign.embedded_message && campaign.embedded_message.data) {
const embeddedMessage = campaign.embedded_message;
embeddedMessage.parentCampaign = campaign.id;
if (this.onEmbeddedListener) {
this.onEmbeddedListener(campaign.embedded_message, personalizationProperties);
}
return true;
}
else {
return false;
}
}
checkTriggers(triggerName, payload, impressionCallback, qa = false, personalizationProperties) {
const matchingMessages = [];
let globalStatus = this.applyGlobalRules(triggerName);
let campaignStatus;
const campaignStatuses = [];
function logCampaignTriggerStatus(id, displayed, reason, code) {
SwrveLogger_1.default.debug("Campaign trigger status: " + reason + " Displayed " + displayed);
campaignStatus = { status: code, message: reason };
campaignStatuses.push({ id, displayed, type: "iam", reason });
}
if (globalStatus.status === SwrveConstants_1.CAMPAIGN_MATCH) {
//for each campaign, see if there is a matching trigger and pull out the messages if there is
this.campaigns.forEach(campaign => {
if (campaign.triggers && campaign.triggers.length > 0) {
for (const trigger of campaign.triggers) {
const canTrigger = trigger.event_name === triggerName && this.canTriggerWithPayload(trigger, payload);
if (!canTrigger && qa) {
const reason = "Campaign [" + campaign.id + "], Trigger [" + trigger.event_name + "], " +
"does not match eventName[" + triggerName + "] & payload[" + JSON.stringify(payload) +
"]. Skipping this trigger.";
logCampaignTriggerStatus(campaign.id, "false", reason, SwrveConstants_1.CAMPAIGN_NO_MATCH);
}
if (canTrigger) {
if (campaign.messages) {
campaign.messages.forEach(message => (matchingMessages.push(Object.assign({ parentCampaign: campaign.id }, message))));
}
else if (campaign.embedded_message) {
matchingMessages.push(Object.assign({ parentCampaign: campaign.id }, campaign.embedded_message));
}
break;
}
}
}
else {
if (qa) {
const reason = "Campaign [" + campaign.id + "], no triggers " + "(could be message centre). Skipping this campaign";
logCampaignTriggerStatus(campaign.id, "false", reason, SwrveConstants_1.CAMPAIGN_ERROR_INVALID_TRIGGERS);
}
}
});
if (matchingMessages.length > 0) {
matchingMessages.sort((a, b) => a.priority - b.priority);
let passedAllRules = matchingMessages.filter(message => {
for (const campaign of this.campaigns) {
if (campaign.id === message.parentCampaign) {
const { status, message: reason } = this.applyCampaignRules(triggerName, campaign, personalizationProperties);
const ok = status === SwrveConstants_1.CAMPAIGN_MATCH;
SwrveLogger_1.default.debug(reason);
if (qa && !ok) {
logCampaignTriggerStatus(campaign.id, "false", reason, status);
}
return ok;
}
}
return false;
});
if (passedAllRules.length > 1) {
const minPriority = passedAllRules.reduce((min, msg) => msg.priority < min ? msg.priority
: min, passedAllRules[0].priority);
passedAllRules = passedAllRules.filter((message) => {
return message.priority <= minPriority;
});
}
if (passedAllRules.length > 0) {
const randomPick = passedAllRules.length > 1 ? Math.floor(Math.random() * passedAllRules.length) : 0;
const selectedMessage = passedAllRules[randomPick];
if (qa) {
const pickedCampaign = passedAllRules[randomPick];
const logNotPicked = passedAllRules.filter((message, index) => index !== randomPick);
logNotPicked.forEach(message => {
const reason = "Campaign " + pickedCampaign.id + " was selected for display ahead of this campaign.";
logCampaignTriggerStatus(message.parentCampaign, "false", reason, SwrveConstants_1.CAMPAIGN_ELIGIBLE_BUT_OTHER_CHOSEN);
});
const reason = "Campaign [" + pickedCampaign.id + "], Trigger [" + triggerName + "], matches " + triggerName
+ " & payload " + JSON.stringify(payload) + ".";
logCampaignTriggerStatus(pickedCampaign.parentCampaign, "true", reason, SwrveConstants_1.CAMPAIGN_MATCH);
}
this.lastShownMessageTime = this.getNow();
if (!this.messageDisplayManager.isIAMShowing()) {
if (this.onMessageListener) {
this.onMessageListener(selectedMessage);
}
else {
for (const campaign of this.campaigns) {
if (campaign.id === selectedMessage.parentCampaign) {
if (campaign.messages && campaign.messages.length > 0) {
this.showMessage(selectedMessage, campaign, personalizationProperties);
this.updateCampaignState(selectedMessage);
if (impressionCallback) {
impressionCallback(selectedMessage);
}
break;
}
if (campaign.embedded_message &&
campaign.embedded_message.data) {
if (this.onEmbeddedListener) {
this.onEmbeddedListener(selectedMessage, personalizationProperties);
}
break;
}
}
}
}
}
}
}
else if (qa) {
globalStatus = { status: SwrveConstants_1.CAMPAIGN_NO_MATCH, message: "No matching campaigns." };
}
}
const campaignFailCode = globalStatus.status !== SwrveConstants_1.CAMPAIGN_MATCH || !campaignStatus ? globalStatus.status : campaignStatus.status;
return { globalStatus, campaignStatus, campaignFailCode, campaigns: campaignStatuses };
}
showMessage(message, campaign, personalizationProperties) {
this.messageDisplayManager.showMessage(message, campaign, this.assetManager.ImagesCDN, this.platform, personalizationProperties);
}
closeMessage() {
this.messageDisplayManager.closeMessage();
}
canTriggerWithPayload(trigger, payload) {
if (typeof trigger !== "object" || !trigger) {
return false;
}
return this.hasFulfilledCondition(trigger.conditions || {}, payload || {});
}
hasFulfilledCondition(condition, payload) {
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
switch (condition.op) {
case "eq":
const payloadValue = (_b = (_a = payload[condition.key]) === null || _a === void 0 ? void 0 : _a.toString()) === null || _b === void 0 ? void 0 : _b.toLowerCase();
const conditionValue = (_d = (_c = condition.value) === null || _c === void 0 ? void 0 : _c.toString()) === null || _d === void 0 ? void 0 : _d.toLowerCase();
return payloadValue === conditionValue;
case "contains":
const payloadValueLowercase = (_g = (_f = (_e = payload[condition.key]) === null || _e === void 0 ? void 0 : _e.toString()) === null || _f === void 0 ? void 0 : _f.toLowerCase()) !== null && _g !== void 0 ? _g : "";
const conditionValueLowercase = (_j = (_h = condition.value) === null || _h === void 0 ? void 0 : _h.toString()) === null || _j === void 0 ? void 0 : _j.toLowerCase();
return payloadValueLowercase.includes(conditionValueLowercase);
case "and":
for (const arg of condition.args) {
if (!this.hasFulfilledCondition(arg, payload)) {
return false;
}
}
return true;
case "or":
for (const arg of condition.args) {
if (this.hasFulfilledCondition(arg, payload)) {
return true;
}
}
return false;
default:
if (Object.keys(condition).length === 0) {
return true;
}
return false;
}
}
handleCDN(response) {
this.assetManager.clearCDN();
if (response.campaigns && response.campaigns.cdn_root) {
this.assetManager.ImagesCDN = response.campaigns.cdn_root;
}
else if (response.campaigns && response.campaigns.cdn_paths) {
const paths = response.campaigns.cdn_paths;
this.assetManager.ImagesCDN = paths.message_images;
this.assetManager.FontsCDN = paths.message_fonts;
}
}
getAssetManager() {
return this.assetManager;
}
getMessageCenterCampaigns(personalizationProperties) {
return this.campaigns
.filter(campaign => {
var _a;
return (campaign.message_center && campaign.messages && campaign.messages.length > 0) ||
(campaign.message_center && campaign.embedded_message && campaign.embedded_message.data.length > 0) ||
(campaign.message_center && ((_a = this.campaignState[campaign.id]) === null || _a === void 0 ? void 0 : _a.status) !== SwrveConstants_1.SWRVE_CAMPAIGN_STATUS_DELETED && this.canCampaignRender(campaign, personalizationProperties));
})
.map(campaign => (Object.assign({}, campaign)));
}
updateCampaignState(message) {
for (const parentCampaign of this.campaigns) {
if (parentCampaign.id === message.parentCampaign) {
const campaignState = this.campaignState[parentCampaign.id];
if (campaignState) {
campaignState.impressions++;
campaignState.status = "seen";
campaignState.lastShownTime = this.getNow();
this.campaignState[parentCampaign.id] = campaignState;
StorageManager_1.StorageManager.saveData(SwrveConstants_1.CAMPAIGN_STATE + this.profileManager.currentUser.userId, JSON.stringify(this.campaignState));
}
else {
SwrveLogger_1.default.error("Campaign state not found for campaign " + parentCampaign.id);
}
this.messagesShownCount++;
}
}
}
markCampaignAsSeen(campaign) {
this.setCampaignStatus(campaign, SwrveConstants_1.SWRVE_CAMPAIGN_STATUS_SEEN);
}
removeMessageCenterCampaign(campaign) {
this.setCampaignStatus(campaign, SwrveConstants_1.SWRVE_CAMPAIGN_STATUS_DELETED);
}
isMessageShowing() {
var _a, _b;
return (_b = (_a = this.messageDisplayManager) === null || _a === void 0 ? void 0 : _a.isIAMShowing()) !== null && _b !== void 0 ? _b : false;
}
setCampaignStatus(campaign, newStatus) {
if (campaign) {
const campaignState = this.campaignState[campaign.id];
if (campaignState) {
campaignState.status = newStatus;
this.campaignState[campaign.id] = campaignState;
StorageManager_1.StorageManager.saveData(SwrveConstants_1.CAMPAIGN_STATE + this.profileManager.currentUser.userId, JSON.stringify(this.campaignState));
}
else {
SwrveLogger_1.default.error("Campaign state not found for campaign " + campaign.id);
}
}
}
applyGlobalRules(triggerName) {
if (this.messagesShownCount >= this._maxMessagesPerSession) {
return {
status: SwrveConstants_1.GLOBAL_CAMPAIGN_THROTTLE_MAX_IMPRESSIONS,
message: "{App Throttle limit} Too many messages shown.",
};
}
const timeSinceStartup = this.profileManager.currentUser.sessionStart + (this._delayFirstMessage * 1000);
SwrveLogger_1.default.info("delay first message " + this._delayFirstMessage);
if (triggerName !== SwrveConstants_1.SWRVE_AUTOSHOW_AT_SESSION_START_TRIGGER && timeSinceStartup > this.getNow()) {
return {
status: SwrveConstants_1.GLOBAL_CAMPAIGN_THROTTLE_LAUNCH_TIME,
message: "{App Throttle limit} Too soon after launch. Wait until " + timeSinceStartup,
};
}
const lastDisplay = this.lastShownMessageTime + (this._minDelay * 1000);
if (this.lastShownMessageTime !== 0 && lastDisplay > this.getNow()) {
return {
status: SwrveConstants_1.GLOBAL_CAMPAIGN_THROTTLE_RECENT,
message: "{App Throttle limit} Too soon after last message. Wait until " + lastDisplay,
};
}
return {
status: SwrveConstants_1.CAMPAIGN_MATCH,
message: "Global display rules passing.",
};
}
applyCampaignRules(triggerName, parentCampaign, personalizationProperties) {
const rules = parentCampaign.rules;
const campaignState = this.campaignState[parentCampaign.id];
if (parentCampaign.start_date > this.getNow() || parentCampaign.end_date < this.getNow()) {
return {
status: SwrveConstants_1.CAMPAIGN_NOT_ACTIVE,
message: "Campaign " + parentCampaign.id + "not active.",
};
}
const timeSinceStart = this.profileManager.currentUser.sessionStart + (rules.delay_first_message * 1000);
if (triggerName !== SwrveConstants_1.SWRVE_AUTOSHOW_AT_SESSION_START_TRIGGER && timeSinceStart > this.getNow()) {
return {
status: SwrveConstants_1.CAMPAIGN_THROTTLE_LAUNCH_TIME,
message: "{Campaign throttle limit} Too soon after launch. Wait until " + timeSinceStart,
};
}
if (rules.hasOwnProperty("dismiss_after_views") && (campaignState === null || campaignState === void 0 ? void 0 : campaignState.impressions) >= rules.dismiss_after_views) {
const message = "{Campaign throttle limit} Campaign " + parentCampaign.id
+ " has been shown " + parentCampaign.rules.dismiss_after_views + " times already";
return {
status: SwrveConstants_1.CAMPAIGN_THROTTLE_MAX_IMPRESSIONS,
message,
};
}
const lastShown = (campaignState === null || campaignState === void 0 ? void 0 : campaignState.lastShownTime) + (rules.min_delay_between_messages * 1000);
if ((campaignState === null || campaignState === void 0 ? void 0 : campaignState.lastShownTime) !== 0 && lastShown > this.getNow()) {
const message = "{Campaign throttle limit} Too soon after last campaign. Wait until "
+ (lastShown + rules.min_delay_between_messages);
return {
status: SwrveConstants_1.CAMPAIGN_THROTTLE_RECENT,
message,
};
}
const assetsForTrigger = CampaignManager.getAllAssets([parentCampaign], this.assetManager.ImagesCDN, personalizationProperties);
if (!assetsForTrigger.every((asset) => asset.canRender())) {
return {
status: SwrveConstants_1.CAMPAIGN_COULD_NOT_PERSONALIZE,
message: "Personalization failed for campaign " + parentCampaign,
};
}
if (!this.assetManager.checkAssetsForCampaign(assetsForTrigger)) {
return {
status: SwrveConstants_1.CAMPAIGN_NOT_DOWNLOADED,
message: "Assets not loaded for Campaign " + parentCampaign,
};
}
return {
status: SwrveConstants_1.CAMPAIGN_MATCH,
message: "Campaign " + parentCampaign.id + "passes display rules",
};
}
parseGlobalRules(campaigns) {
if (campaigns && campaigns.rules) {
const rules = campaigns.rules;
this._maxMessagesPerSession = rules.hasOwnProperty("max_messages_per_session") ?
rules.max_messages_per_session : this.MAX_MESSAGES_PER_SESSION;
this._minDelay = rules.hasOwnProperty("min_delay_between_messages") ? rules.min_delay_between_messages : this.MIN_DELAY;
this._delayFirstMessage = rules.hasOwnProperty("delay_first_message") ? rules.delay_first_message : this.DELAY_FIRST_MESSAGE;
}
SwrveLogger_1.default.info("Global Rules: max_messages_per_session:" + this._maxMessagesPerSession + " min_delay:" +
this._minDelay + " delay_first_message:" + this._delayFirstMessage);
}
handleAssets(personalizationProperties, onAssetsLoaded) {
const assets = CampaignManager.getAllAssets(this.campaigns, this.assetManager.ImagesCDN, personalizationProperties);
this.assetManager.manageAssets(assets)
.then(() => {
SwrveLogger_1.default.info("CampaignManager: asset download complete");
onAssetsLoaded();
})
.catch(error => {
SwrveLogger_1.default.info("Error downloading assets " + error);
onAssetsLoaded(error);
});
}
static getAllAssets(campaigns, cdn, personalizationProperties) {
const assets = [];
for (const campaign of campaigns) {
for (const message of campaign.messages || []) {
const formats = (message.template && message.template.formats) || [];
for (const format of formats) {
// Check images and buttons directly under format if any.
for (const button of format.buttons || []) {
assets.push(new SwrveButton_1.SwrveButton(button, cdn, personalizationProperties));
}
for (const image of format.images || []) {
assets.push(new SwrveImage_1.SwrveImage(image, cdn, personalizationProperties));
}
// Check images and buttons in each page within the format if any
for (const page of format.pages || []) {
for (const button of page.buttons || []) {
assets.push(new SwrveButton_1.SwrveButton(button, cdn, personalizationProperties));
}
for (const image of page.images || []) {
assets.push(new SwrveImage_1.SwrveImage(image, cdn, personalizationProperties));
}
}
}
}
}
return assets;
}
canCampaignRender(campaign, personalizationProperties) {
const assets = CampaignManager.getAllAssets([campaign], this.assetManager.ImagesCDN, personalizationProperties);
return assets.every((asset) => asset.canRender());
}
getNow() {
return DateHelper_1.default.nowInUtcTime();
}
filterUnsupportedCampaigns(response) {
return response.campaigns.campaigns.filter((campaign) => {
var _a;
return !((_a = campaign.messages) === null || _a === void 0 ? void 0 : _a.some((message) => { var _a; return (_a = message.template.formats) === null || _a === void 0 ? void 0 : _a.some((format) => {
// each check needs to verify legacy json format (which has no concept of pages) and new format (which has pages)
const hasUnsupportedFeature = this.hasMultilineTextFeature(format) ||
this.hasPlainTextButtonFeature(format) ||
this.hasCopyToClipboardFeature(format);
if (hasUnsupportedFeature) {
SwrveLogger_1.default.warn("Campaign " + campaign.id + " contains unsupported feature. Skipping.");
}
return hasUnsupportedFeature;
}); }));
});
}
hasMultilineTextFeature(format) {
var _a, _b;
const isMultilineImage = (image) => 'multiline_text' in image;
return ((_a = format.images) === null || _a === void 0 ? void 0 : _a.some(isMultilineImage)) || ((_b = format.pages) === null || _b === void 0 ? void 0 : _b.some(page => { var _a; return (_a = page.images) === null || _a === void 0 ? void 0 : _a.some(isMultilineImage); }));
}
hasPlainTextButtonFeature(format) {
var _a, _b;
const isTextButton = (button) => 'text' in button;
return ((_a = format.buttons) === null || _a === void 0 ? void 0 : _a.some(isTextButton)) || ((_b = format.pages) === null || _b === void 0 ? void 0 : _b.some(page => { var _a; return (_a = page.buttons) === null || _a === void 0 ? void 0 : _a.some(isTextButton); }));
}
hasCopyToClipboardFeature(format) {
var _a, _b;
const isCopyToClipboardButton = (button) => { var _a; return ((_a = button.type) === null || _a === void 0 ? void 0 : _a.value) === SwrveConstants_1.COPY_TO_CLIPBOARD; };
return ((_a = format.buttons) === null || _a === void 0 ? void 0 : _a.some(isCopyToClipboardButton)) || ((_b = format.pages) === null || _b === void 0 ? void 0 : _b.some(page => { var _a; return (_a = page.buttons) === null || _a === void 0 ? void 0 : _a.some(isCopyToClipboardButton); }));
}
}
exports.CampaignManager = CampaignManager;