@qikdev/sdk
Version:
Promise based Javascript SDK
1,549 lines (1,296 loc) • 49.1 kB
HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>qik.utils.js - Documentation</title>
<script src="scripts/prettify/prettify.js"></script>
<script src="scripts/prettify/lang-css.js"></script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc.css">
<script src="scripts/nav.js" defer></script>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<input type="checkbox" id="nav-trigger" class="nav-trigger" />
<label for="nav-trigger" class="navicon-button x">
<div class="navicon"></div>
</label>
<label for="nav-trigger" class="overlay"></label>
<nav >
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="access.html">access</a><ul class='methods'><li data-type='method'><a href="access.html#.actionableScopes">actionableScopes</a></li><li data-type='method'><a href="access.html#.allPermissions">allPermissions</a></li><li data-type='method'><a href="access.html#.allPermissionTypes">allPermissionTypes</a></li><li data-type='method'><a href="access.html#.allUserScopes">allUserScopes</a></li><li data-type='method'><a href="access.html#.canCreate">canCreate</a></li><li data-type='method'><a href="access.html#.canDeleteItem">canDeleteItem</a></li><li data-type='method'><a href="access.html#.canEditItem">canEditItem</a></li><li data-type='method'><a href="access.html#.canEraseItem">canEraseItem</a></li><li data-type='method'><a href="access.html#.canKnowOf">canKnowOf</a></li><li data-type='method'><a href="access.html#.canListItem">canListItem</a></li><li data-type='method'><a href="access.html#.canRestoreItem">canRestoreItem</a></li><li data-type='method'><a href="access.html#.canViewItem">canViewItem</a></li><li data-type='method'><a href="access.html#.checkActionAccess">checkActionAccess</a></li><li data-type='method'><a href="access.html#.getAllDescendants">getAllDescendants</a></li><li data-type='method'><a href="access.html#.has">has</a></li><li data-type='method'><a href="access.html#.hasFeature">hasFeature</a></li><li data-type='method'><a href="access.html#.hashPermissions">hashPermissions</a></li><li data-type='method'><a href="access.html#.isAdministrator">isAdministrator</a></li><li data-type='method'><a href="access.html#.isAssignee">isAssignee</a></li><li data-type='method'><a href="access.html#.isOwner">isOwner</a></li></ul></li><li><a href="api.html">api</a><ul class='methods'><li data-type='method'><a href="api.html#.generateEndpointURL">generateEndpointURL</a></li></ul></li><li><a href="auth.html">auth</a><ul class='methods'><li data-type='method'><a href="auth.html#.changeOrganisation">changeOrganisation</a></li><li data-type='method'><a href="auth.html#.ensureValidToken">ensureValidToken</a></li><li data-type='method'><a href="auth.html#.getCurrentToken">getCurrentToken</a></li><li data-type='method'><a href="auth.html#.getCurrentUser">getCurrentUser</a></li><li data-type='method'><a href="auth.html#.impersonate">impersonate</a></li><li data-type='method'><a href="auth.html#.login">login</a></li><li data-type='method'><a href="auth.html#.logout">logout</a></li><li data-type='method'><a href="auth.html#.retrieveUserFromResetToken">retrieveUserFromResetToken</a></li><li data-type='method'><a href="auth.html#.sendResetPasswordRequest">sendResetPasswordRequest</a></li><li data-type='method'><a href="auth.html#.set">set</a></li><li data-type='method'><a href="auth.html#.sync">sync</a></li><li data-type='method'><a href="auth.html#.updateUserWithToken">updateUserWithToken</a></li></ul></li><li><a href="cache.html">cache</a><ul class='methods'><li data-type='method'><a href="cache.html#.get">get</a></li><li data-type='method'><a href="cache.html#.reset">reset</a></li></ul></li><li><a href="content.html">content</a><ul class='methods'><li data-type='method'><a href="content.html#.create">create</a></li><li data-type='method'><a href="content.html#.delete">delete</a></li><li data-type='method'><a href="content.html#.get">get</a></li><li data-type='method'><a href="content.html#.getFromSlug">getFromSlug</a></li><li data-type='method'><a href="content.html#.glossary">glossary</a></li><li data-type='method'><a href="content.html#.list">list</a></li><li data-type='method'><a href="content.html#.patch">patch</a></li><li data-type='method'><a href="content.html#.restore">restore</a></li><li data-type='method'><a href="content.html#.scopeGlossary">scopeGlossary</a></li><li data-type='method'><a href="content.html#.update">update</a></li><li data-type='method'><a href="content.html#.validateField">validateField</a></li><li data-type='method'><a href="content.html#.variables">variables</a></li></ul></li><li><a href="files.html">files</a><ul class='methods'><li data-type='method'><a href="files.html#.downloadUrl">downloadUrl</a></li><li data-type='method'><a href="files.html#.duration">duration</a></li><li data-type='method'><a href="files.html#.filesize">filesize</a></li><li data-type='method'><a href="files.html#.getBinaryTypeFromMime">getBinaryTypeFromMime</a></li><li data-type='method'><a href="files.html#.mediaUrl">mediaUrl</a></li></ul></li><li><a href="filter.html">filter</a><ul class='methods'><li data-type='method'><a href="filter.html#.activeFilterComparators">activeFilterComparators</a></li><li data-type='method'><a href="filter.html#.activeFilterKeys">activeFilterKeys</a></li><li data-type='method'><a href="filter.html#.activeFilterOperators">activeFilterOperators</a></li><li data-type='method'><a href="filter.html#.activeFilters">activeFilters</a></li><li data-type='method'><a href="filter.html#.activeFilterValues">activeFilterValues</a></li><li data-type='method'><a href="filter.html#.filterChangeString">filterChangeString</a></li><li data-type='method'><a href="filter.html#.isValidFilter">isValidFilter</a></li></ul></li><li><a href="geo.html">geo</a><ul class='methods'><li data-type='method'><a href="geo.html#.distance">distance</a></li><li data-type='method'><a href="geo.html#.getDefaultCountry">getDefaultCountry</a></li><li data-type='method'><a href="geo.html#.getDefaultTimezone">getDefaultTimezone</a></li><li data-type='method'><a href="geo.html#.getPosition">getPosition</a></li><li data-type='method'><a href="geo.html#.stopWatchPosition">stopWatchPosition</a></li><li data-type='method'><a href="geo.html#.watchPosition">watchPosition</a></li></ul></li><li><a href="module-QikStorage-CookieStorageAdapter.html">CookieStorageAdapter</a><ul class='methods'><li data-type='method'><a href="module-QikStorage-CookieStorageAdapter.html#getCookieOptions">getCookieOptions</a></li><li data-type='method'><a href="module-QikStorage-CookieStorageAdapter.html#isCookieMode">isCookieMode</a></li></ul></li><li><a href="module-QikStorage-LocalStorageAdapter.html">LocalStorageAdapter</a></li><li><a href="module-QikStorage-StorageAdapter.html">StorageAdapter</a><ul class='methods'><li data-type='method'><a href="module-QikStorage-StorageAdapter.html#clearUser">clearUser</a></li><li data-type='method'><a href="module-QikStorage-StorageAdapter.html#getAccessToken">getAccessToken</a></li><li data-type='method'><a href="module-QikStorage-StorageAdapter.html#getRefreshToken">getRefreshToken</a></li><li data-type='method'><a href="module-QikStorage-StorageAdapter.html#getTokenExpiry">getTokenExpiry</a></li><li data-type='method'><a href="module-QikStorage-StorageAdapter.html#getUser">getUser</a></li><li data-type='method'><a href="module-QikStorage-StorageAdapter.html#isAvailable">isAvailable</a></li><li data-type='method'><a href="module-QikStorage-StorageAdapter.html#setUser">setUser</a></li></ul></li><li><a href="socket.html">socket</a><ul class='methods'><li data-type='method'><a href="socket.html#.channel">channel</a></li><li data-type='method'><a href="socket.html#.close">close</a></li><li data-type='method'><a href="socket.html#.connect">connect</a></li><li data-type='method'><a href="socket.html#.reconnect">reconnect</a></li><li data-type='method'><a href="socket.html#.subscribe">subscribe</a></li><li data-type='method'><a href="socket.html#.unsubscribe">unsubscribe</a></li></ul></li><li><a href="system.html">system</a><ul class='methods'><li data-type='method'><a href="system.html#.countries">countries</a></li></ul></li><li><a href="utils.html">utils</a><ul class='methods'><li data-type='method'><a href="utils.html#.cleanValue">cleanValue</a></li><li data-type='method'><a href="utils.html#.comma">comma</a></li><li data-type='method'><a href="utils.html#.errorMessage">errorMessage</a></li><li data-type='method'><a href="utils.html#.exists">exists</a></li><li data-type='method'><a href="utils.html#.extractFromArray">extractFromArray</a></li><li data-type='method'><a href="utils.html#.formatCurrency">formatCurrency</a></li><li data-type='method'><a href="utils.html#.getTypeFromID">getTypeFromID</a></li><li data-type='method'><a href="utils.html#.guid">guid</a></li><li data-type='method'><a href="utils.html#.hash">hash</a></li><li data-type='method'><a href="utils.html#.id">id</a></li><li data-type='method'><a href="utils.html#.ids">ids</a></li><li data-type='method'><a href="utils.html#.isValidEmailAddress">isValidEmailAddress</a></li><li data-type='method'><a href="utils.html#.loadExternalScript">loadExternalScript</a></li><li data-type='method'><a href="utils.html#.loadExternalStyle">loadExternalStyle</a></li><li data-type='method'><a href="utils.html#.machineName">machineName</a></li><li data-type='method'><a href="utils.html#.mapParameters">mapParameters</a></li><li data-type='method'><a href="utils.html#.matchInArray">matchInArray</a></li><li data-type='method'><a href="utils.html#.parseBoolean">parseBoolean</a></li><li data-type='method'><a href="utils.html#.parseDate">parseDate</a></li><li data-type='method'><a href="utils.html#.parseEmail">parseEmail</a></li><li data-type='method'><a href="utils.html#.parseInt">parseInt</a></li><li data-type='method'><a href="utils.html#.parseNumber">parseNumber</a></li><li data-type='method'><a href="utils.html#.parseURL">parseURL</a></li></ul></li></ul><h3>Modules</h3><ul><li><a href="module-QikStorage.html">QikStorage</a><ul class='methods'><li data-type='method'><a href="module-QikStorage.html#.createStorageAdapter">createStorageAdapter</a></li></ul></li></ul><h3>Global</h3><ul><li><a href="global.html#EventDispatcher">EventDispatcher</a></li></ul>
</nav>
<div id="main">
<h1 class="page-title">qik.utils.js</h1>
<section>
<article>
<pre class="prettyprint source linenums"><code>import _get from "lodash.get";
import _camelCase from "lodash.camelcase";
import { isBrowser, isNode } from "browser-or-node";
///////////////////////////////////////////////////////////////////////////////
/**
* @name utils
* @classdesc Utility helper functions — id, ids, hashing, parsing, cleaning, and other general-purpose helpers
* @class
*/
const service = {};
////////////////////////////////////
const loadedExternalScripts = {};
/**
* A function that dynamically include an external javascript resource
* ensuring that it will only be included once
* @alias utils.loadExternalScript
* @param {String} url The url of the external script
* @return {Promise} A promise that will be resolved once the script has been loaded
* @example
*
* await sdk.utils.loadExternalScript('https://cdn.javascript.com/external/script.js');
*/
service.loadExternalScript = function (url) {
// If we already have a promise
if (loadedExternalScripts[url]) {
// return the existing promise
return loadedExternalScripts[url];
}
// Create a new promise
const promise = (loadedExternalScripts[url] = new Promise(createNewScript));
function createNewScript(resolve, reject) {
if (!document) {
delete loadedExternalScripts[url];
return reject("document is undefined");
}
//////////////////////////////////////
var script = document.createElement("script");
script.type = "text/javascript";
script.async = true;
script.onload = function () {
console.log("Loaded external script", url);
return resolve(url);
};
script.src = url;
// Inject it into the document's <head> tag
document.head.appendChild(script);
}
return promise;
};
const loadedExternalStyles = {};
/**
* A function that dynamically include an external css resource
* ensuring that it will only be included once
* @alias utils.loadExternalStyle
* @param {String} url The url of the external css
* @return {Promise} A promise that will be resolved once the css has been loaded
* @example
*
* await sdk.utils.loadExternalStyle('https://cdn.css.com/external/style.css');
*/
service.loadExternalStyle = function (url) {
const promise = new Promise(createNewScript);
function createNewScript(resolve, reject) {
if (!document) {
return reject("document is undefined");
}
if (loadedExternalStyles[url]) {
return resolve(url);
}
// Avoid duplicate scripts
loadedExternalStyles[url] = true;
//////////////////////////////////////
var script = document.createElement("link");
script.setAttribute("rel", "stylesheet");
script.setAttribute("type", "text/css");
script.setAttribute("href", url);
script.onload = function () {
console.log("Loaded external stylesheet", url);
return resolve(url);
};
// Inject it into the document's <head> tag
document.head.appendChild(script);
}
return promise;
};
///////////////////////////////////////////////////////////////////////////////
/**
* A helper function for checking whether a value is truthy/falsy
* @alias utils.exists
* @param {Anything} value The value to check
* @return {Boolean} whether the value is truthy
* @example
*
* sdk.utils.exists('undefined'); // false
* sdk.utils.exists([]); // true
* sdk.utils.exists(''); // false
* sdk.utils.exists(undefined); // false
*/
service.exists = function (value) {
var isUndefinedOrNull;
////////////////////////
if (Array.isArray(value)) {
return true;
}
////////////////////////
if (value === 0) {
return true;
}
////////////////////////
switch (typeof value) {
case "undefined":
case "null":
isUndefinedOrNull = true;
break;
default:
var string = String(value).toLowerCase();
switch (string) {
case "":
case "undefined":
case "null":
isUndefinedOrNull = true;
break;
}
break;
}
return !!!isUndefinedOrNull;
};
//////////////////////////////////
/**
* A helper function for getting a javascript date object from some input
* returns undefined if it can not be parsed
* @alias utils.parseDate
* @param {String|Number} input The value to parse as a date
* @return {Date} the javascript date object
* @example
*
* sdk.utils.parseDate(input);
*/
service.parseDate = function (input) {
if (!input) {
return;
}
if (input instanceof Date) {
return input;
}
//Attempt to create a date
var d = new Date(input);
var isValid = d instanceof Date && !isNaN(d);
if (isValid) {
return d;
}
return;
};
/**
* A helper function for getting a full url string from some input
* @alias utils.parseURL
* @param {String} input The input value
* @return {String} the fully parsed URL
* @example
*
* sdk.utils.parseURL('google.com'); // Returns https://google.com
* sdk.utils.parseURL('mailto:hello@qik.dev'); // Returns 'mailto:hello@qik.dev'
* sdk.utils.parseURL('://hello.com'); // Returns '://hello.com'
* sdk.utils.parseURL('hello@email.com'); // Returns 'mailto:hello@email.com'
*/
service.parseURL = function (string) {
if (!string) {
return false;
}
const relative = string.startsWith("/");
if (relative) {
return string;
}
if (string.startsWith("://")) {
return string;
}
if (string.startsWith("mailto:")) {
return string;
}
if (string.startsWith("tel:")) {
return string;
}
if (string.startsWith("sms:")) {
return string;
}
//////////////////////////////////
//If someone entered an email by accident
var email = service.parseEmail(string);
//Convert it to a mailto link
if (email && email === string) {
return `mailto:${email}`;
}
//////////////////////////////////
const withHttp = (string) =>
!/^https?:\/\//i.test(string) ? `http://${string}` : string;
string = withHttp(string);
//TODO: test this more.
string = string.replace(/\s/g, "");
var valid = RegExp(
"(https?:\\/\\/)?((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|((\\d{1,3}\\.){3}\\d{1,3}))(\\:\\d+)?(\\/[-a-z\\d%_.~+@]*)*(\\?[;&a-z\\d%_.~+=-@]*)?(\\#[-a-z\\d_@]*)?$",
"i",
).test(string);
if (!valid) {
return false;
}
///////////////////////////
return withHttp(string);
};
///////////////////////////////////////////////
/**
* A helper function for getting a number from some input
* @alias utils.parseNumber
* @param {String} value The input value
* @param {Number} decimalPoints The number of decimal points to round to
* @return {Number} the parsed number value
* @example
*
* sdk.utils.parseNumber('123'); // Returns 123
* sdk.utils.parseNumber('75.501', 2); // Returns 75.5
* sdk.utils.parseNumber(''); // Returns 0
* sdk.utils.parseNumber(null); // Returns 0
* sdk.utils.parseNumber(); // Returns 0
*/
service.parseNumber = function (input, decimalPoints) {
if (!input) {
return 0;
}
input = Number(input);
if (isNaN(input)) {
return 0;
}
if (decimalPoints) {
var str = input.toFixed(decimalPoints);
input = service.parseNumber(str);
}
return input;
};
/**
* A helper function for getting a number from some input
* @alias utils.isValidEmailAddress
* @param {String} emailAddress The email to validate
* @return {Boolean} whether or not the input is a valid email address
* @example
*
* sdk.utils.isValidEmailAddress('123.com'); // Returns false
* sdk.utils.isValidEmailAddress('hello@world.com'); // Returns true
* sdk.utils.isValidEmailAddress('something@special.io'); // Returns true
*/
service.isValidEmailAddress = function (email) {
var tester =
/^[-!#$%&'*+\/0-9=?A-Z^_a-z`{|}~](\.?[-!#$%&'*+\/0-9=?A-Z^_a-z`{|}~])*@[a-zA-Z0-9](-*\.?[a-zA-Z0-9])*\.[a-zA-Z](-?[a-zA-Z0-9])+$/;
if (!email) return false;
var emailParts = email.split("@");
if (emailParts.length !== 2) return false;
var account = emailParts[0];
var address = emailParts[1];
if (account.length > 64) return false;
else if (address.length > 255) return false;
var domainParts = address.split(".");
if (
domainParts.some(function (part) {
return part.length > 63;
})
)
return false;
if (!tester.test(email)) return false;
return true;
};
/**
* A helper function for getting a formatted email address
* @alias utils.parseEmail
* @param {String} emailAddress The input to parse as an email
* @return {String|Boolean} a valid email address in lowercase, or false if parse is not possible
* @example
*
* sdk.utils.parseEmail('ToMack@gmail.com'); // returns 'tomack@gmail.com'
* sdk.utils.isValidEmailAddress('hello.world.com'); // Returns false
*/
service.parseEmail = function (input) {
if (!input) {
return false;
}
var lowercase = String(input).toLowerCase();
var valid = service.isValidEmailAddress(lowercase);
if (!valid) {
return false;
} else {
return lowercase;
}
};
//////////////////////////////////
/**
* A helper function for getting an integer
* @alias utils.parseInt
* @param {String|Number} input The input to parse as an integer
* @return {Integer} the resulting integer or 0
* @example
*
* sdk.utils.parseInt('134'); // returns 134
* sdk.utils.parseInt('cows'); // returns 0
* sdk.utils.parseInt(); // returns 0
*/
service.parseInt = function (input) {
if (!input) {
return 0;
}
input = parseInt(input);
if (isNaN(input)) {
return 0;
}
return input;
};
/**
* A helper function for cleaning an input value to match
* a required type
* @alias utils.cleanValue
* @param {*} data The input to clean
* @param {String} type The data type to parse
* @param {Object} options Additional options for parsing
* @return {*} the resulting cleaned value
* @example
*
* sdk.utils.cleanValue({_id:'1234', title:'Item'...}, 'reference'); // returns '1234'
* sdk.utils.cleanValue('true', 'boolean'); // returns true
* sdk.utils.cleanValue('Mr Rogers House', 'key'); // returns 'mrRogersHouse';
* sdk.utils.cleanValue('Hello.World@email.COM', 'email'); // returns 'hello.world@email.com';
*/
service.cleanValue = function (data, type, options) {
if (!options) {
options = {};
}
////////////////////////
var noInputValue = service.exists(data);
if (options.strict && noInputValue && type != "boolean") {
return data;
}
////////////////////////
switch (type) {
case "reference":
return service.id(data, true);
break;
case "boolean":
return service.parseBoolean(data);
break;
case "url":
return service.parseURL(data);
break;
case "key":
return service.machineName(data);
break;
case "email":
return service.parseEmail(data);
break;
case "date":
var parsed = service.parseDate(data, options);
if (parsed === undefined) {
return;
} else {
return parsed;
}
break;
case "number":
case "decimal":
case "float":
var val = service.parseNumber(data);
if (String(val) == String(Number(data))) {
return val;
}
return;
break;
case "integer":
var val = service.parseInt(data);
if (String(val) == String(parseInt(data))) {
return val;
}
return;
break;
case "group":
return data;
break;
case "object":
if (typeof data === "object" && data !== null && !Array.isArray(data)) {
return data;
}
return;
break;
case "string":
if (service.exists(data)) {
var stringed = String(data);
if (stringed == "[object Object]") {
return;
}
return stringed;
} else {
return;
}
// if(!data) {
// return '';
// }
// if(Array.isArray(data)) {
// return JSON.stringify(data);
// }
// if(Object.isObject(data)) {
// return JSON.stringify(data);
// }
// return String(data);
// break;
default:
return String(data);
break;
}
};
service.isValidValue = function (value, dataType, strict) {
var isValue = service.exists(value);
var valueIsNumber = typeof value == "number";
////////////////////////
var isValidEntry = false;
if (!isValue) {
return false;
}
////////////////////////
switch (dataType) {
case "url":
if (strict) {
isValidEntry =
value.startsWith("/") ||
value.startsWith("://") ||
value.startsWith("http://") ||
value.startsWith("https://");
} else {
isValidEntry = String(value) === service.parseURL(value);
}
break;
case "key":
isValidEntry = String(value) === service.machineName(value);
break;
case "date":
//If we are being strict
if (strict) {
//And the input is not a javascript date object
if (typeof value != "object" || !(value instanceof Date)) {
return false;
}
}
///////////////////////////////
var parsed = service.parseDate(value);
return parsed !== undefined;
break;
case "email":
if (strict) {
isValidEntry = value === service.parseEmail(value);
} else {
isValidEntry =
String(value).toLowerCase() === service.parseEmail(value);
}
break;
case "number":
case "decimal":
case "float":
if (strict) {
isValidEntry =
valueIsNumber && Number(value) === service.parseNumber(value);
} else {
isValidEntry = Number(value) === service.parseNumber(value);
}
break;
case "integer":
if (strict) {
isValidEntry =
valueIsNumber && Number(value) === service.parseInt(value);
} else {
isValidEntry = Number(value) === service.parseInt(value);
}
break;
case "boolean":
var parsed = service.parseBoolean(value);
//Only accept true booleans
if (strict) {
if (value === true || value === false) {
isValidEntry = value === parsed;
}
} else {
isValidEntry = parsed === true || parsed === false;
}
break;
case "reference":
if (strict) {
isValidEntry = String(value) === service.id(value);
} else {
isValidEntry = !!service.id(value);
}
break;
case "string":
var checkString = String(value);
if (strict) {
if (typeof value != "string") {
return false;
}
}
if (checkString == "[object Object]") {
return false;
}
isValidEntry = true;
break;
// case 'group':
// isValidEntry = _isObject(value);
// break;
case "object":
isValidEntry =
typeof value === "object" && value !== null && !Array.isArray(value);
break;
case "array":
isValidEntry = Array.isArray(value);
break;
}
////////////////////////
return isValidEntry;
};
/**
* A helper function for parsing input as boolean values
* @alias utils.parseBoolean
* @param {*} value The input to parse
* @return {Boolean} the resulting true/false value
* @example
*
* sdk.utils.parseBoolean('true'); // returns true
* sdk.utils.parseBoolean('y'); // returns true
* sdk.utils.parseBoolean('YES'); // returns true
* sdk.utils.parseBoolean('1'); // returns true
* sdk.utils.parseBoolean('t'); // returns true
* sdk.utils.parseBoolean(''); // returns false
* sdk.utils.parseBoolean('0'); // returns false
* sdk.utils.parseBoolean('n'); // returns false
* sdk.utils.parseBoolean('no'); // returns false
* sdk.utils.parseBoolean('f'); // returns false
* sdk.utils.parseBoolean('null'); // returns false
*/
service.parseBoolean = function (value) {
switch (String(value).toLowerCase()) {
case "true":
case "y":
case "yes":
case "1":
case "t":
value = true;
break;
case "false":
case "n":
case "no":
case "0":
case "f":
case "undefined":
case "null":
case "":
case "-1":
value = false;
break;
}
return !!value;
};
///////////////////////////////////////////////////////////////////////////////
service.clone = function (input) {
return JSON.parse(JSON.stringify(input));
};
///////////////////////////////////////////////////////////////////////////////
service.getAllFields = function (actualDefinition) {
const self = this;
const isProfile =
actualDefinition.definesType === "profile" ||
actualDefinition.key === "profile";
const isFormSubmission = actualDefinition.definesType === "submission";
var allFields = [...actualDefinition.fields];
var definedFields = actualDefinition.definedFields || [];
if (definedFields.length) {
if (isFormSubmission) {
var formDataFields = {
title: `Form Data`,
minimum: 1,
maximum: 1,
key: "formData",
asObject: true,
type: "group",
fields: definedFields,
};
allFields.push(formDataFields);
const cleanedDataFields = definedFields.map(function (field) {
if (field.type === "reference") {
field = JSON.parse(JSON.stringify(field));
delete field.fields;
}
return field;
});
var dataFields = {
title: `Data`,
minimum: 1,
maximum: 1,
key: "data",
asObject: true,
type: "group",
fields: cleanedDataFields,
};
allFields.push(dataFields);
} else {
var dataFields = {
title: `${actualDefinition.title}`,
minimum: 1,
maximum: 1,
key: "data",
asObject: true,
type: "group",
fields: definedFields,
};
allFields.push(dataFields);
}
}
if (isProfile) {
allFields.push({
title: "Age",
key: "_age",
minimum: 1,
maximum: 1,
type: "integer",
});
allFields.push({
title: "Date of birth",
key: "_dob",
minimum: 1,
maximum: 1,
type: "date",
});
}
var mapped = service
.mapFields(allFields)
.filter(function (field) {
var isObject =
field.type == "group" &&
field.asObject &&
field.minimum == 1 &&
field.maximum == 1;
return !isObject;
})
.map(function (field) {
field.title =
field.filterTitle || field.titles.filter(Boolean).join(" › ");
return field;
})
.sort(function (a, b) {
return a.title < b.title ? -1 : 1;
});
return mapped;
};
///////////////////////////////////////////////////////////////////////////////
service.mapFields = function (fields, options) {
if (!options) {
options = {};
}
//////////////////////////////
var output = [];
var trail = [];
var titles = [];
var currentDepth = 1;
var depthLimit = options.depth;
var delimiter = options.arrayDelimeter || "[]";
//Loop through each field
fields.forEach(mapField);
//////////////////////////////
//Recursively map the fields
function mapField(field, i) {
var fieldKey = field.key;
var isGroup = field.type == "group";
var singleValue = field.minimum === field.maximum && field.minimum === 1;
var asObject = field.asObject; // || (isGroup && singleValue);
var isLayout = isGroup && !asObject;
//////////////////////////////////
// Whether to clone the field or use the existing reference
var mapped = options.original ? field : service.clone(field);
mapped.trail = trail.slice();
mapped.trail.push(fieldKey);
mapped.path = mapped.trail.join(".");
mapped.titles = titles.slice();
mapped.titles.push(field.filterTitle || field.title || "");
const isNotLayoutOrIsAllowed = !isLayout || options.includeLayout;
//If its an actual element or we've asked to include
//layout only elements
if (isNotLayoutOrIsAllowed) {
//Add it to the mix
output.push(mapped);
}
//////////////////////////////////
//Now see if there are child fields and should we go further
var limitHit = depthLimit && currentDepth >= depthLimit;
//If there are child fields for this field
if (field.fields && field.fields.length) {
//If it's just a group with no extra key
if (isLayout) {
//Loop through fields as if they are at the same depth
field.fields.forEach(mapField);
} else {
//We don't need to traverse any further
//because we hit the limit
if (limitHit) {
return;
}
//Move down a level and add to the object
//Include the key in the path trail
currentDepth++;
let injectKey = fieldKey;
if (options.includeArrayDelimeter) {
if (!singleValue) {
injectKey = `${fieldKey}${delimiter}`;
}
}
trail.push(injectKey);
titles.push(field.filterTitle || field.title);
//Loop through each field
field.fields.forEach(mapField);
//Move back up a level before we go to the next field
currentDepth--;
trail.pop();
titles.pop();
}
}
}
return output;
};
///////////////////////////////////////////////////////////////////////////////
/**
* A helpful function that can take a keyed object literal and map it to url query string parameters
* @alias utils.mapParameters
* @param {Object} parameters The object you want to transalte
* @return {String} The query string
* @example
* //Returns &this=that&hello=world
* sdk.utils.mapParameters({"this":"that", "hello":"world"})
*/
service.mapParameters = function (parameters) {
parameters = parameters || {};
var array = [];
Object.entries(parameters).forEach(function ([key, value]) {
if (value === undefined || value === null || value == false) {
return;
}
if (Array.isArray(value)) {
value.forEach(function (v) {
array.push(`${key}=${encodeURIComponent(v)}`);
});
} else {
array.push(encodeURIComponent(key) + "=" + encodeURIComponent(value));
}
});
return array.join("&");
};
///////////////////////////////////////////////////////////////////////////////
/**
* A function that will take an integer and a currency string and return a formatted numeric amount rounded to 2 decimal places
* @alias utils.formatCurrency
* @param {Integer} value The amount in cents
* @param {String} currency The currency to format
* @return {String} The formatted value
* @example
*
* //Returns £10.00
* sdk.utils.formatCurrency(1000, 'gbp');
*
* //Returns $10.00
* sdk.utils.formatCurrency(1000, 'usd');
*
*/
service.formatCurrency = function (value, currency, decimalPoints) {
if (!value || isNaN(value)) {
value = 0;
}
decimalPoints = decimalPoints || 2;
var currencyPrefix = service.currencySymbol(currency);
return `${currencyPrefix}${parseFloat(Number(value) / 100).toFixed(decimalPoints)}`;
};
/**
* A function that will take an id and return the type key
* @alias utils.getTypeFromID
* @param {String} id The id of an object
* @return {String} The key
* @example
*
* // Returns 'user'
* sdk.utils.getTypeFromID('52b523f775beea960013f6cd');
*
* // Returns 'role'
* sdk.utils.getTypeFromID('62b59cb572fb4772e7b5fa93');
*
*/
service.getTypeFromID = function (id) {
id = service.id(id);
if (!id) {
return;
}
const lookup = {
21: "organisation",
23: "comment",
24: "persona",
25: "audio",
26: "campaign",
28: "smartlist",
29: "cache",
40: "application",
41: "action",
42: "billinginvoice",
43: "componentsnapshot",
44: "diff",
45: "email",
46: "submission",
49: "import",
53: "socket",
54: "timetrigger",
61: "article",
63: "component",
65: "event",
66: "file",
67: "tag",
69: "image",
70: "profile",
72: "role",
73: "scope",
74: "transaction",
75: "user",
76: "video",
77: "workflowcard",
78: "variable",
"3c": "patoken",
"7b": "uatoken",
"7d": "urtoken",
"2d": "systemtask",
"3d": "export",
"5f": "systemflag",
"2b": "batch",
"3a": "log",
"2e": "stat",
"2f": "unsubscribe",
"6b": "intent",
"7c": "upload",
"2c": "systemtrigger",
"6e": "interface",
"4e": "interfacesnapshot",
"4f": "notification",
"2a": "definition",
"7e": "definitionsnapshot",
"5e": "integration",
"4c": "listener",
"6d": "topic",
"7a": "code",
"4d": "paymentmethod",
"6c": "sslcertificate",
};
var hexKey = id.slice(8, 10);
return lookup[hexKey];
};
/**
* A function that will take a currency string and return the symbol
* @alias utils.currencySymbol
* @param {String} currency The currency
* @return {String} The symbol
* @example
*
* //Returns £
* sdk.utils.currencySymbol('gbp');
*
* //Returns $
* sdk.utils.currencySymbol('usd');
*
*/
const CURRENCY_SYMBOLS = { gbp: "\u00A3", eur: "\u20AC" };
service.currencySymbol = function (currency) {
return CURRENCY_SYMBOLS[String(currency).toLowerCase()] || "$";
};
const SUPPORTED_CURRENCIES = [
{ code: "usd", countries: ["US"] },
{ code: "gbp", countries: ["GB", "UK"] },
{ code: "cad", countries: ["CA"] },
{ code: "aud", countries: ["AU"] },
{ code: "nzd", countries: ["NZ"] },
{ code: "sgd", countries: ["SG"] },
];
service.getAvailableCurrencies = function (defaultCountryID) {
let currencies = SUPPORTED_CURRENCIES.map(({ code, countries }) => ({
name: `${code.toUpperCase()} (${service.currencySymbol(code)})`,
value: code,
countryCode: Object.fromEntries(countries.map((c) => [c, true])),
}));
if (defaultCountryID) {
const idx = currencies.findIndex((c) => c.countryCode[defaultCountryID]);
if (idx > 0) {
const [match] = currencies.splice(idx, 1);
currencies.unshift(match);
}
}
return currencies;
};
///////////////////////////////////////////////////////////////////////////////
/**
* Creates a fast keyed hash object from an array of items
* @alias utils.hash
* @param {Array} array The array of items to convert into a hash
* @param {String} key The key or path to the property on each item to use as the hashed key
* @return {Object} A key/value paired object
* @example
* //Returns { jimbo:{id:'jimbo', title:'Jim Jones'}, {id:'roger', title:'Roger Fellow'} }
* sdk.utils.hash([{id:'jimbo', title:'Jim Jones'}, {id:'roger', title:'Roger Fellow'}], 'id');
*
*/
service.hash = function (items, key) {
items = !Array.isArray(items) ? [] : items;
return items.reduce(function (memo, item) {
const k = key ? _get(item, key) : item;
memo[k] = item;
return memo;
}, {});
};
//////////////////////////////////////////////////
/**
* Returns a subset of values in an array that match a provided rule
* @alias utils.extractFromArray
* @param {Array} array The array you want to extract values from
* @param {String} key The path to the child property you want to extract
* @param {Boolean} sum Whether to sum the extracted values together in total
* @param {Boolean} flatten Whether to flatten nested child arrays
* @param {Boolean} unique Whether to only return unique values
* @param {Boolean} exclude Whether to exclude null or undefined values
* @param {Object} options Pass through extra options for how to extract the values
* @return {Array} An array of all values retrieved from the array, unless provided arguments require otherwise
* @example
* //Returns [12, 45] as all the values
* sdk.utils.extractFromArray([{name:'Wendy', age:12}, {name:'Roger', age:45}], 'age');
*
* //Returns 32
* sdk.utils.extractFromArray([{name:'Wendy', age:12}, {name:'Roger', age:20}], 'age', {sum:true});
*
*/
service.extractFromArray = function (
array,
key,
sum,
flatten,
unique,
exclude,
options,
) {
options = options || {};
if (sum) {
options.sum = sum;
}
if (flatten) {
options.flatten = true;
}
if (unique) {
options.unique = true;
}
if (exclude) {
options.excludeNull = true;
}
/////////////////
//Filter the array options by a certain value and operator
var extractedValues = array.reduce(function (set, entry) {
//Get the value from the object
var retrievedValue = _get(entry, key);
var isNull =
!retrievedValue && retrievedValue !== false && retrievedValue !== 0;
if (options.excludeNull && isNull) {
return set;
}
set.push(retrievedValue);
return set;
}, []);
if (options.flatten) {
extractedValues = extractedValues.flat();
}
if (options.unique) {
extractedValues = [...new Set(extractedValues)];
}
if (options.sum) {
extractedValues = extractedValues.reduce(function (a, b) {
return a + b;
}, 0);
}
return extractedValues;
};
//////////////////////////////////////////////////////
/**
* A function that can return a selection of values that were found in an array that match a specific rule,
* This is often used to evaluate expressions within form fields
* @alias utils.matchInArray
* @param {Array} array The array to check
* @param {String} key The javascript dot notation path to extract
* @param {String} value The value to compare against
* @param {String} comparator The logical operator to use to compare the extracted value with the provided value ('>', '<', '>=', '<=', 'in', '==') Defaults to '==' (Is equal to)
* @return {Array} Returns an array of matching values
* @example
* //Returns [{name:'Michael', age:45}] as that is only item in the array that matches the criteria
* sdk.utils.matchInArray([{name:'Wendy', age:12}, {name:'Michael', age:45}], 'age', 45, '>=');
*
*/
service.matchInArray = function (array, key, v, comparator) {
//Filter the array options by a certain v and comparator
var matches = array.filter(function (entry) {
//Get the v from the object
var extracted = _get(entry, key);
var found;
switch (comparator) {
case "in":
found = extracted.includes(v);
break;
case ">=":
found = extracted >= v;
break;
case "<=":
found = extracted <= v;
break;
case "<":
found = extracted < v;
break;
case ">":
found = extracted > v;
break;
case "==":
default:
if (v === undefined) {
found = extracted;
} else {
found = extracted == v;
}
break;
}
return found;
});
return matches;
};
///////////////////////////////////////////////////////////////////////////////
/**
* A helpful class that can take an array of values and return them as a comma seperated
* string, If the values are objects, then a property to use as the string representation can be specified
* @alias utils.comma
* @param {Array} array The array of values to translate
* @param {String} path An optional property key to use for each value
* @return {String} The resulting comma seperated string
* @example
* //Returns 'cat, dog, bird'
* sdk.utils.comma(['cat', 'dog', 'bird']);
*
* //Returns 'cat, dog, bird'
* sdk.utils.comma([{title:'cat'}, {title:'dog'}, {title:'bird'}], 'title');
*/
service.comma = function (array, path, limit) {
if (limit) {
array = array.slice(0, limit);
}
return array
.filter(function (item) {
return item != null && item !== undefined && item !== "";
})
.map(function (item) {
if (path && path.length) {
return _get(item, path);
}
return item;
})
.join(", ");
};
///////////////////////////////////////////////////////////////////////////////
//Helper function to get an id of an object
/**
* Returns a specified _id for an object
* @alias utils.id
* @param {Object} input An object that is or has an _id property
* @param {Boolean} asObjectID Whether to convert to a Mongo ObjectId
* @return {String} Will return either a string or a Mongo ObjectId
*
* @example
*
* //Returns '5cb3d8b3a2219970e6f86927'
* sdk.utils.id('5cb3d8b3a2219970e6f86927')
*
* //Returns true
* typeof service.id({_id:'5cb3d8b3a2219970e6f86927', title, ...}) == 'string';
* //Returns true
* typeof service.id({_id:'5cb3d8b3a2219970e6f86927'}, true) == 'object';
*/
service.id = function (source) {
if (!source) {
return;
}
/////////////////////////////////
var output;
if (source._id) {
output = String(source._id);
} else {
output = String(source);
}
var isValid = service.isValidID(output);
if (!isValid) {
return;
}
return output;
};
///////////////////////////////////////////////////////////////////////////////
/**
* Cleans and maps an array of objects to an array of IDs
* @alias utils.ids
* @param {Array} array An array of objects or object ids
* @param {Boolean} asObjectID Whether or not to map the ids as Mongo ObjectIds
* @return {Array} An array of Ids
*
* @example
* //Returns ['5cb3d8b3a2219970e6f86927', '5cb3d8b3a2219970e6f86927', '5cb3d8b3a2219970e6f86927']
* sdk.utils.ids([{_id:'5cb3d8b3a2219970e6f86927'}, {_id:'5cb3d8b3a2219970e6f86927'}, null, '5cb3d8b3a2219970e6f86927'])
*/
service.ids = function (array) {
if (!array) {
return [];
}
/////////////////////////////////
var ids = Object.keys(
array.reduce(function (set, entry) {
if (!entry) {
return set;
}
var cleaned = service.id(entry);
if (cleaned) {
set[cleaned] = 1;
}
return set;
}, {}),
);
///////////////////////////////
return ids;
};
///////////////////////////////////////////////////////////////////////////////
service.isValidID = function (input) {
var checkForHexRegExp = new RegExp("^[0-9a-fA-F]{24}$");
return checkForHexRegExp.test(String(input));
};
///////////////////////////////////////////////////////////////////////////////
/**
* Helper function for retrieving a human readable error message from server error response objects
* @alias utils.errorMessage
* @param {Object} error The error object to translate
* @return {String} The resulting human readable error message
*/
service.errorMessage = function (err) {
if (!err) {
return;
}
if (Array.isArray(err)) {
err = err[0];
}
// Walk through the error object to find the most useful message
let message =
err?.response?.data?.message ||
(typeof err?.response?.data === "string" ? err.response.data : null) ||
err?.message ||
null;
if (Array.isArray(message)) {
message = message[0];
}
return message || JSON.stringify(err);
};
////////////////////////////////////
/**
* Generates a globally unique ID, helpful for adding unique keys for iterable loops
* @alias utils.guid
* @return {String} The new globally unique identifier
* @example
* //Returns 4323a78br-z16h-289j-zwl1-938lda334asd
* sdk.utils.guid()
*/
service.guid = function () {
if (typeof crypto !== "undefined" && crypto.randomUUID) {
return crypto.randomUUID();
}
// Fallback for environments without crypto.randomUUID
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) {
const r = (Math.random() * 16) | 0;
return (c === "x" ? r : (r & 0x3) | 0x8).toString(16);
});
};
////////////////////////////////////
////////////////////////////////////
////////////////////////////////////
/**
* Helper function for cleaning strings to use as database ids
* @alias utils.machineName
* @param {String} string The string to clean eg. (Awesome Event!)
* @return {String} A cleaned and formatted string eg. (awesomeEvent)
*/
service.machineName = function (string) {
if (!string || !string.length) {
return;
}
var regexp = /[^a-zA-Z0-9-_]+/g;
return String(string)
.replace(regexp, " ")
.split("_")
.map((part) => _camelCase(part))
.join("_");
};
/////////////////////////////////////////////
/////////////////////////////////////////////
/////////////////////////////////////////////
export default service;
/////////////////////////////////////////////
/**
* A lightweight event emitter that can be attached to any service object
* to provide pub/sub event capabilities. Can be called with or without `new`.
*/
export function EventDispatcher() {
const handlers = new Map();
const emitter = {
dispatch(event, details) {
const fns = handlers.get(event);
if (fns) {
for (const fn of fns) {
fn(details);
}
}
},
addEventListener(event, callback) {
if (typeof callback !== "function") {
throw new TypeError(
`Expected a function for addEventListener, received ${typeof callback}`,
);
}
if (!handlers.has(event)) {
handlers.set(event, new Set());
}
handlers.get(event).add(callback);
},
removeEventListener(event, callback) {
const fns = handlers.get(event);
if (fns) {
fns.delete(callback);
if (fns.size === 0) {
handlers.delete(event);
}
}
},
removeAllListeners() {
handlers.clear();
},
bootstrap(target) {
if (!target) return;
target.dispatch = emitter.dispatch;
target.addEventListener = emitter.addEventListener;
target.removeEventListener = emitter.removeEventListener;
target.removeAllListeners = emitter.removeAllListeners;
},
};
return emitter;
}
</code></pre>
</article>
</section>
</div>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.6.11</a> on Mon Jul 13 2026 18:49:48 GMT+1000 (Australian Eastern Standard Time) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
</footer>
<script>prettyPrint();</script>
<script src="scripts/polyfill.js"></script>
<script src="scripts/linenumber.js"></script>
</body>
</html>