UNPKG

@snowplow/tracker-core

Version:

Core functionality for Snowplow JavaScript trackers

1,295 lines (1,286 loc) 94.6 kB
/*! * Core functionality for Snowplow JavaScript trackers v4.5.0 (http://bit.ly/sp-js) * Copyright 2022 Snowplow Analytics Ltd, 2010 Anthon Pang * Licensed under BSD-3-Clause */ import { __spreadArray, __assign, __awaiter, __generator } from 'tslib'; import { v4 } from 'uuid'; var version$1 = "4.5.0"; /* * Copyright (c) 2013 Kevin van Zonneveld (http://kvz.io) * and Contributors (http://phpjs.org/authors) * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in * the Software without restriction, including without limitation the rights to * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies * of the Software, and to permit persons to whom the Software is furnished to do * so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ /** * Decodes a url safe Base 64 encoded string * @remarks See: {@link http://tools.ietf.org/html/rfc4648#page-7} * @param data - String to decode * @returns The decoded string */ function base64urldecode(data) { if (!data) { return data; } var padding = 4 - (data.length % 4); switch (padding) { case 2: data += '=='; break; case 3: data += '='; break; } var b64Data = data.replace(/-/g, '+').replace(/_/g, '/'); return base64decode(b64Data); } /** * Encodes a string into a url safe Base 64 encoded string * @remarks See: {@link http://tools.ietf.org/html/rfc4648#page-7} * @param data - String to encode * @returns The url safe Base 64 string */ function base64urlencode(data) { if (!data) { return data; } var enc = base64encode(data); return enc.replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_'); } var b64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; /** * Encode string as base64. * Any type can be passed, but will be stringified * * @param data - string to encode * @returns base64-encoded string */ function base64encode(data) { // discuss at: http://phpjs.org/functions/base64_encode/ // original by: Tyler Akins (http://rumkin.com) // improved by: Bayron Guevara // improved by: Thunder.m // improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) // improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) // improved by: Rafał Kukawski (http://kukawski.pl) // bugfixed by: Pellentesque Malesuada // example 1: base64_encode('Kevin van Zonneveld'); // returns 1: 'S2V2aW4gdmFuIFpvbm5ldmVsZA==' // example 2: base64_encode('a'); // returns 2: 'YQ==' // example 3: base64_encode('✓ à la mode'); // returns 3: '4pyTIMOgIGxhIG1vZGU=' var o1, o2, o3, h1, h2, h3, h4, bits, i = 0, ac = 0; var tmp_arr = []; if (!data) { return data; } data = unescape(encodeURIComponent(data)); do { // pack three octets into four hexets o1 = data.charCodeAt(i++); o2 = data.charCodeAt(i++); o3 = data.charCodeAt(i++); bits = (o1 << 16) | (o2 << 8) | o3; h1 = (bits >> 18) & 0x3f; h2 = (bits >> 12) & 0x3f; h3 = (bits >> 6) & 0x3f; h4 = bits & 0x3f; // use hexets to index into b64, and append result to encoded string tmp_arr[ac++] = b64.charAt(h1) + b64.charAt(h2) + b64.charAt(h3) + b64.charAt(h4); } while (i < data.length); var enc = tmp_arr.join(''); var r = data.length % 3; return (r ? enc.slice(0, r - 3) : enc) + '==='.slice(r || 3); } /** * Decode base64 to string * * @param data - base64 to string * @returns decoded string */ function base64decode(encodedData) { // discuss at: http://locutus.io/php/base64_decode/ // original by: Tyler Akins (http://rumkin.com) // improved by: Thunder.m // improved by: Kevin van Zonneveld (http://kvz.io) // improved by: Kevin van Zonneveld (http://kvz.io) // input by: Aman Gupta // input by: Brett Zamir (http://brett-zamir.me) // bugfixed by: Onno Marsman (https://twitter.com/onnomarsman) // bugfixed by: Pellentesque Malesuada // bugfixed by: Kevin van Zonneveld (http://kvz.io) // improved by: Indigo744 // example 1: base64_decode('S2V2aW4gdmFuIFpvbm5ldmVsZA==') // returns 1: 'Kevin van Zonneveld' // example 2: base64_decode('YQ==') // returns 2: 'a' // example 3: base64_decode('4pyTIMOgIGxhIG1vZGU=') // returns 3: '✓ à la mode' // decodeUTF8string() // Internal function to decode properly UTF8 string // Adapted from Solution #1 at https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/Base64_encoding_and_decoding var decodeUTF8string = function (str) { // Going backwards: from bytestream, to percent-encoding, to original string. return decodeURIComponent(str .split('') .map(function (c) { return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2); }) .join('')); }; var o1, o2, o3, h1, h2, h3, h4, bits, i = 0, ac = 0, dec = ''; var tmpArr = []; if (!encodedData) { return encodedData; } encodedData += ''; do { // unpack four hexets into three octets using index points in b64 h1 = b64.indexOf(encodedData.charAt(i++)); h2 = b64.indexOf(encodedData.charAt(i++)); h3 = b64.indexOf(encodedData.charAt(i++)); h4 = b64.indexOf(encodedData.charAt(i++)); bits = (h1 << 18) | (h2 << 12) | (h3 << 6) | h4; o1 = (bits >> 16) & 0xff; o2 = (bits >> 8) & 0xff; o3 = bits & 0xff; if (h3 === 64) { tmpArr[ac++] = String.fromCharCode(o1); } else if (h4 === 64) { tmpArr[ac++] = String.fromCharCode(o1, o2); } else { tmpArr[ac++] = String.fromCharCode(o1, o2, o3); } } while (i < encodedData.length); dec = tmpArr.join(''); return decodeUTF8string(dec.replace(/\0+$/, '')); } /* * Copyright (c) 2022 Snowplow Analytics Ltd, 2010 Anthon Pang * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * 3. Neither the name of the copyright holder nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ function payloadBuilder() { var dict = {}, allJson = [], jsonForProcessing = [], contextEntitiesForProcessing = []; var processor; var add = function (key, value) { if (value != null && value !== '') { // null also checks undefined dict[key] = value; } }; var addDict = function (dict) { for (var key in dict) { if (Object.prototype.hasOwnProperty.call(dict, key)) { add(key, dict[key]); } } }; var addJson = function (keyIfEncoded, keyIfNotEncoded, json) { if (json && isNonEmptyJson(json)) { var jsonWithKeys = { keyIfEncoded: keyIfEncoded, keyIfNotEncoded: keyIfNotEncoded, json: json }; jsonForProcessing.push(jsonWithKeys); allJson.push(jsonWithKeys); } }; var addContextEntity = function (entity) { contextEntitiesForProcessing.push(entity); }; return { add: add, addDict: addDict, addJson: addJson, addContextEntity: addContextEntity, getPayload: function () { return dict; }, getJson: function () { return allJson; }, withJsonProcessor: function (jsonProcessor) { processor = jsonProcessor; }, build: function () { processor === null || processor === void 0 ? void 0 : processor(this, jsonForProcessing, contextEntitiesForProcessing); return dict; }, }; } /** * A helper to build a Snowplow request from a set of name-value pairs, provided using the add methods. * Will base64 encode JSON, if desired, on build * * @returns The request builder, with add and build methods */ function payloadJsonProcessor(encodeBase64) { return function (payloadBuilder, jsonForProcessing, contextEntitiesForProcessing) { var add = function (json, keyIfEncoded, keyIfNotEncoded) { var str = JSON.stringify(json); if (encodeBase64) { payloadBuilder.add(keyIfEncoded, base64urlencode(str)); } else { payloadBuilder.add(keyIfNotEncoded, str); } }; var getContextFromPayload = function () { var payload = payloadBuilder.getPayload(); if (encodeBase64 ? payload.cx : payload.co) { return JSON.parse(encodeBase64 ? base64urldecode(payload.cx) : payload.co); } return undefined; }; var combineContexts = function (originalContext, newContext) { var context = originalContext || getContextFromPayload(); if (context) { context.data = context.data.concat(newContext.data); } else { context = newContext; } return context; }; var context = undefined; for (var _i = 0, jsonForProcessing_1 = jsonForProcessing; _i < jsonForProcessing_1.length; _i++) { var json = jsonForProcessing_1[_i]; if (json.keyIfEncoded === 'cx') { context = combineContexts(context, json.json); } else { add(json.json, json.keyIfEncoded, json.keyIfNotEncoded); } } jsonForProcessing.length = 0; if (contextEntitiesForProcessing.length) { var newContext = { schema: 'iglu:com.snowplowanalytics.snowplow/contexts/jsonschema/1-0-0', data: __spreadArray([], contextEntitiesForProcessing, true), }; context = combineContexts(context, newContext); contextEntitiesForProcessing.length = 0; } if (context) { add(context, 'cx', 'co'); } }; } /** * Is property a non-empty JSON? * @param property - Checks if object is non-empty json */ function isNonEmptyJson(property) { if (!isJson(property)) { return false; } for (var key in property) { if (Object.prototype.hasOwnProperty.call(property, key)) { return true; } } return false; } /** * Is property a JSON? * @param property - Checks if object is json */ function isJson(property) { return (typeof property !== 'undefined' && property !== null && (property.constructor === {}.constructor || property.constructor === [].constructor)); } /* * Copyright (c) 2022 Snowplow Analytics Ltd, 2010 Anthon Pang * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * 3. Neither the name of the copyright holder nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ var label = 'Snowplow: '; var LOG_LEVEL; (function (LOG_LEVEL) { LOG_LEVEL[LOG_LEVEL["none"] = 0] = "none"; LOG_LEVEL[LOG_LEVEL["error"] = 1] = "error"; LOG_LEVEL[LOG_LEVEL["warn"] = 2] = "warn"; LOG_LEVEL[LOG_LEVEL["debug"] = 3] = "debug"; LOG_LEVEL[LOG_LEVEL["info"] = 4] = "info"; })(LOG_LEVEL || (LOG_LEVEL = {})); var LOG = logger(); function logger(logLevel) { if (logLevel === void 0) { logLevel = LOG_LEVEL.warn; } function setLogLevel(level) { if (LOG_LEVEL[level]) { logLevel = level; } else { logLevel = LOG_LEVEL.warn; } } /** * Log errors, with or without error object */ function error(message, error) { var extraParams = []; for (var _i = 2; _i < arguments.length; _i++) { extraParams[_i - 2] = arguments[_i]; } if (logLevel >= LOG_LEVEL.error && typeof console !== 'undefined') { var logMsg = label + message + '\n'; if (error) { console.error.apply(console, __spreadArray([logMsg + '\n', error], extraParams, false)); } else { console.error.apply(console, __spreadArray([logMsg], extraParams, false)); } } } /** * Log warnings, with or without error object */ function warn(message, error) { var extraParams = []; for (var _i = 2; _i < arguments.length; _i++) { extraParams[_i - 2] = arguments[_i]; } if (logLevel >= LOG_LEVEL.warn && typeof console !== 'undefined') { var logMsg = label + message; if (error) { console.warn.apply(console, __spreadArray([logMsg + '\n', error], extraParams, false)); } else { console.warn.apply(console, __spreadArray([logMsg], extraParams, false)); } } } /** * Log debug messages */ function debug(message) { var extraParams = []; for (var _i = 1; _i < arguments.length; _i++) { extraParams[_i - 1] = arguments[_i]; } if (logLevel >= LOG_LEVEL.debug && typeof console !== 'undefined') { console.debug.apply(console, __spreadArray([label + message], extraParams, false)); } } /** * Log info messages */ function info(message) { var extraParams = []; for (var _i = 1; _i < arguments.length; _i++) { extraParams[_i - 1] = arguments[_i]; } if (logLevel >= LOG_LEVEL.info && typeof console !== 'undefined') { console.info.apply(console, __spreadArray([label + message], extraParams, false)); } } return { setLogLevel: setLogLevel, warn: warn, error: error, debug: debug, info: info }; } /* * Copyright (c) 2022 Snowplow Analytics Ltd, 2010 Anthon Pang * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * 3. Neither the name of the copyright holder nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** * Contains helper functions to aid in the addition and removal of Global Contexts */ function globalContexts() { var globalPrimitives = []; var conditionalProviders = []; var namedPrimitives = {}; var namedConditionalProviders = {}; /** * Returns all applicable global contexts for a specified event * @param event - The event to check for applicable global contexts for * @returns An array of contexts */ var assembleAllContexts = function (event) { var eventSchema = getUsefulSchema(event); var eventType = getEventType(event); var contexts = []; var generatedPrimitives = generatePrimitives(globalPrimitives.concat(Object.values(namedPrimitives)), event, eventType, eventSchema); contexts.push.apply(contexts, generatedPrimitives); var generatedConditionals = generateConditionals(conditionalProviders.concat(Object.values(namedConditionalProviders)), event, eventType, eventSchema); contexts.push.apply(contexts, generatedConditionals); return contexts; }; return { getGlobalPrimitives: function () { return globalPrimitives.concat(Object.values(namedPrimitives)); }, getConditionalProviders: function () { return conditionalProviders.concat(Object.values(namedConditionalProviders)); }, addGlobalContexts: function (contexts) { if (Array.isArray(contexts)) { var acceptedConditionalContexts = []; var acceptedContextPrimitives = []; for (var _i = 0, contexts_1 = contexts; _i < contexts_1.length; _i++) { var context = contexts_1[_i]; if (isConditionalContextProvider(context)) { acceptedConditionalContexts.push(context); } else if (isContextPrimitive(context)) { acceptedContextPrimitives.push(context); } } globalPrimitives = globalPrimitives.concat(acceptedContextPrimitives); conditionalProviders = conditionalProviders.concat(acceptedConditionalContexts); } else { for (var _a = 0, _b = Object.entries(contexts); _a < _b.length; _a++) { var _c = _b[_a], name_1 = _c[0], context = _c[1]; if (isConditionalContextProvider(context)) { namedConditionalProviders[name_1] = context; } else if (isContextPrimitive(context)) { namedPrimitives[name_1] = context; } } } }, clearGlobalContexts: function () { conditionalProviders = []; globalPrimitives = []; namedConditionalProviders = {}; namedPrimitives = {}; }, removeGlobalContexts: function (contexts) { var _loop_1 = function (context) { if (typeof context === 'string') { delete namedConditionalProviders[context]; delete namedPrimitives[context]; } else if (isConditionalContextProvider(context)) { conditionalProviders = conditionalProviders.filter(function (item) { return !compareProvider(context, item); }); } else if (isContextPrimitive(context)) { globalPrimitives = globalPrimitives.filter(function (item) { return !compareProvider(context, item); }); } }; for (var _i = 0, contexts_2 = contexts; _i < contexts_2.length; _i++) { var context = contexts_2[_i]; _loop_1(context); } }, getApplicableContexts: function (event) { return assembleAllContexts(event); }, }; } function pluginContexts(plugins) { /** * Add common contexts to every event * * @param array - additionalContexts List of user-defined contexts * @returns userContexts combined with commonContexts */ return { addPluginContexts: function (additionalContexts) { var combinedContexts = additionalContexts ? __spreadArray([], additionalContexts, true) : []; plugins.forEach(function (plugin) { try { if (plugin.contexts) { combinedContexts.push.apply(combinedContexts, plugin.contexts()); } } catch (ex) { LOG.error('Error adding plugin contexts', ex); } }); return combinedContexts; }, }; } /** * Find dynamic context generating functions and return their results to be merged into the static contexts * Combine an array of unchanging contexts with the result of a context-creating function * * @param dynamicOrStaticContexts - Array of custom context Objects or custom context generating functions * @param Parameters - to pass to dynamic context callbacks * @returns An array of Self Describing JSON context */ function resolveDynamicContext(dynamicOrStaticContexts) { var _a; var extraParams = []; for (var _i = 1; _i < arguments.length; _i++) { extraParams[_i - 1] = arguments[_i]; } return ((_a = dynamicOrStaticContexts === null || dynamicOrStaticContexts === void 0 ? void 0 : dynamicOrStaticContexts.map(function (context) { if (typeof context === 'function') { try { return context.apply(void 0, extraParams); } catch (e) { //TODO: provide warning return undefined; } } else { return context; } }).filter(Boolean)) !== null && _a !== void 0 ? _a : []); } /** * Slices a schema into its composite parts. Useful for ruleset filtering. * @param input - A schema string * @returns The vendor, schema name, major, minor and patch information of a schema string */ function getSchemaParts(input) { var re = new RegExp('^iglu:([a-zA-Z0-9-_.]+)/([a-zA-Z0-9-_]+)/jsonschema/([1-9][0-9]*)-(0|[1-9][0-9]*)-(0|[1-9][0-9]*)$'); var matches = re.exec(input); if (matches !== null) return matches.slice(1, 6); return undefined; } /** * Validates the vendor section of a schema string contains allowed wildcard values * @param parts - Array of parts from a schema string * @returns Whether the vendor validation parts are a valid combination */ function validateVendorParts(parts) { if (parts[0] === '*' || parts[1] === '*') { return false; // no wildcard in first or second part } if (parts.slice(2).length > 0) { var asterisk = false; for (var _i = 0, _a = parts.slice(2); _i < _a.length; _i++) { var part = _a[_i]; if (part === '*') // mark when we've found a wildcard asterisk = true; else if (asterisk) // invalid if alpha parts come after wildcard return false; } return true; } else if (parts.length == 2) return true; return false; } /** * Validates the vendor part of a schema string is valid for a rule set * @param input - Vendor part of a schema string * @returns Whether the vendor validation string is valid */ function validateVendor(input) { var parts = input.split('.'); if (parts && parts.length > 1) return validateVendorParts(parts); return false; } /** * Checks for validity of input and returns all the sections of a schema string that are used to match rules in a ruleset * @param input - A Schema string * @returns The sections of a schema string that are used to match rules in a ruleset */ function getRuleParts(input) { var re = new RegExp('^iglu:((?:(?:[a-zA-Z0-9-_]+|\\*).)+(?:[a-zA-Z0-9-_]+|\\*))/([a-zA-Z0-9-_.]+|\\*)/jsonschema/([1-9][0-9]*|\\*)-(0|[1-9][0-9]*|\\*)-(0|[1-9][0-9]*|\\*)$'); var matches = re.exec(input); if (matches !== null && validateVendor(matches[1])) return matches.slice(1, 6); return undefined; } /** * Ensures the rules specified in a schema string of a ruleset are valid * @param input - A Schema string * @returns if there rule is valid */ function isValidRule(input) { var ruleParts = getRuleParts(input); if (ruleParts) { var vendor = ruleParts[0]; return ruleParts.length === 5 && validateVendor(vendor); } return false; } /** * Check if a variable is an Array containing only strings * @param input - The variable to validate * @returns True if the input is an array containing only strings */ function isStringArray(input) { return (Array.isArray(input) && input.every(function (x) { return typeof x === 'string'; })); } /** * Validates whether a rule set is an array of valid ruleset strings * @param input - The Array of rule set arguments * @returns True is the input is an array of valid rules */ function isValidRuleSetArg(input) { if (isStringArray(input)) return input.every(function (x) { return isValidRule(x); }); else if (typeof input === 'string') return isValidRule(input); return false; } /** * Check if a variable is a valid, non-empty Self Describing JSON * @param input - The variable to validate * @returns True if a valid Self Describing JSON */ function isSelfDescribingJson(input) { var sdj = input; if (isNonEmptyJson(sdj)) if ('schema' in sdj && 'data' in sdj) return typeof sdj.schema === 'string' && typeof sdj.data === 'object'; return false; } /** * Validates if the input object contains the expected properties of a ruleset * @param input - The object containing a rule set * @returns True if a valid rule set */ function isRuleSet(input) { var ruleSet = input; var ruleCount = 0; if (input != null && typeof input === 'object' && !Array.isArray(input)) { if (Object.prototype.hasOwnProperty.call(ruleSet, 'accept')) { if (isValidRuleSetArg(ruleSet['accept'])) { ruleCount += 1; } else { return false; } } if (Object.prototype.hasOwnProperty.call(ruleSet, 'reject')) { if (isValidRuleSetArg(ruleSet['reject'])) { ruleCount += 1; } else { return false; } } // if either 'reject' or 'accept' or both exists, // we have a valid ruleset return ruleCount > 0 && ruleCount <= 2; } return false; } /** * Validates if the function can be a valid context generator function * @param input - The function to be validated */ function isContextCallbackFunction(input) { return typeof input === 'function' && input.length <= 1; } /** * Validates if the function can be a valid context primitive function or self describing json * @param input - The function or orbject to be validated * @returns True if either a Context Generator or Self Describing JSON */ function isContextPrimitive(input) { return isContextCallbackFunction(input) || isSelfDescribingJson(input); } /** * Validates if an array is a valid shape to be a Filter Provider * @param input - The Array of Context filter callbacks */ function isFilterProvider(input) { if (Array.isArray(input)) { if (input.length === 2) { if (Array.isArray(input[1])) { return isContextCallbackFunction(input[0]) && input[1].every(isContextPrimitive); } return isContextCallbackFunction(input[0]) && isContextPrimitive(input[1]); } } return false; } /** * Validates if an array is a valid shape to be an array of rule sets * @param input - The Array of Rule Sets */ function isRuleSetProvider(input) { if (Array.isArray(input) && input.length === 2) { if (!isRuleSet(input[0])) return false; if (Array.isArray(input[1])) return input[1].every(isContextPrimitive); return isContextPrimitive(input[1]); } return false; } /** * Checks if an input array is either a filter provider or a rule set provider * @param input - An array of filter providers or rule set providers * @returns Whether the array is a valid {@link ConditionalContextProvider} */ function isConditionalContextProvider(input) { return isFilterProvider(input) || isRuleSetProvider(input); } /** * Checks if a given schema matches any rules within the provided rule set * @param ruleSet - The rule set containing rules to match schema against * @param schema - The schema to be matched against the rule set */ function matchSchemaAgainstRuleSet(ruleSet, schema) { var rejectCount = 0; var acceptCount = 0; var acceptRules = ruleSet['accept']; if (Array.isArray(acceptRules)) { if (ruleSet.accept.some(function (rule) { return matchSchemaAgainstRule(rule, schema); })) { acceptCount++; } } else if (typeof acceptRules === 'string') { if (matchSchemaAgainstRule(acceptRules, schema)) { acceptCount++; } } var rejectRules = ruleSet['reject']; if (Array.isArray(rejectRules)) { if (ruleSet.reject.some(function (rule) { return matchSchemaAgainstRule(rule, schema); })) { rejectCount++; } } else if (typeof rejectRules === 'string') { if (matchSchemaAgainstRule(rejectRules, schema)) { rejectCount++; } } if (acceptCount > 0 && rejectCount === 0) { return true; } else if (acceptCount === 0 && rejectCount > 0) { return false; } return false; } /** * Checks if a given schema matches a specific rule from a rule set * @param rule - The rule to match schema against * @param schema - The schema to be matched against the rule */ function matchSchemaAgainstRule(rule, schema) { if (!isValidRule(rule)) return false; var ruleParts = getRuleParts(rule); var schemaParts = getSchemaParts(schema); if (ruleParts && schemaParts) { if (!matchVendor(ruleParts[0], schemaParts[0])) return false; for (var i = 1; i < 5; i++) { if (!matchPart(ruleParts[i], schemaParts[i])) return false; } return true; // if it hasn't failed, it passes } return false; } function matchVendor(rule, vendor) { // rule and vendor must have same number of elements var vendorParts = vendor.split('.'); var ruleParts = rule.split('.'); if (vendorParts && ruleParts) { if (vendorParts.length !== ruleParts.length) return false; for (var i = 0; i < ruleParts.length; i++) { if (!matchPart(vendorParts[i], ruleParts[i])) return false; } return true; } return false; } function matchPart(rule, schema) { // parts should be the string nested between slashes in the URI: /example/ return (rule && schema && rule === '*') || rule === schema; } // Returns the "useful" schema, i.e. what would someone want to use to identify events. // For some events this is the 'e' property but for unstructured events, this is the // 'schema' from the 'ue_px' field. function getUsefulSchema(sb) { var eventJson = sb.getJson(); for (var _i = 0, eventJson_1 = eventJson; _i < eventJson_1.length; _i++) { var json = eventJson_1[_i]; if (json.keyIfEncoded === 'ue_px' && typeof json.json['data'] === 'object') { var schema = json.json['data']['schema']; if (typeof schema == 'string') { return schema; } } } return ''; } function getEventType(payloadBuilder) { var eventType = payloadBuilder.getPayload()['e']; return typeof eventType === 'string' ? eventType : ''; } function buildGenerator(generator, event, eventType, eventSchema) { var contextGeneratorResult = undefined; try { // try to evaluate context generator var args = { event: event.getPayload(), eventType: eventType, eventSchema: eventSchema, }; contextGeneratorResult = generator(args); // determine if the produced result is a valid SDJ if (Array.isArray(contextGeneratorResult) && contextGeneratorResult.every(isSelfDescribingJson)) { return contextGeneratorResult; } else if (isSelfDescribingJson(contextGeneratorResult)) { return contextGeneratorResult; } else { return undefined; } } catch (error) { contextGeneratorResult = undefined; } return contextGeneratorResult; } function normalizeToArray(input) { if (Array.isArray(input)) { return input; } return Array.of(input); } function generatePrimitives(contextPrimitives, event, eventType, eventSchema) { var _a; var normalizedInputs = normalizeToArray(contextPrimitives); var partialEvaluate = function (primitive) { var result = evaluatePrimitive(primitive, event, eventType, eventSchema); if (result && result.length !== 0) { return result; } return undefined; }; var generatedContexts = normalizedInputs.map(partialEvaluate); return (_a = []).concat.apply(_a, generatedContexts.filter(function (c) { return c != null && c.filter(Boolean); })); } function evaluatePrimitive(contextPrimitive, event, eventType, eventSchema) { if (isSelfDescribingJson(contextPrimitive)) { return [contextPrimitive]; } else if (isContextCallbackFunction(contextPrimitive)) { var generatorOutput = buildGenerator(contextPrimitive, event, eventType, eventSchema); if (isSelfDescribingJson(generatorOutput)) { return [generatorOutput]; } else if (Array.isArray(generatorOutput)) { return generatorOutput; } } return undefined; } function evaluateProvider(provider, event, eventType, eventSchema) { if (isFilterProvider(provider)) { var filter = provider[0]; var filterResult = false; try { var args = { event: event.getPayload(), eventType: eventType, eventSchema: eventSchema, }; filterResult = filter(args); } catch (error) { filterResult = false; } if (filterResult === true) { return generatePrimitives(provider[1], event, eventType, eventSchema); } } else if (isRuleSetProvider(provider)) { if (matchSchemaAgainstRuleSet(provider[0], eventSchema)) { return generatePrimitives(provider[1], event, eventType, eventSchema); } } return []; } function compareProviderPart(a, b) { if (typeof a === 'function') return a === b; return JSON.stringify(a) === JSON.stringify(b); } function compareProvider(a, b) { if (isConditionalContextProvider(a)) { if (!isConditionalContextProvider(b)) return false; var ruleA = a[0], primitivesA = a[1]; var ruleB = b[0], primitivesB_1 = b[1]; if (!compareProviderPart(ruleA, ruleB)) return false; if (Array.isArray(primitivesA)) { if (!Array.isArray(primitivesB_1)) return false; if (primitivesA.length !== primitivesB_1.length) return false; return primitivesA.reduce(function (matches, a, i) { return matches && compareProviderPart(a, primitivesB_1[i]); }, true); } else { if (Array.isArray(primitivesB_1)) return false; return compareProviderPart(primitivesA, primitivesB_1); } } else if (isContextPrimitive(a)) { if (!isContextPrimitive(b)) return false; return compareProviderPart(a, b); } return false; } function generateConditionals(providers, event, eventType, eventSchema) { var _a; var normalizedInput = normalizeToArray(providers); var partialEvaluate = function (provider) { var result = evaluateProvider(provider, event, eventType, eventSchema); if (result && result.length !== 0) { return result; } return undefined; }; var generatedContexts = normalizedInput.map(partialEvaluate); return (_a = []).concat.apply(_a, generatedContexts.filter(function (c) { return c != null && c.filter(Boolean); })); } /** * Create a new EventStorePayload */ function newEventStorePayload(_a) { var payload = _a.payload, _b = _a.svrAnon, svrAnon = _b === void 0 ? false : _b; return { payload: payload, svrAnon: svrAnon, }; } /* * Copyright (c) 2022 Snowplow Analytics Ltd, 2010 Anthon Pang * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * 3. Neither the name of the copyright holder nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** * Transform optional/old-behavior number timestamp into`Timestamp` ADT * * @param timestamp - optional number or timestamp object * @returns correct timestamp object */ function getTimestamp(timestamp) { if (timestamp == null) { return { type: 'dtm', value: new Date().getTime() }; } else if (typeof timestamp === 'number') { return { type: 'dtm', value: timestamp }; } else if (timestamp.type === 'ttm') { // We can return timestamp here, but this is safer fallback return { type: 'ttm', value: timestamp.value }; } else { return { type: 'dtm', value: timestamp.value || new Date().getTime() }; } } /** * Create a tracker core object * * @param base64 - Whether to base 64 encode contexts and self describing event JSONs * @param corePlugins - The core plugins to be processed with each event * @param callback - Function applied to every payload dictionary object * @returns Tracker core */ function trackerCore(configuration) { if (configuration === void 0) { configuration = {}; } function newCore(base64, corePlugins, callback) { var pluginContextsHelper = pluginContexts(corePlugins), globalContextsHelper = globalContexts(); var encodeBase64 = base64, payloadPairs = {}; // Dictionary of key-value pairs which get added to every payload, e.g. tracker version /** * Wraps an array of custom contexts in a self-describing JSON * * @param contexts - Array of custom context self-describing JSONs * @returns Outer JSON */ function completeContexts(contexts) { if (contexts && contexts.length) { return { schema: 'iglu:com.snowplowanalytics.snowplow/contexts/jsonschema/1-0-0', data: contexts, }; } return undefined; } /** * Adds all global contexts to a contexts array * * @param pb - PayloadData * @param contexts - Custom contexts relating to the event */ function attachGlobalContexts(pb, contexts) { var applicableContexts = globalContextsHelper.getApplicableContexts(pb); var returnedContexts = []; if (contexts && contexts.length) { returnedContexts.push.apply(returnedContexts, contexts); } if (applicableContexts && applicableContexts.length) { returnedContexts.push.apply(returnedContexts, applicableContexts); } return returnedContexts; } /** * Gets called by every trackXXX method * Adds context and payloadPairs name-value pairs to the payload * Applies the callback to the built payload * * @param pb - Payload * @param context - Custom contexts relating to the event * @param timestamp - Timestamp of the event * @returns Payload after the callback is applied or undefined if the event is skipped */ function track(pb, context, timestamp) { if (!active) { LOG.error('Track called on deactivated tracker'); return undefined; } pb.withJsonProcessor(payloadJsonProcessor(encodeBase64)); pb.add('eid', v4()); pb.addDict(payloadPairs); var tstamp = getTimestamp(timestamp); pb.add(tstamp.type, tstamp.value.toString()); var allContexts = attachGlobalContexts(pb, pluginContextsHelper.addPluginContexts(context)); var wrappedContexts = completeContexts(allContexts); if (wrappedContexts !== undefined) { pb.addJson('cx', 'co', wrappedContexts); } corePlugins.forEach(function (plugin) { try { if (plugin.beforeTrack) { plugin.beforeTrack(pb); } } catch (ex) { LOG.error('Plugin beforeTrack', ex); } }); // Call the filter on plugins to determine if the event should be tracked var skip = corePlugins.find(function (plugin) { try { return plugin.filter && plugin.filter(pb.build()) === false; } catch (ex) { LOG.error('Plugin filter', ex); return false; } }); if (skip) { return undefined; } if (typeof callback === 'function') { callback(pb); } var finalPayload = pb.build(); corePlugins.forEach(function (plugin) { try { if (plugin.afterTrack) { plugin.afterTrack(finalPayload); } } catch (ex) { LOG.error('Plugin afterTrack', ex); } }); return finalPayload; } /** * Set a persistent key-value pair to be added to every payload * * @param key - Field name * @param value - Field value */ function addPayloadPair(key, value) { payloadPairs[key] = value; } var core = { track: track, addPayloadPair: addPayloadPair, getBase64Encoding: function () { return encodeBase64; }, setBase64Encoding: function (encode) { encodeBase64 = encode; }, addPayloadDict: function (dict) { for (var key in dict) { if (Object.prototype.hasOwnProperty.call(dict, key)) { payloadPairs[key] = dict[key]; } } }, resetPayloadPairs: function (dict) { payloadPairs = isJson(dict) ? dict : {}; }, setTrackerVersion: function (version) { addPayloadPair('tv', version); }, setTrackerNamespace: function (name) { addPayloadPair('tna', name); }, setAppId: function (appId) { addPayloadPair('aid', appId); }, setPlatform: function (value) { addPayloadPair('p', value); }, setUserId: function (userId) { addPayloadPair('uid', userId); }, setScreenResolution: function (width, height) { addPayloadPair('res', width + 'x' + height); }, setViewport: function (width, height) { addPayloadPair('vp', width + 'x' + height); }, setColorDepth: function (depth) { addPayloadPair('cd', depth); }, setTimezone: function (timezone) { addPayloadPair('tz', timezone); }, setLang: function (lang) { addPayloadPair('lang', lang); }, setIpAddress: function (ip) { addPayloadPair('ip', ip); }, setUseragent: function (useragent) { addPayloadPair('ua', useragent); }, addGlobalContexts: function (contexts) { globalContextsHelper.addGlobalContexts(contexts); }, clearGlobalContexts: function () { globalContextsHelper.clearGlobalContexts(); }, removeGlobalContexts: function (contexts) { globalContextsHelper.removeGlobalContexts(contexts); }, }; return core; } var active = true; var base64 = configuration.base64, corePlugins = configuration.corePlugins, callback = configuration.callback, plugins = corePlugins !== null && corePlugins !== void 0 ? corePlugins : [], partialCore = newCore(base64 !== null && base64 !== void 0 ? base64 : true, plugins, callback), core = __assign(__assign({}, partialCore), { addPlugin: function (configuration) { var _a, _b; var plugin = configuration.plugin; plugins.push(plugin); (_a = plugin.logger) === null || _a === void 0 ? void 0 : _a.call(plugin, LOG); (_b = plugin.activateCorePlugin) === null || _b === void 0 ? void 0 : _b.call(plugin, core);