fiftyone.pipeline.javascriptbuilder.templates.JavaScriptResource.mustache Maven / Gradle / Ivy
The newest version!
fiftyoneDegreesManager = function() {
'use-strict';
var json = {{&_jsonObject}};
var parameters = {{&_parameters}};
var sessionId = "{{&_sessionId}}";
var sessionKey = "{{_objName}}_" + sessionId;
this.sessionId = sessionId;
var sequence = {{&_sequence}};
// Log any errors returned in the JSON object.
if(json.error !== undefined){
console.log(json.error);
}
// Log any warnings returned in the JSON object.
if (json.warnings !== undefined) {
console.log(json.warnings);
}
// Set to true when the JSON object is complete.
var completed = false;
// Set to true when the `catchError` is called.
let failed = false;
// changeFuncs is an array of functions. When onChange is called and passed
// a function, the function is registered and is called when processing is
// complete.
var changeFuncs = [];
// Counter is used to count how many pieces of callbacks are expected. Every
// time the completedCallback method is called, the counter is decremented
// by 1.
var callbackCounter = 0;
// Array of JavaScript properties that have started evaluation.
var jsPropertiesStarted = [];
// startsWith polyfill.
var startsWith = function(source, searchValue) {
return source.lastIndexOf(searchValue, 0) === 0;
}
// endsWith polyfill.
var endsWith = function(source, searchValue) {
return source.substring(source.length - searchValue.length, source.length) === searchValue;
}
var clearCache = function() {
if (sessionStorage) {
for (i = 0; i < sessionStorage.length; i++) {
key = sessionStorage.key(i);
if (startsWith(key, sessionKey)) {
sessionStorage.removeItem(key);
}
}
}
}
var loadParameters = function() {
if (sessionStorage) {
var parametersString = sessionStorage.getItem(sessionKey + "_parameters");
if (parametersString) {
parameters = JSON.parse(parametersString);
}
}
return parameters;
}
var saveParameters = function(sourceParams) {
if (sourceParams) {
parameters = sourceParams
}
if (sessionStorage) {
var parametersString = JSON.stringify(parameters);
sessionStorage.setItem(sessionKey + "_parameters", parametersString);
}
}
// Get stored values with the '51D_' prefix that have been added to the request
// and return the data as key value pairs. This method is needed to extract
// stored values for inclusion in the GET or POST request for situations
// where CORS will prevent them from being sent to third parties.
var getFodSavedValues = function() {
let fodValues = {};
{{#_enableCookies}}
{
let keyValuePairs = document.cookie.split(/; */);
for(let nextPair of keyValuePairs) {
let firstEqualsLocation = nextPair.indexOf("=");
let name = nextPair.substring(0, firstEqualsLocation);
if(startsWith(name, "51D_")){
let value = nextPair.substring(firstEqualsLocation+1);
fodValues[name] = value;
}
}
};
{{/_enableCookies}}
{{^_enableCookies}}
{
// Collect values from session storage
let session51DataPrefix = sessionKey + "_data_";
for(let i = 0, n = window.sessionStorage.length; i < n; ++i) {
let nextKey = window.sessionStorage.key(i);
if(startsWith(nextKey, session51DataPrefix)){
let value = window.sessionStorage[nextKey];
fodValues[nextKey.substring(session51DataPrefix.length)] = value;
}
}
};
{{/_enableCookies}}
return fodValues;
};
// Extract key value pairs from the '51D_' prefixed values and concatenates
// them to form a query string for the subsequent json refresh.
var getParametersFromStorage = function() {
var fodValues = getFodSavedValues();
var keyValuePairs = [];
for (var key in fodValues) {
if (fodValues.hasOwnProperty(key)) {
// Url encode the value.
// This is done to ensure that invalid characters (e.g. = chars at the end of
// base 64 encoded strings) reach the server intact.
// The server will automatically decode the value before passing it into the
// Pipeline API.
keyValuePairs.push(key + "=" + encodeURIComponent(fodValues[key]));
}
}
return keyValuePairs;
};
// Fetch a value safely from the json object. If a key somewhere down the
// '.' separated hierarchy of keys is not present then 'undefined' is
// returned rather than letting an exception occur.
var getFromJson = function(key, allowObjects, allowBooleans) {
var result = undefined;
if (typeof allowObjects === "undefined") { allowObjects = false; }
if (typeof allowBooleans === "undefined") { allowBooleans = false; }
if (typeof(key) === "string") {
var functions = json;
var segments = key.split('.');
var i = 0;
while (functions !== undefined && i < segments.length) {
functions = functions[segments[i++]];
}
if (typeof functions === "string") {
result = functions;
} else if (allowBooleans && typeof functions === "boolean") {
result = functions;
} else if (allowObjects && typeof functions === "object" && functions !== null) {
result = functions;
}
}
return result;
}
// Executed at the end of the processJSproperties method or for each piece
// of JavaScript which has 51D code injected. When there are 0 pieces of
// JavaScript left to process then reload the JSON object.
var completedCallback = function(resolve, reject){
callbackCounter--;
if (callbackCounter === 0) {
{{#_updateEnabled}}
processRequest(resolve, reject);
{{/_updateEnabled}}
} else if (callbackCounter < 0){
reject('Too many callbacks.');
}
}
// Executes any JavaScript contained in the JSON data. Session storage is
// used to check the process state of the JavaScript property, if the name
// of the property exists as a key then it has been processed. If all the
// processed JavaScript properties have been flagged as processed already
// then session storage is checked again for a JSON payload. If it exists
// then this is loaded into the managers internal data store. If not or if
// JavaScript properties have been processed then the call-back is
// processed with any new evidence produced by the JavaScript properties.
// If JavaScript properties are processed then a key containing the name of
// the JavaScript property is added to session storage. The complete flag is
// set to true when there is no further JavaScript to be processed.
var processJsProperties = function(resolve, reject, jsProperties, ignoreDelayFlag) {
var executeCallback = true;
var started = 0;
var cached = 0;
var toProcess = 0;
// If there is no cached response and there are JavaScript code snippets
// then process them and perform any call-backs required.
if (jsProperties !== undefined && jsProperties.length > 0) {
{{^_enableCookies}}
let valueSetPrefix = new RegExp('document\\.cookie\\s*=\\s*(("([A-Za-z0-9_"\\s\\+]+)\\s*=\\s*"\\s*\\+\\s*([^\\s};]+))|(`([A-Za-z0-9_]+)\\s*=\\s*\\$\\{([^}]+)\\}`))', 'g');
let session51DataPrefix = sessionKey + "_data_";
let sessionSetPatch = 'window.sessionStorage["' + session51DataPrefix + '$3$6"]=$4$7';
{{/_enableCookies}}
// Execute each of the JavaScript property code snippets using the
// index of the value to access the value to avoid problems with
// JavaScript returning erroneous values.
for (var index = 0; index < jsProperties.length; index++) {
var name = jsProperties[index];
if (jsPropertiesStarted.indexOf(name) !== -1) {
continue;
}
var body = getFromJson(name);
// If there is a body then this property should be processed.
if (body) {
toProcess++;
}
var isCached = sessionStorage && sessionStorage.getItem(sessionKey + "_property_" + name);
// If the property has already been processed then skip it.
if (isCached) {
cached++;
continue;
}
// Create new function bound to this instance and execute it.
// This is needed to ensure the scope of the function is
// associated with this instance if any members are altered or
// added. Avoids global scoped variables.
var delay = getFromJson(name + 'delayexecution', false, true);
if (
(ignoreDelayFlag || delay === undefined || delay === false) &&
typeof body === "string" &&
body.length
) {
var func = undefined;
var searchString = '// 51D replace this comment with callback function.';
completed = false;
jsPropertiesStarted.push(name);
started++;
{{^_enableCookies}}
body = body.replaceAll(valueSetPrefix, sessionSetPatch);
{{/_enableCookies}}
if (body.indexOf(searchString) !== -1){
callbackCounter++;
body = body.replace(/\/\/ 51D replace this comment with callback function./g, 'callbackFunc(resolveFunc, rejectFunc);');
func = new Function('callbackFunc', 'resolveFunc', 'rejectFunc',
"try {\n" +
body + "\n" +
"} catch (err) {\n" +
"console.log(err);" +
"}"
);
func(completedCallback, resolve, reject);
executeCallback = false;
} else {
func = new Function(
"try {\n" +
body + "\n" +
"} catch (err) {\n" +
"console.log(err);" +
"}"
);
func();
}
if (sessionStorage) {
sessionStorage.setItem(sessionKey + "_property_" + name, true)
}
// If the property is `javascripthardwareprofile` then check if the
// profile has been set. If not then remove current property from
// the list of properties that have started to prevent the
// 2nd request from being made.
if (name === "device.javascripthardwareprofile") {
var hrw = getFodSavedValues();
if (hrw && !hrw["51D_ProfileIds"]) {
// find and remove name from jsPropertiesStarted
var propIndex = jsPropertiesStarted.indexOf(name);
if (propIndex > -1) {
jsPropertiesStarted.splice(index, 1);
}
started--;
toProcess--;
}
}
}
}
}
if ((cached === toProcess || started === 0) && sessionStorage) {
var cachedResponse = sessionStorage.getItem(sessionKey);
if (cachedResponse) {
loadJSON(resolve, reject, cachedResponse);
executeCallback = false;
}
}
if (started === 0) {
executeCallback = false;
failed = false;
completed = true;
}
if (executeCallback) {
callbackCounter = 1;
completedCallback(resolve, reject);
}
};
{{#_updateEnabled}}
{{^_supportsFetch}}
// Standard method to create a CORS HTTP request ready to send data.
var createCORSRequest = function(method, url) {
var xhr;
try {
xhr = new XMLHttpRequest();
} catch(err){
xhr = null;
}
if (xhr !== null && "withCredentials" in xhr) {
// Check if the XMLHttpRequest object has a "withCredentials"
// property.
// "withCredentials" only exists on XMLHTTPRequest2 objects.
xhr.open(method, url, true);
} else if (typeof XDomainRequest != "undefined") {
// Otherwise, check if XDomainRequest.
// XDomainRequest only exists in IE, and is IE's way of making CORS
// requests.
xhr = new XDomainRequest();
xhr.open(method, url);
} else {
// Otherwise, CORS is not supported by the browser.
xhr = null;
}
return xhr;
};
{{/_supportsFetch}}
{{/_updateEnabled}}
// Check if the JSON object still has any JavaScript snippets to run.
var hasJSFunctions = function() {
for (var i = i; i < json.javascriptProperties; i++) {
var body = getFromJson(json.javascriptProperties[i]);
if (body !== undefined && body.length > 0) {
return true;
}
}
return false;
}
// Process the JavaScript properties.
var process = function(resolve, reject){
processJsProperties(resolve, reject, json.javascriptProperties, false);
}
var fireChangeFuncs = function(json) {
for (var i = 0; i < changeFuncs.length; i++) {
if (typeof changeFuncs[i] === 'function' &&
changeFuncs[i].length === 1) {
changeFuncs[i](json);
}
}
}
{{#_updateEnabled}}
// Process the response as json and call the resolve method.
var loadJSON = function(resolve, reject, responseText) {
try {
json = JSON.parse(responseText);
} catch(err) {
clearCache();
reject(new Error("Invalid JSON - the endpoint is likely setup incorrectly", { cause: err }));
return;
}
if (hasJSFunctions()) {
// json updated so fire 'on change' functions
// before executing any new JS properties that
// have come back.
fireChangeFuncs(json);
process(resolve, reject);
} else {
failed = false;
completed = true;
// json updated so fire 'on change' functions
// This must happen after completed = true in order
// for 'complete' functions to fire.
fireChangeFuncs(json);
resolve(json);
}
}
// Sends a POST request to the call-back URL to retrieve and updated
// JSON payload from the cloud service. A POST request is used so that
// parameters can be passed in the request body, this is to get around
// the limitations on the length of query strings. As POST requests are not
// cached by browsers, the result of the POST request is stored in session
// storage on a successful response. This can then be checked before making
// repeat requests to the call-back URL.
// Any saved value parameters that have been set by the executed JavaScript
// properties are added to the list of parameters, this list is then
// serialized as Form Data and sent in the POST body to the call-back URL,
// refreshing the JSON data. The new JSON is then loaded if the request is
// returned with a success status code. If there was a problem then the
// session storage items are invalidated and reject is called.
var processRequest = function(resolve, reject){
loadParameters();
// Get additional parameters in case they are not sent
// by the browser.
var savedValueParams = getParametersFromStorage();
for(var savedValueIndex in savedValueParams) {
var parts = savedValueParams[savedValueIndex].split('=');
parameters[parts[0]] = parts[1];
}
saveParameters();
var params = [];
for (var param in parameters) {
if (parameters.hasOwnProperty(param)) {
params.push(param+"="+parameters[param])
}
}
// Add the session and sequence to the request
if (sessionId) {
params.push("session-id=" + sessionId);
}
if (sequence) {
params.push("sequence=" + sequence);
}
var postBody = "";
if (params.length > 0) {
postBody = params.join('&').replace(/%20/g, '+');
}
{{#_supportsFetch}}
fetch('{{{_url}}}', {
method: 'POST',
mode: 'cors',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: postBody
})
.then(response => {
return response.text();
})
.then(responseText => {
{{/_supportsFetch}}
{{^_supportsFetch}}
// Request callback URL with additional parameters.
var xhr = createCORSRequest('POST', '{{{_url}}}');
// If there is no support for HTTP requests then call reject and throw
// a no CORS support error.
if (!xhr) {
reject(new Error('CORS not supported'));
return;
}
// Add the HTTP header for POST form data.
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.setRequestHeader('Accept', 'application/json');
xhr.onload = function () {
// Get the response body from the request.
var responseText = xhr.responseText;
{{/_supportsFetch}}
// Cache the response text.
if (sessionStorage) {
sessionStorage.setItem(sessionKey, responseText);
}
// Load the JSON object from the response text
loadJSON(resolve, reject, responseText);
// Increment the sequence on a successful request
sequence++;
{{#_supportsFetch}}
})
.catch(error => {
// Invalidate the cache on error.
clearCache();
reject(error);
});
{{/_supportsFetch}}
{{^_supportsFetch}}
};
xhr.onerror = function () {
// An error occurred with the request. Invalidate the cache and
// return the details in the call to reject method.
clearCache();
reject(Error(xhr.statusText));
};
// Send the GET request.
xhr.send(postBody);
{{/_supportsFetch}}
}
{{/_updateEnabled}}
// Function logs errors, used to 'reject' a promise or for error callbacks.
var catchError = function(value) {
failed = true;
function errorToDesc(subError) {
let msg = subError.message;
if (!msg) {
msg = String(subError);
}
let cause = subError.cause;
if (cause) {
msg += '\n--- caused by ---\n';
msg += errorToDesc(cause);
}
return msg;
}
let errorDesc = errorToDesc(value);
console.log(errorDesc);
if (json.errors === null || json.errors === undefined) {
json.errors = [errorDesc];
} else if (Array.isArray(json.errors)) {
json.errors.push(errorDesc);
}
fireChangeFuncs(json);
}
// Populate this instance of the FOD object with getters to access the
// properties. If the value is null then get the noValueMessage from the
// JSON object corresponding to the property.
var update = function(data){
var self = this;
Object.getOwnPropertyNames(data).forEach(function(key) {
self[key] = {};
for(var i in data[key]){
var obj = self[key];
(function(i) {
Object.defineProperty(obj, i, {
get: function (){
if(data[key][i] === null && (i !== "javascriptProperties")){
return data[key][i + "nullreason"];
} else {
return data[key][i];
}
}
})
})(i);
}
});
}
{{#_hasDelayedProperties}}
// Get the JS property(s) that, when evaluated, will populate
// evidence that can be used to determine the value of the
// supplied property.
// The supplied name can either be a complete property name or a top level
// aspect name.
// Where the aspect name is given, ALL evidence properties under that
// key will be returned.
// Example property names are 'location.country' or 'devices.profiles.hardwarename'
// Example aspect names are 'location' or 'devices'
var getEvidenceProperties = function (name) {
var evidenceProperties = getFromJson(name + 'evidenceproperties');
if(typeof evidenceProperties === "undefined") {
var item = getFromJson(name, true);
evidenceProperties = getEvidencePropertiesFromObject(item);
}
return evidenceProperties;
}
// Get all values in any 'evidenceproperty' fields on this object
// or sub-objects.
var getEvidencePropertiesFromObject = function (dataObject) {
evidenceProperties = [];
for (var prop in dataObject) {
if (dataObject.hasOwnProperty(prop)) {
var value = dataObject[prop];
// Property name ends with 'evidenceproperties' so is
// what we're looking for.
// Add the values to the array if we don't already have it.
if (value !== null && Array.isArray(value) && endsWith(prop, 'evidenceproperties')) {
value.forEach(function(item, index) {
if(evidenceProperties.indexOf(item) === -1) {
evidenceProperties.push(item);
}
});
}
// Item is an object so recursively call this method
// and add any resulting evidence properties to the list.
else if(typeof value === 'object' && value !== null) {
getEvidencePropertiesFromObject(value).forEach(function(item, index) {
if(evidenceProperties.indexOf(item) === -1) {
evidenceProperties.push(item);
}
});
}
}
}
return evidenceProperties;
}
{{/_hasDelayedProperties}}
{{#_supportsPromises}}
this.promise = new Promise(function(resolve, reject) {
process(resolve,reject);
});
{{/_supportsPromises}}
this.onChange = function(resolve) {
changeFuncs.push(resolve);
}
this.complete = function(resolve, properties) {
{{#_hasDelayedProperties}}
// If properties is set then check if we need to kick off
// processing of anything.
if(typeof properties !== "undefined") {
// If properties is a string then split on comma to produce
// an array of one or more key names.
if(typeof properties === "string") {
properties = properties.split(',');
}
if(Array.isArray(properties)) {
properties.forEach(function(key, i) {
// We pass an empty function rather than 'resolve' because we
// don't want to call resolve when a single evidence function
// evaluates but after all of them have completed.
// This is handled by the 'if(complete)' code below.
processJsProperties(function(json) {}, catchError, getEvidenceProperties(key), true);
});
}
}
{{/_hasDelayedProperties}}
if(completed || failed){
resolve(json);
}else{
this.onChange(function(data) {
if(completed || failed){
resolve(data);
}
})
}
};
// Update this instance with the initial JSON payload.
update.call(this, json);
{{#_supportsPromises}}
var parent = this;
this.promise.then(function(value) {
// JSON has been updated so replace the current instance.
update.call(parent, value);
failed = false;
completed = true;
}).catch(catchError);
{{/_supportsPromises}}
{{^_supportsPromises}}
process(function(json) {}, catchError);
{{/_supportsPromises}}
}
var {{_objName}} = new fiftyoneDegreesManager();
© 2015 - 2025 Weber Informatics LLC | Privacy Policy