UNPKG

@shopgate/tracking-core

Version:

Tracking core library for the Shopgate Connect PWA.

288 lines (270 loc) • 8.59 kB
import "core-js/modules/es.string.replace.js"; import DataRequest from '@shopgate/pwa-core/classes/DataRequest'; import { logger } from '@shopgate/pwa-core/helpers'; import * as _SGAction from '@shopgate/pwa-core/commands/unifiedTracking'; export { _SGAction as SGAction }; /** * Decodes a hexadecimal encoded binary string * @param {string} str The string that shall be decoded * @see http://locutus.io/php/strings/hex2bin/ * @returns {string|boolean} Hexadecimal representation of data. FALSE if decoding failed. */ export const hex2bin = str => { const s = `${str}`; const ret = []; let i = 0; let l; for (l = s.length; i < l; i += 2) { const c = parseInt(s.substr(i, 1), 16); const k = parseInt(s.substr(i + 1, 1), 16); // eslint-disable-next-line no-restricted-globals if (isNaN(c) || isNaN(k)) { return false; } // eslint-disable-next-line no-bitwise ret.push(c << 4 | k); } // eslint-disable-next-line prefer-spread return String.fromCharCode.apply(String, ret); }; /** * Sends a DataRequest * @param {string} url Url for the request */ export function sendDataRequest(url) { new DataRequest(url).dispatch().then(result => logger.info(url, result)).catch(err => err && logger.error(err)); } /** * Object representation of URI(RFC2396) string * Made for general purpose. Feel free to extend it for your needs. */ export let SGLink = /*#__PURE__*/function () { /** * Constructor * @param {string} url Url to creating SGLink from */ function SGLink(url) { // Complete url string this.url = ''; // Any scheme we support (ex. https|shopgate-{$number}|sgapi) this.scheme = ''; this.authority = ''; this.path = ''; this.splittedPath = []; this.query = ''; // Endpoint - safe for shopgate (ex. no endpoint is converted to "index") this.action = ''; this.params = {}; this.isDeepLink = false; /** * Converts the object to relative url * @param {boolean} [leadingSlash=true] Tells if the url shall start with a leading slash * @return {string} */ this.toRelativeString = (leadingSlash = true) => { let outputUrl = ''; if (leadingSlash && this.path[0] !== '/') { outputUrl = '/'; } if (this.path) { outputUrl += SGLink.encodeURISafe(this.path); } if (this.query) { // .query is always encoded outputUrl += `?${this.query}`; } return outputUrl; }; this.url = url; this.parseUrl(url); } /** * Encode the url with encodeURIComponent and * takes care of double encoding * * @param {string} string - string to be encoded * @return {string} */ SGLink.encodeURIComponentSafe = function encodeURIComponentSafe(string) { const decoded = decodeURIComponent(string); if (decoded !== string) { return string; } return encodeURIComponent(string); } /** * Encode the url and takes care of double encoding * @param {string} string String to be encoded * @returns {string} encoded string */; SGLink.encodeURISafe = function encodeURISafe(string) { const decoded = decodeURI(string); if (decoded !== string) { return string; } return encodeURI(string); } /** * Parses url and extracts path, query, action, splittedPath, ... * * @param {string} incomingUrl The url that shall be parsed. */; var _proto = SGLink.prototype; _proto.parseUrl = function parseUrl(incomingUrl) { let urlToSanitize = incomingUrl; if (!urlToSanitize) { urlToSanitize = ''; } const commonSchemas = ['http', 'https', 'tel', 'mailto']; // Based on the regex in RFC2396 Appendix B. const parser = /^(?:([^:/?#]+):)?(?:\/\/([^/?#]*))?([^?#]*)(?:\?([^#]*))?(?:#(.*))?/; const result = urlToSanitize.match(parser); if (!this.isDeepLink && result[1] && commonSchemas.indexOf(result[1]) === -1) { this.isDeepLink = true; const scheme = `${result[1]}://`; /** * Add slash so that we can parse the attributes properly * (for shopgate-standalone://cart we would get e.g. "authority: cart" which is wrong) */ urlToSanitize = urlToSanitize.replace(scheme, `${scheme}/`); this.parseUrl(urlToSanitize); return; } this.scheme = result[1] || ''; this.authority = result[2] || ''; this.path = result[3] || ''; this.query = result[4] || ''; this.action = ''; // Endpoint - safe for shopgate (ex. no endpoint is converted to "index") if (this.query) { const queryParts = this.query.split('&'); const queryPartsLength = queryParts.length; // Clearing params this.setParams({}); for (let i = 0; i < queryPartsLength; i += 1) { const queryPair = queryParts[i].split('='); this.setParam(queryPair[0], queryPair[1]); } } if (this.path) { const pathSplitted = this.path.replace('/php/shopgate', '').split('/'); this.action = pathSplitted[0] || ''; this.splittedPath = pathSplitted; if (pathSplitted[0] === '/' || pathSplitted[0] === '') { this.action = pathSplitted[1] || ''; this.splittedPath.shift(); } } } /** * Gets a param * @param {string} name name of param * @return {string|undefined} */; _proto.getParam = function getParam(name) { if (!(name in this.params)) { return undefined; } return this.params[name]; } /** * Sets param. * @param {string} name Name of param * @param {string} [value] Value of param - if empty, the parameter will be deleted from the query */; _proto.setParam = function setParam(name, value) { if (typeof value === 'undefined') { this.deleteParam(name); } else { this.params[name] = value; this.setParams(); } } /** * Sets params array * * @param {Object} [newParams] New Params to be set */; _proto.setParams = function setParams(newParams) { const newQueryArr = []; if (typeof newParams !== 'undefined') { this.params = newParams; } Object.keys(this.params).forEach(keyName => { newQueryArr.push(`${keyName}=${SGLink.encodeURIComponentSafe(this.params[keyName])}`); }); this.query = newQueryArr.join('&'); } /** * Safely deletes the param. * * @param {string} name name of param * @returns {boolean} */; _proto.deleteParam = function deleteParam(name) { if (!(name in this.params)) { return false; } delete this.params[name]; this.setParams(this.params); return true; } /** * Converts the object to string * * @return {string} */; _proto.toString = function toString() { let outputUrl = ''; const notNavigatorSchema = ['mailto', 'tel'].indexOf(this.scheme) > -1; if (this.scheme) { outputUrl += this.scheme; // The sgapi-links don't need further scheme parsing since the scheme is 'sgapi:' if (this.scheme !== 'sgapi') { if (notNavigatorSchema) { if (this.scheme.indexOf(':') === -1) { outputUrl += ':'; } } else { if (this.scheme.indexOf(':/') === -1) { // If the scheme already contains :/ we don't want to add it again outputUrl += ':/'; this.scheme = outputUrl; } if (!this.isDeepLink) { outputUrl += '/'; } } } else if (this.scheme.indexOf(':') === -1) { outputUrl += ':'; } } if (this.authority) { outputUrl += this.authority; } outputUrl += this.toRelativeString(false); return outputUrl; }; /** * Sets utm param from event. * @param {Object} data event data * @param {Object} raw event raw data */ _proto.setUtmParams = function setUtmParams(data, raw) { // Add fake params, only if it didn't come from branch.io if (raw.type !== 'branchio') { this.setParam('utm_source', 'shopgate'); this.setParam('utm_medium', raw.type); } if (raw.type === 'push_message') { const campaigns = ['cart_reminder', 'inactive_app_user']; const notificationId = raw.notificationId || 'not-provided'; const campaignName = this.getParam('utm_campaign'); if (campaigns.indexOf(campaignName) !== -1) { // Set utm_content to distinguish the cart reminders from "normal" push messages this.setParam('utm_content', campaignName); } this.setParam('utm_campaign', `push-${notificationId}`); } }; return SGLink; }();