UNPKG

@analytics/snowplow

Version:

Snowplow integration for 'analytics' module for browser & node

614 lines (529 loc) 27.9 kB
'use strict'; var browserTracker = require('@snowplow/browser-tracker'); var browserPluginAdTracking = require('@snowplow/browser-plugin-ad-tracking'); var browserPluginClientHints = require('@snowplow/browser-plugin-client-hints'); var browserPluginConsent = require('@snowplow/browser-plugin-consent'); var browserPluginEcommerce = require('@snowplow/browser-plugin-ecommerce'); var browserPluginEnhancedEcommerce = require('@snowplow/browser-plugin-enhanced-ecommerce'); var browserPluginErrorTracking = require('@snowplow/browser-plugin-error-tracking'); var browserPluginFormTracking = require('@snowplow/browser-plugin-form-tracking'); var browserPluginGaCookies = require('@snowplow/browser-plugin-ga-cookies'); var browserPluginGeolocation = require('@snowplow/browser-plugin-geolocation'); var browserPluginLinkClickTracking = require('@snowplow/browser-plugin-link-click-tracking'); var browserPluginOptimizelyX = require('@snowplow/browser-plugin-optimizely-x'); var browserPluginPerformanceTiming = require('@snowplow/browser-plugin-performance-timing'); var browserPluginSiteTracking = require('@snowplow/browser-plugin-site-tracking'); var browserPluginTimezone = require('@snowplow/browser-plugin-timezone'); function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); } function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); } function _iterableToArray(iter) { if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter); } function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) return _arrayLikeToArray(arr); } function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; } function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; } function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; } function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } var defaultConfig = { /* See description below */ name: 'snowplow', trackerSettings: {} }; /** * Snowplow analytics integration * @link https://getanalytics.io/plugins/snowplow/ * @link https://docs.snowplowanalytics.com/docs/collecting-data/collecting-from-own-applications/javascript-trackers/browser-tracker/ * @param {Object} pluginConfig - Plugin settings * @param {string} pluginConfig.collectorUrl - The URL to a Snowplow collector * @param {string} [pluginConfig.name] - The name to identify this instance of the tracker, use if using multiple tracker instances ('snowplow' default) * @param {Object} [pluginConfig.trackerSettings] - The arg map to pass to the Snowplow Tracker * @param {string} [pluginConfig.trackerSettings.appId] - The appId to identify this application * @param {string} [pluginConfig.trackerSettings.platform] - Platform of tracking ("web" default) * @param {string} [pluginConfig.trackerSettings.cookieDomain] - Domain to set cookie against * @param {string} [pluginConfig.trackerSettings.discoverRootDomain] - Automatically discover root domain * @param {string} [pluginConfig.trackerSettings.cookieName] - Prefix for cookies ("_sp_" default) * @param {string} [pluginConfig.trackerSettings.cookieSameSite] - SameSite cookie setting ("None" default) * @param {boolean} [pluginConfig.trackerSettings.cookieSecure] - Secure cookie setting (true default) * @param {boolean} [pluginConfig.trackerSettings.encodeBase64] - Encode JSON objects as Base64 (true default) * @param {bolean} [pluginConfig.trackerSettings.respectDoNotTrack] - Respect do not track (consider analytics-plugin-do-not-track) (false default) * @param {string} [pluginConfig.trackerSettings.eventMethod] - Method to send events, GET, POST, Beacon (POST default) * @param {number} [pluginConfig.trackerSettings.bufferSize] - Amount of events to buffer before sending (1 default) * @param {number} [pluginConfig.trackerSettings.maxPostBytes] - Maximum size of POST or Beacon before sending (40000 default) * @param {string} [pluginConfig.trackerSettings.crossDomainLinker] - function to configure which links to add cross domain linking * @param {number} [pluginConfig.trackerSettings.cookieLifetime] - Cookie lifetime (63072000 default) * @param {string} [pluginConfig.trackerSettings.stateStorageStrategy] - Use cookies and/or localstorage ("cookieAndLocalStorage" default) * @param {number} [pluginConfig.trackerSettings.maxLocalStorageQueueSize] - Maximum numbers of events to buffer in localstorage to prevent filling local storage (1000 default) * @param {boolean} [pluginConfig.trackerSettings.resetActivityTrackingOnPageView] - Flag to decide whether to reset page ping timers on virtual page view (true default) * @param {boolean} [pluginConfig.trackerSettings.connectionTimeout] - The timeout, in milliseconds, before GET and POST requests will timeout (5000 default) (Snowplow JS 2.15.0+) * @param {(Object|boolean)} [pluginConfig.trackerSettings.anonymousTracking] - Flag to enable anonymous tracking functionality (false default) * @param {boolean} [pluginConfig.trackerSettings.anonymousTracking.withSessionTracking] - Flag to enable whether to continue tracking sessions in anonymous tracking mode (false default) * @param {boolean} [pluginConfig.trackerSettings.anonymousTracking.withServerAnonymisation] - Flag which prevents collector from returning and capturing cookies and capturing ip address (false default) * @param {Object} [pluginConfig.trackerSettings.contexts] - The auto contexts for each event * @param {boolean} [pluginConfig.trackerSettings.contexts.webPage] - The webpage context, containing the page view id. (true default) * @param {boolean} [pluginConfig.trackerSettings.contexts.performanceTiming] - Add performance timing information * @param {(Object|boolean)} [pluginConfig.trackerSettings.contexts.clientHints] - Add Client Hint information (Snowplow JS 2.15.0+) * @param {boolean} [pluginConfig.trackerSettings.contexts.clientHints.includeHighEntropy] - Capture High Entropy Client Hints (Snowplow JS 2.15.0+) * @param {boolean} [pluginConfig.trackerSettings.contexts.gaCookies] - Add gaCookie information * @param {boolean} [pluginConfig.trackerSettings.contexts.geolocation] - Add browser geolocation information * @param {boolean} [pluginConfig.trackerSettings.contexts.optimizelyXSummary] - Add browser geolocation information * @param {Array} [pluginConfig.trackerSettings.plugins] - Additional plugins to include at tracker initialisation. See https://docs.snowplowanalytics.com/docs/collecting-data/collecting-from-own-applications/javascript-trackers/browser-tracker/browser-tracker-v3-reference/plugins/ * @return {Object} Analytics plugin * * @example * // Minimal recommended configuration * snowplowPlugin({ * name: 'snowplow', * collectorUrl: 'collector.mysite.com', * trackerSettings: { * appId: 'myApp' * } * }) */ function snowplowPlugin() { var pluginConfig = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; var config = _objectSpread(_objectSpread({}, defaultConfig), pluginConfig); var _loaded = false; return { name: config.name, config: config, initialize: function initialize() { var collectorUrl = config.collectorUrl, name = config.name, trackerSettings = config.trackerSettings; if (!collectorUrl) { throw new Error('No collector url defined'); } if (!trackerSettings.contexts) { trackerSettings.contexts = {}; } trackerSettings.plugins = [].concat(_toConsumableArray(trackerSettings.plugins || []), [browserPluginAdTracking.AdTrackingPlugin(), browserPluginConsent.ConsentPlugin(), browserPluginEcommerce.EcommercePlugin(), browserPluginEnhancedEcommerce.EnhancedEcommercePlugin(), browserPluginErrorTracking.ErrorTrackingPlugin(), browserPluginFormTracking.FormTrackingPlugin(), browserPluginLinkClickTracking.LinkClickTrackingPlugin(), browserPluginSiteTracking.SiteTrackingPlugin(), browserPluginTimezone.TimezonePlugin()]); if (trackerSettings.contexts.performanceTiming) { trackerSettings.plugins.push(browserPluginPerformanceTiming.PerformanceTimingPlugin()); } if (trackerSettings.contexts.clientHints) { trackerSettings.plugins.push(browserPluginClientHints.ClientHintsPlugin(trackerSettings.contexts.clientHints === true ? false : trackerSettings.contexts.clientHints.includeHighEntropy)); } if (trackerSettings.contexts.gaCookies) { trackerSettings.plugins.push(browserPluginGaCookies.GaCookiesPlugin()); } if (trackerSettings.contexts.geolocation) { trackerSettings.plugins.push(browserPluginGeolocation.GeolocationPlugin()); } if (trackerSettings.contexts.optimizelyXSummary) { trackerSettings.plugins.push(browserPluginOptimizelyX.OptimizelyXPlugin()); } browserTracker.newTracker(name, collectorUrl, trackerSettings); _loaded = true; }, /** * Snowplow page view event https://docs.snowplowanalytics.com/docs/collecting-data/collecting-from-own-applications/javascript-trackers/browser-tracker/browser-tracker-v3-reference/tracking-events/ * @example * * // Enable some automatic tracking before page event * analytics.on('initialize:snowplow', ({instance}) => { * instance.plugins.snowplow.enableActivityTracking(10,10); * instance.plugins.snowplow.enableLinkClickTracking(); * }); * * // Track page * analytics.page(); * * // or Track page view with additional entities * analytics.page({ * contexts: [{ * schema: 'iglu:com.acme/blog_post/jsonschema/1-0-0', * data: { * title: 'Re-thinking the structure of event data', * category: 'Data Insights', * author: 'Cara Baestlein', * datePublished: '2020-01-24' * } * }] * }); */ page: function page(_ref) { var payload = _ref.payload; var properties = payload.properties; var name = config.name; if (!_loaded) return; // Use url and referrer values from Analaytics browserTracker.setCustomUrl(properties.url, [name]); browserTracker.setReferrerUrl(properties.referrer, [name]); // Track Page View event browserTracker.trackPageView({ title: properties.title, context: properties.contexts, dynamicContexts: properties.dynamicContexts }, [name]); }, /** * Reset Snowplow domain_userid and all session information store in cookies and/or local storage * Also resets the user_id set with identify and setUserId * Also resets all global contexts */ reset: function reset() { var name = config.name; if (!_loaded) return; browserTracker.clearUserData({}, [name]); browserTracker.setUserId(undefined, [name]); browserTracker.clearGlobalContexts([name]); }, /** * Snowplow track event https://docs.snowplowanalytics.com/docs/collecting-data/collecting-from-own-applications/javascript-trackers/browser-tracker/browser-tracker-v3-reference/tracking-events/ * All Snowplow 'track' and 'add' functions are available, remove 'track' or 'add' when calling analytics.track * i.e. analytics.track('adImpression', ..., ..., ...); * @example * * // Track structured event * analytics.track('playedVideo', { * category: 'Videos', * label: 'Fall Campaign', * value: 42 * }) * * // or Track Self Describing event * analytics.track('selfDescribingEvent', { * schema: 'iglu:com.acme/video_played/jsonschema/1-0-0', * data: { * id: 42, * title: 'Fall Campaign' * } * }) * * // or Track Self Describing event with additional entities * analytics.track('selfDescribingEvent', { * schema: 'iglu:com.acme/video_played/jsonschema/1-0-0', * data: { * id: 42, * title: 'Fall Campaign', * videoTimestampInMs: 1104 * }, * contexts: [{ * schema: 'iglu:com.acme/product/jsonschema/1-0-0', * data: { * id: 'f9cb6cb6-5ca8-47a6-9035-d9c6ac13029e', * category: 'clothing', * subCategory: 'socks', * price: 3.99 * } * }] * }) * * // or Track Enchanced Ecommerce event with product context * analytics.track('EnhancedEcommerceProductContext', { * id: 'P12345', * name: 'Android Warhol T-Shirt', * list: 'Search Results', * brand: 'Google', * category: 'Apparel/T-Shirts', * variant: 'Black', * quantity: 1 * }); * analytics.track('EnhancedEcommerceAction', { * action: 'add' * }); */ track: function track(_ref2) { var payload = _ref2.payload; var event = payload.event, properties = payload.properties; var name = config.name; if (!_loaded) return; _track(name, event, properties); }, /** * Set Snowplow user_id with setUserId * @example * * analytics.identify('user-id-xyz'); */ identify: function identify(_ref3) { var payload = _ref3.payload; var userId = payload.userId; var name = config.name; if (!_loaded) return; if (userId) { browserTracker.setUserId(userId, [name]); } }, loaded: function loaded() { return _loaded; }, methods: { /** * Enables Activity Tracking * Generates page ping events based on user activity on page * Should be called before first page view event * @param {number} [minimumVisitLength] - Minimum visit length before first page ping event fires * @param {number} [heartbeat] - Period where user must be active to fire the next page ping */ enableActivityTracking: function enableActivityTracking(minimumVisitLength, heartbeat) { var name = config.name; browserTracker.enableActivityTracking({ minimumVisitLength: minimumVisitLength, heartbeatDelay: heartbeat }, [name]); }, /** * Refresh Link Click Tracking */ refreshLinkClickTracking: function refreshLinkClickTracking() { var name = config.name; browserPluginLinkClickTracking.refreshLinkClickTracking([name]); }, /** * Allows for refreshing of form tracking * Use when forms are added to page * @param {Object} [formTrackingConfig] - Optional form tracking configuration * @param {string[]} [formTrackingConfig.blacklist] - An array of CSS classes which should be ignored by form tracking * @param {string[]} [formTrackingConfig.whitelist] - An array of the CSS classes of form elements which you do want to be tracked * @param {Object} [formTrackingConfig.fields] - An object that describes how to filter and transform form elements * @param {formFieldsFilter} [formTrackingConfig.fields.filter] - A function which determines which fields should be tracked. The function should take one argument, the form element, and return a boolean * @param {formFieldsTransform} [formTrackingConfig.fields.transform] - An array of CSS classes which should be ignored by link click tracking * @param {Context[]} [contexts] - Optional array of custom contexts to attach to every form track event */ enableFormTracking: function enableFormTracking(formTrackingConfig, contexts) { var name = config.name; browserPluginFormTracking.enableFormTracking({ context: contexts, options: formTrackingConfig }, [name]); }, /** * Adds the GDPR Context to all events * @param {string} [basisForProcessing] - Required. GDPR basis for data collection & processing * @param {string} [documentId] - ID for document detailing basis for processing * @param {string} [documentVersion] - Version of document detailing basis for processing * @param {string} [documentDescription] - Description of document detailing basis for processing */ enableGdprContext: function enableGdprContext(basisForProcessing, documentId, documentVersion, documentDescription) { var name = config.name; browserPluginConsent.enableGdprContext({ basisForProcessing: basisForProcessing, documentId: documentId, documentVersion: documentVersion, documentDescription: documentDescription }, [name]); }, /** * Enables automatic link click tracking * @param {Object} [filter] - An object to specify which links to include in tracking * @param {linkClickFilter} [filter.filter] - A function which determines which links should be tracked * @param {string[]} [filter.blacklist] - An array of CSS classes which should be ignored by link click tracking * @param {string[]} [filter.whitelist] - An array of the CSS classes of links which you do want to be tracked * @param {boolean} [pseudoClicks] - Allows middle clicks to track in Firefox. Can cause false positives. Recommended to enable. * @param {boolean} [content] - Set to true to capture the innerHTML of the clicked link * @param {Context[]} [contexts] - An array of custom contexts to attach to every link click event */ enableLinkClickTracking: function enableLinkClickTracking(filter, pseudoClicks, content, contexts) { var name = config.name; browserPluginLinkClickTracking.enableLinkClickTracking({ options: filter, pseudoClicks: pseudoClicks, trackContent: content, context: contexts }, [name]); }, /** * Enables automatic error tracking * @param {errorFilter} [filter] - A function which determines which ErrorEvents should be tracked. * @param {errorContexts} [contextAdder] - A function to build custom contexts */ enableErrorTracking: function enableErrorTracking(filter, contextAdder) { var name = config.name; browserPluginErrorTracking.enableErrorTracking({ filter: filter, contextAdder: contextAdder }, [name]); }, /** * Allows contexts to be sent with every event, or a subset of events as defined in the ruleset * @param {GlobalContexts} globalContexts - Array of global contexts to be added to every event */ addGlobalContexts: function addGlobalContexts(globalContexts) { var name = config.name; browserTracker.addGlobalContexts(globalContexts, [name]); }, /** * Removes global contexts by matching on the schema name * @param {Context[]} globalContexts */ removeGlobalContexts: function removeGlobalContexts(globalContexts) { var name = config.name; browserTracker.removeGlobalContexts(globalContexts, [name]); }, /** * Clears all Global Contexts */ clearGlobalContexts: function clearGlobalContexts() { var name = config.name; browserTracker.clearGlobalContexts([name]); }, /** * Disables Anonymous Tracking * https://docs.snowplowanalytics.com/docs/collecting-data/collecting-from-own-applications/javascript-trackers/browser-tracker/browser-tracker-v3-reference/tracker-setup/additional-options/ * @param {string} stateStorageStrategy - Use to switch storage strategy, or don't use to leave the same */ disableAnonymousTracking: function disableAnonymousTracking(stateStorageStrategy) { var name = config.name; browserTracker.disableAnonymousTracking({ stateStorageStrategy: stateStorageStrategy }, [name]); }, /** * Enables Anonymous Tracking * https://docs.snowplowanalytics.com/docs/collecting-data/collecting-from-own-applications/javascript-trackers/browser-tracker/browser-tracker-v3-reference/tracker-setup/additional-options/ * @param {Object} anonymousTrackingArgs - Configures anonymous tracking features * @param {boolean} anonymousTrackingArgs.withSessionTracking - Enables session tracking when enabling Anonymous Tracking * @param {boolean} anonymousTrackingArgs.withServerAnonymisation - Prevents collector from returning cookies and capturing ip address * @param {string} stateStorageStrategy - Use to switch storage strategy, or don't use to leave the same */ enableAnonymousTracking: function enableAnonymousTracking(anonymousTrackingArgs, stateStorageStrategy) { var name = config.name; browserTracker.enableAnonymousTracking({ options: anonymousTrackingArgs, stateStorageStrategy: stateStorageStrategy }, [name]); }, /** * Adds a plugin to the available plugins * https://docs.snowplowanalytics.com/docs/collecting-data/collecting-from-own-applications/javascript-trackers/javascript-tracker/javascript-tracker-v3/plugins/ * @param {Object} plugin - The Plugin to include */ addPlugin: function addPlugin(plugin) { var name = config.name; browserTracker.addPlugin(plugin, [name]); } } }; } function _track(name, event, properties) { if (event && properties) { // Map V2 naming into V3 naming if (properties.contexts && !properties.context) { properties.context = properties.contexts; } switch (event.toLowerCase()) { case 'selfdescribingevent': case 'unstructevent': browserTracker.trackSelfDescribingEvent({ event: properties, context: properties.context }, [name]); break; case 'socialinteraction': browserPluginSiteTracking.trackSocialInteraction(properties, [name]); break; case 'adimpression': browserPluginAdTracking.trackAdImpression(properties, [name]); break; case 'adclick': browserPluginAdTracking.trackAdClick(properties, [name]); break; case 'adconversion': browserPluginAdTracking.trackAdConversion(properties, [name]); break; case 'linkclick': browserPluginLinkClickTracking.trackLinkClick(properties, [name]); break; case 'addtocart': browserPluginEcommerce.trackAddToCart(properties, [name]); break; case 'removefromcart': browserPluginEcommerce.trackRemoveFromCart(properties, [name]); break; case 'sitesearch': browserPluginSiteTracking.trackSiteSearch(properties, [name]); break; case 'consentgranted': browserPluginConsent.trackConsentGranted(properties, [name]); break; case 'consentwithdrawn': browserPluginConsent.trackConsentWithdrawn(properties, [name]); break; case 'error': browserPluginErrorTracking.trackError(properties, [name]); break; case 'enhancedecommerceactioncontext': browserPluginEnhancedEcommerce.addEnhancedEcommerceActionContext(properties, [name]); break; case 'enhancedecommerceimpressioncontext': browserPluginEnhancedEcommerce.addEnhancedEcommerceImpressionContext(properties, [name]); break; case 'enhancedecommerceproductcontext': browserPluginEnhancedEcommerce.addEnhancedEcommerceProductContext(properties, [name]); break; case 'enhancedecommercepromocontext': browserPluginEnhancedEcommerce.addEnhancedEcommercePromoContext(properties, [name]); break; case 'enhancedecommerceaction': snowplow("trackEnhancedEcommerceAction:".concat(name), properties.action); break; case 'timing': browserPluginSiteTracking.trackTiming(properties, [name]); case 'structevent': default: browserTracker.trackStructEvent({ category: properties.category || 'All', action: event || properties.actions, label: properties.label, property: properties.property, value: properties.value, context: properties.context }, [name]); break; } } } /** * @typedef {Object} Context * @property {string} schema - The Schema which describes the context * @property {Object} data - The data object which conforms to the schema */ /** * A function which determines which links should be tracked * @callback linkClickFilter * @param {Object} element * @return {boolean} true if the element should be tracked */ /** * A function which determines which ErrorEvents should be tracked * @callback errorFilter * @param {Object} errorEvent * @return {boolean} true if the error should be tracked */ /** * A function which determines which ErrorEvents should be tracked * @callback errorContexts * @param {Object} errorEvent * @return {Context[]} Array of context to be included with the error tracking event */ /** * A function which determines which fields should be tracked * @callback formFieldsFilter * @param {Object} formElement * @return {boolean} true if the form field should be tracked */ /** * Transforms a form elements value * @callback formFieldsTransform * @param {Object} formElement * @return {string} The value to replace this form elements value with */ /** * @typedef {Object} GlobalContexts * @property {Context[]} contexts - The contexts to add * @property {GlobalContextFilter|GlobalContextRuleset} [filterConditions] - The Schema which describes the context */ /** * A function which determines which contexts should be sent * @callback GlobalContextFilter * @param {Object} event * @param {string} event.eventType This argument is a string taken from the event payload field, e. e.g. pv, pp, se * @param {string} event.eventSchema The schema of the event * @return {boolean} true if the context should be included with the event */ /** * @typedef {Object} GlobalContextRuleset * @property {string[]} [accept] - A list of schemas to attach this context to * @property {string[]} [reject] - A list of schemas to not attach this context too */ var index = snowplowPlugin ; module.exports = index;