@snowplow/tracker-core
Version:
Core functionality for Snowplow JavaScript trackers
1,297 lines (1,287 loc) • 96.2 kB
JavaScript
/*!
* 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
*/
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
var tslib = require('tslib');
var uuid = require('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: tslib.__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: ';
exports.LOG_LEVEL = void 0;
(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";
})(exports.LOG_LEVEL || (exports.LOG_LEVEL = {}));
var LOG = logger();
function logger(logLevel) {
if (logLevel === void 0) { logLevel = exports.LOG_LEVEL.warn; }
function setLogLevel(level) {
if (exports.LOG_LEVEL[level]) {
logLevel = level;
}
else {
logLevel = exports.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 >= exports.LOG_LEVEL.error && typeof console !== 'undefined') {
var logMsg = label + message + '\n';
if (error) {
console.error.apply(console, tslib.__spreadArray([logMsg + '\n', error], extraParams, false));
}
else {
console.error.apply(console, tslib.__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 >= exports.LOG_LEVEL.warn && typeof console !== 'undefined') {
var logMsg = label + message;
if (error) {
console.warn.apply(console, tslib.__spreadArray([logMsg + '\n', error], extraParams, false));
}
else {
console.warn.apply(console, tslib.__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 >= exports.LOG_LEVEL.debug && typeof console !== 'undefined') {
console.debug.apply(console, tslib.__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 >= exports.LOG_LEVEL.info && typeof console !== 'undefined') {
console.info.apply(console, tslib.__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
? tslib.__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', uuid.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 = tslib.__assign(tslib.__assign({}, partialCore), { addPlugin: function (configuration) {
var _a, _b;
var plugin = configuration.plugin;
plugins.push(plugin);
(_a