import utils from './utils';
/* eslint-disable no-underscore-dangle */
/**
* Pinpoll Sdk
* Currently used for
* - adding audiences to a given visitor
* - removing audiences from a given visitor
* - setting custom attributes to a given visitor
* - querying audiences of given visitor
*/
/**
* Definition of Audience Object
* @typedef {Object} Audience - Audience Object
* @property {number} id - Id of the audience.
* @property {boolean} isPinpoll - Indicator if this audience is a pinpoll audience or not.
* @property {number} parentId - Parent Id of the audience.
* @property {number} realDepth - Depth of the audience (needed for audience tree).
* @property {Object} text - Translations of the audience as object.
* @property {string} text.en - English translation of the audience.
* @property {string} text.de - German translation of the audience.
* @property {number} typeId - Type of the audience (1 = interest, 2 = property).
*/
/**
* Private methods defined as symbols
* @private
*/
const _fetch = Symbol('submit');
const _parseOptions = Symbol('parseOptions');
class Sdk {
/**
* Constructor of Pinpoll Sdk
*/
constructor() {
this.attributes = {};
this.audiences = {
addedAudiences: [],
removedAudiences: [],
};
this.visitorAudiences = {};
}
/**
* Pings the sdk which logs 'pong' in the console if reachable
* @static
* @returns {void}
*/
static ping() {
console.info('PinpollSdk: pong!');
}
/**
* Set attributes object
* @param {Object<string,string>} attributes - Attributes object in the form key:value
* @returns {void}
*/
setAttributes(attributes) {
this.attributes = attributes;
}
/**
* Add audience to addedAudiences array
* @param {object} audience - Audience Object
* @param {number} audience.id - Id of the audience (must be a valid id!)
* @param {number} [audience.affinityScore=1] - Optional Affinity Score (Range 1-10, where 10 is the highest)
* @returns {void}
*/
addAudience({ id, affinityScore = 1 }) {
this.audiences.addedAudiences.push({ id, affinityScore });
}
/**
* Add audiences to addedAudiences array
* @param {Array<Object>} audiences - Audiences array with audience objects with the properties id and an optional affinityScore
* @returns {void}
*/
addAudiences(audiences) {
this.audiences.addedAudiences = [...this.audiences.addedAudiences, ...audiences];
}
/**
* Add audience too removedAudiences array
* @param {object} audience - Audience Object
* @param {number} audience.id - Id of the audience (must be a valid id!)
* @param {number} [audience.affinityScore=null] - Optional Affinity Score (This will only remove the audience with exact match of affinity score)
* @returns {void}
*/
removeAudience({ id, affinityScore = null }) {
this.audiences.removedAudiences.push({ id, affinityScore });
}
/**
* Add audiences to removedAudiences array
* @param {Array<Object>} audiences - Audiences array with audience objects with the properties id and an optional affinityScore
* @returns {void}
*/
removeAudiences(audiences) {
this.audiences.removedAudiences = [...this.audiences.removedAudiences, ...audiences];
}
/**
* Send data to endpoint
* @returns {void}
*/
async sendData() {
if (this.attributes) {
const data = {
attributes: this.attributes,
audiences: this.audiences,
};
await Sdk[_fetch]({ endpoint: '/sdk/visitors', data });
}
}
/**
* Get audiences of current visitor
* @param {Object} options - Options how to display audiences
* @param {boolean} options.tree=true - Display audiences as tree structure
* @param {string} options.removeRootLevel='none' - Remove root level audiences ['interests', 'properties', 'all', 'none']
* @param {boolean|string} options.onlyLabels=false - Show only labels of one specific language ['en', 'de', false]
* @returns {Object<string, Audience[]>} Audiences of the current visitor divided in interests and properties
*/
async getAudiences(options = { tree: true, removeRootLevel: 'none', onlyLabels: false }) {
try {
const jsonResponse = await Sdk[_fetch]({ method: 'get', endpoint: '/sdk/getAudiences' }).then((response) => response.json());
if (jsonResponse.status === 'success') {
const audiences = jsonResponse.data;
// parse options
this.visitorAudiences = Sdk[_parseOptions](audiences, options);
} else {
console.warn(jsonResponse.data);
}
} catch (err) {
console.error('Failed to fetch getAudiences!');
console.error(err);
}
return this.visitorAudiences;
}
/**
* Track a new event with a given event name
* @static
* @param {string} eventKey - Name of the event to track
* @param {number} [eventValue] - Value of the event to track
* @param {string} [eventLabel] - Label of the event to track
* @returns {void}
*/
static async trackEvent(eventKey, eventValue, eventLabel) {
try {
if (!eventKey) {
console.warn('Please provide an event name in order to make an event tracking request!');
} else {
const postParams = {
eventKey,
location: document.location.href,
title: document.title,
};
if (eventValue) postParams.eventValue = eventValue;
if (eventLabel) postParams.eventLabel = eventLabel;
await Sdk[_fetch]({ method: 'post', endpoint: '/sdk/event', data: postParams });
}
} catch (err) {
console.error('Failed to fetch trackEvent!');
console.error(err);
}
}
/**
* Parse options for transforming audiences
* @static
* @private
* @param {Object} audiences - Given audiences Object
* @param {Object} options - Options how to display audiences
* @param {boolean} options.tree - Display audiences as tree structure
* @param {string} options.removeRootLevel='properties' - Remove root level ['interests', 'properties', 'all', 'none']
* @param {boolean|string} options.onlyLabels=false - Show only labels of one specific language ['en', 'de', false]
* @returns {Object} transformedAudiences - transformed audiences with applied options
*/
static [_parseOptions](audiences, { tree, removeRootLevel, onlyLabels }) {
const transformedAudiences = audiences;
// remove root level
if (removeRootLevel !== 'none') {
if (removeRootLevel === 'interests' || removeRootLevel === 'all') {
transformedAudiences.interests = utils.removeRootLevel(audiences.interests);
}
if (removeRootLevel === 'properties' || removeRootLevel === 'all') {
transformedAudiences.properties = utils.removeRootLevel(audiences.properties);
}
}
// create tree
if (tree) {
transformedAudiences.interests = utils.transformToTree(transformedAudiences.interests, removeRootLevel === 'interests');
transformedAudiences.properties = utils.transformToTree(transformedAudiences.properties, removeRootLevel === 'properties');
}
// show only labels
if (onlyLabels) {
transformedAudiences.interests = utils.onlyLabels(transformedAudiences.interests, onlyLabels, tree);
transformedAudiences.properties = utils.onlyLabels(transformedAudiences.properties, onlyLabels, tree);
}
return transformedAudiences;
}
/**
* Fetch data from analytics endpoint
* @static
* @private
* @param {Object} params parameters
* @param {String} [params.method='post'] - method of call
* @param {String} [params.endpoint=''] - endpoint to send to
* @param {Object} params.data - data to send
* @returns {Promise}
*/
static async [_fetch]({ method = 'post', endpoint = '', data }) {
// send request
const dmpEndpoint = process.env.MIX_DMP_ENDPOINT || 'https://api.dmp.pinpoll.com';
try {
return fetch(dmpEndpoint + endpoint, {
method,
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
'PP-Visitor': Sdk.getVisitor(), // TODO 11084 use storage module from global.js instead
},
referrerPolicy: 'no-referrer-when-downgrade',
body: JSON.stringify(data),
});
} catch (err) {
console.error(err);
}
return null;
}
/**
* Code duplicate of global js storage module getter
* @returns {string} encrypted visitor
*/
static getVisitor() {
try {
if (!localStorage.PP_visitor) {
return null;
}
const visitor = JSON.parse(localStorage.PP_visitor);
if (visitor.expires && visitor.expires < Date.now()) {
delete localStorage.PP_visitor;
return null;
}
// refresh expiry
visitor.expires = new Date().setDate(new Date().getDate() + 30); // 30 days from now
localStorage.PP_visitor = JSON.stringify(visitor);
return visitor.value;
} catch (e) {
// eslint-disable-next-line no-console
console.warn(e);
return null;
}
}
}
// set class to 'PinpollSdk'
window.PinpollSdk = Sdk;
export default Sdk;