UNPKG

@microsoft/windows-admin-center-sdk

Version:

Microsoft - Windows Admin Center Shell

1,344 lines 95.3 kB
/* eslint-disable unused-imports/no-unused-vars */ /* eslint-disable no-console */ /* eslint-disable max-len */ // this code has been worked on other platform, and refactoring may cause other problem so keeps as is. var MsftSme; (function (MsftSme) { 'use strict'; const global = window; const FunctionGlobal = Function; const MathGlobal = Math; const ObjectGlobal = Object; // See Mark Miller’s explanation of what this does. // http://wiki.ecmascript.org/doku.php?id=conventions:safe_meta_programming // // For example: // const array_slice: <T>(target: T[]|IArguments, start?: number, end?: number) => T[] = MsPortalFx.uncurryThis(Array.prototype.slice); // Then the call can be strong typed, rather than call/apply with only runtime check. // // This also **might** have the nice side-effect of reducing the size of // the minified code by reducing x.call() to merely x() const call = FunctionGlobal.call; const apply = FunctionGlobal.apply; function uncurryThis(f) { return function () { return call.apply(f, arguments); }; } MsftSme.uncurryThis = uncurryThis; const _applyCall = uncurryThis(apply); MsftSme.applyCall = _applyCall; MsftSme.applyUncurry = _applyCall; // most uncurry function can be call by _applyCall except for array_concat; (see below.) // declare variable such that minimize better and code readibility const array_prototype = Array.prototype; const array_prototype_concat = array_prototype.concat; // array concat does not work well with uncurryThis since it flatten the arguments array. const array_prototype_push = array_prototype.push; const array_filter = uncurryThis(array_prototype.filter); const array_forEach = uncurryThis(array_prototype.forEach); const array_join = uncurryThis(array_prototype.join); const array_push = uncurryThis(array_prototype_push); const array_slice = uncurryThis(array_prototype.slice); const array_splice = uncurryThis(array_prototype.splice); // eslint-disable-next-line unused-imports/no-unused-vars const array_indexof = uncurryThis(array_prototype.indexOf); const array_isArray = Array.isArray; const string_prototype = String.prototype; const string_concat = uncurryThis(string_prototype.concat); const string_split = uncurryThis(string_prototype.split); const string_substring = uncurryThis(string_prototype.substring); const string_indexOf = uncurryThis(string_prototype.indexOf); const string_toLowerCase = uncurryThis(string_prototype.toLowerCase); const object_hasOwnProperty = uncurryThis(ObjectGlobal.prototype.hasOwnProperty); const object_propertyIsEnumerable = uncurryThis(ObjectGlobal.prototype.propertyIsEnumerable); const object_keys_original = ObjectGlobal.keys; const _undefined = undefined; const object_freeze = ObjectGlobal.freeze; const functionType = "function"; const stringType = "string"; const numberType = "number"; const objectType = "object"; const undefinedType = "undefined"; class LocalStorageHandler { /** * Gets the local storage. * @param window the window object to get local storage from. * @returns The local storage. */ static getLocalStorage(window) { try { return window ? window.localStorage : localStorage; } catch (error) { console.warn('Error getting the localStorage:', error); } } /** * Sets the value for the localStorage by key. * @param key The key to set value. * @param value The value for the key. */ static setItem(key, value, window) { try { const localStorageTarget = window ? window.localStorage : localStorage; localStorageTarget.setItem(key, value); } catch (error) { console.warn('Error saving to localStorage:', error); } } /** * Gets the value from localStorage by key. * @param key The key to get value. * @returns The value for the key passed in. */ static getItem(key, window) { try { const localStorageTarget = window ? window.localStorage : localStorage; return localStorageTarget.getItem(key); } catch (error) { console.warn('Error retrieving from localStorage:', error); } return undefined; } /** * Removes the value for the localStorage by key. * @param key The key to remove value. */ static removeItem(key, window) { try { const localStorageTarget = window ? window.localStorage : localStorage; localStorageTarget.removeItem(key); } catch (error) { console.warn('Error removing from localStorage:', error); } } /** * Clears the current localStorage. */ static clear(window) { try { const localStorageTarget = window ? window.localStorage : localStorage; localStorageTarget.clear(); } catch (error) { console.warn('Error clearing localStorage:', error); } } /** * Gets the length of the local storage */ static getLength(window) { let length; try { const localStorageTarget = window ? window.localStorage : localStorage; length = localStorageTarget.length; } catch (error) { console.warn('Error getting localStorage length:', error); } return length; } /* * Gets the key by index. */ static getKey(index, window) { let value; try { const localStorageTarget = window ? window.localStorage : localStorage; value = localStorageTarget.key(index); } catch (error) { console.warn('Error getting value from localStorage by key:', error); } return value; } } MsftSme.LocalStorageHandler = LocalStorageHandler; class SessionStorageHandler { /** * Gets the session storage. * @returns The session storage. */ static getSessionStorage() { try { return sessionStorage; } catch (error) { console.warn('Error getting the sessionStorage:', error); } } /** * Sets the value for the sessionStorage by key. * @param key The key to set value. * @param value The value for the key. */ static setItem(key, value) { try { SessionStorageHandler.getSessionStorage().setItem(key, value); } catch (error) { console.warn('Error saving to sessionStorage:', error); } } /** * Gets the value from sessionStorage by key. * @param sessionStorageTarget The sessionStorage to use. * @param key The key to get value. * @returns The value for the key passed in. */ static getItem(key) { try { return SessionStorageHandler.getSessionStorage().getItem(key); } catch (error) { console.warn('Error retrieving from sessionStorage:', error); } return '{}'; } /** * Removes the value for the sessionStorage by key. * @param sessionStorageTarget The sessionStorage to use. * @param key The key to remove value. */ static removeItem(key) { try { SessionStorageHandler.getSessionStorage().removeItem(key); } catch (error) { console.warn('Error removing from sessionStorage:', error); } } /** * Clears the current sessionStorage. * @param sessionStorageTarget The sessionStorage to use. */ static clear() { try { SessionStorageHandler.getSessionStorage().clear(); } catch (error) { console.warn('Error clearing sessionStorage:', error); } } /** * Gets the length of the sessionStorage * @param sessionStorageTarget The sessionStorage to use. * @returns */ static getLength() { let length; try { length = SessionStorageHandler.getSessionStorage().length; } catch (error) { console.warn('Error getting sessionStorage length:', error); } return length; } /* Gets the key by index. * @param sessionStorageTarget The sessionStorage to use. * @returns */ static getKey(index) { let value; try { value = SessionStorageHandler.getSessionStorage().key(index); } catch (error) { console.warn('Error getting value from sessionStorage by key:', error); } return value; } } MsftSme.SessionStorageHandler = SessionStorageHandler; /** * For testing only. Use Object.keys. */ function _objectKeysPolyfill(o) { switch (typeof o) { case stringType: const arr = []; for (let i in o) { array_push(arr, i); } return arr; case functionType: case objectType: case undefinedType: return object_keys_original(o); default: // probably some other native type return []; } } MsftSme._objectKeysPolyfill = _objectKeysPolyfill; // intentionally put inside an anonymous function. // Presence of try-catch impacts the browser's ability // to optimize code in the same function context. (() => { try { object_keys_original(""); } catch (err) { // it threw so this browser is in ES5 mode (probably IE11) // let's polyfill Object.keys ObjectGlobal.keys = _objectKeysPolyfill; } })(); const object_keys = ObjectGlobal.keys; function isTypeOf(obj, type) { return typeof obj === type; } function isDate(obj) { return obj instanceof Date; } function isFunction(obj) { return isTypeOf(obj, functionType); } MsftSme.isFunction = isFunction; function isNumber(obj) { return isTypeOf(obj, numberType); } MsftSme.isNumber = isNumber; const regex_NonSpace = /\S/; const regex_WhiteSpace = /\s/; function getDisposeFunc(value) { let dispose = value && value.dispose; return isTypeOf(dispose, functionType) && dispose; } function forEachKey(obj, iterator) { array_forEach(object_keys(obj), (k) => { iterator(k, obj[k]); }); } MsftSme.forEachKey = forEachKey; /** * Shortcut for Object.keys(obj || {}).length. * @return number. */ function keysLength(obj) { return object_keys(obj || {}).length; } MsftSme.keysLength = keysLength; /** * Determines whether an object has properties on it. * Will return true for the following inputs: [], {}, "", 0, 1, true, false, new Date(), function() {}. * Will return false for the following inputs: [1], {a:1}, "123". * @return boolean. */ function isEmpty(obj) { return !keysLength(obj); } MsftSme.isEmpty = isEmpty; /** * Detect a value is Disposable. * * @param value The value to check against value.dispose is a function. * @return boolean. */ function isDisposable(value) { return !!getDisposeFunc(value); } MsftSme.isDisposable = isDisposable; function _disposeDisposable(value) { if (array_isArray(value)) { array_forEach(value, _disposeDisposable); } const dispose = getDisposeFunc(value); if (dispose) { dispose.call(value); // dispose typically rely on this is the object. } } /** * call value.dispose() if a value is Disposable. * * @param value The value to call value.dispose() * @return boolean; */ MsftSme.disposeDisposable = function ( /*...values: any[]*/) { array_forEach(arguments, _disposeDisposable); }; /** * Detect a value is null. * * @param value The value to check against null. * @return boolean. */ function isNull(value) { return value === null; } MsftSme.isNull = isNull; /** * Detect a value is undefined. * * @param value The value to check against undefined. * @return boolean. */ function isUndefined(value) { return value === _undefined; } MsftSme.isUndefined = isUndefined; /** * Indicates whether the specified object is null or undefined. * * @param value The value to test. * @returns True if the value parameter is null or undefined; otherwise, false. */ function isNullOrUndefined(value) { return value === null || value === _undefined; } MsftSme.isNullOrUndefined = isNullOrUndefined; /** * Indicates whether the specified object is not null or undefined. * * @param value The value to test. * @returns True if the value parameter is null or undefined; otherwise, false. */ function notNullOrUndefined(value) { return value !== null && value !== _undefined; } MsftSme.notNullOrUndefined = notNullOrUndefined; /** * Checks if the string is null, undefined or whitespace. * * @param value The target string. * @return true if the string is null, undefined or whitespace; otherwise, false. */ function isNullOrWhiteSpace(value) { // http://jsperf.com/empty-string-test-regex-vs-trim/4 return isNullOrUndefined(value) || (isTypeOf(value, stringType) && !regex_NonSpace.test(value)); // if can't find any characters other than space. } MsftSme.isNullOrWhiteSpace = isNullOrWhiteSpace; /** * Checks if a string contains a white space * * Space, tab, line feed (newline), carriage return, form feed, and vertical tab characters are all * considered white space characters - https://learn.microsoft.com/en-us/cpp/c-language/white-space-characters?view=msvc-170 * @param value the target string * @returns true if string contains a white space character else false */ function containsWhiteSpace(value) { return regex_WhiteSpace.test(value); } MsftSme.containsWhiteSpace = containsWhiteSpace; //#region Array /** * Finds the index of the first element of an array that matches the predicate. * * @param predicate The Predicate function. * @param startIndex The starting index. If negative, it find from the end of the array. * If you want to continue the next search from the back you much pass in startIndex = (prevReturn - length -1) * * @return The first index that matches the predicate. */ function findIndex(array, predicate, startIndex = 0) { if (array) { let length = array.length; let stop = length; let step = 1; let index = startIndex; if (length) { if (startIndex < 0) { index += length; step = stop = -1; } if (!predicate) { return index < length && index >= 0 ? index : -1; } while (index !== stop) { if (predicate(array[index], index, array)) { return index; } index += step; } } } return -1; } MsftSme.findIndex = findIndex; /** * Finds the first element of an array that matches the predicate. * * @param predicate The Predicate function. * @param startIndex The starting index. If negative, it find from the end of the array. * If you want to continue the next search from the back you much pass in startIndex = (prevReturn - length -1) * * @return The first element that matches the predicate. */ function find(array, predicate, startIndex) { const index = findIndex(array, predicate, startIndex); return index < 0 ? _undefined : array[index]; } MsftSme.find = find; /** * Returns the first element of the sequence. * * @return The element */ function first(array) { return array ? array[0] : _undefined; } MsftSme.first = first; /** * Returns the last element of the sequence. * * @return The element */ function last(array) { const length = array ? array.length : 0; return length ? array[length - 1] : _undefined; } MsftSme.last = last; /** * Removes all values that equal the given item and returns them as an array * * @param item The value to be removed. * @return The removed items. */ function remove(array, itemOrPredicate, startIndex = 0) { const removedItems = []; let removedCount = 0; let predicate; if (isTypeOf(itemOrPredicate, functionType)) { predicate = itemOrPredicate; } for (let i = startIndex, l = array.length; i < l; i++) { const item = array[i]; if (removedCount) { array[i - removedCount] = item; //shift the item to the offset } if (itemOrPredicate === item || (predicate && predicate(item))) { removedCount++; array_push(removedItems, item); } } if (removedCount) { array_splice(array, -1 * removedCount); // remove that many item form the end; } return removedItems; } MsftSme.remove = remove; // This function use findIndex, put it here for minimizer friendly // sourceUnique is a flag to optimize the performance, set to true if you know source is unique already. function pushUnique(uniqueTarget, source, predicate, sourceUnique = false) { if (array_isArray(source)) { let getIndex = predicate ? (value) => findIndex(uniqueTarget, (resultValue) => predicate(resultValue, value)) : (value) => uniqueTarget.indexOf(value); let appendTarget = (sourceUnique) ? [] : uniqueTarget; // if source is already unique, we accumlate to a sperated array to increase the perf. for (let i = 0, l = source.length; i < l; i++) { let value = source[i]; if (getIndex(value) < 0) { array_push(appendTarget, value); } } if (sourceUnique) { // if source is unique, we append to a uniqueTarget . array_prototype_push.apply(uniqueTarget, appendTarget); } } return uniqueTarget; } MsftSme.pushUnique = pushUnique; /** * Returns a unique set from this array based on the predicate. * * @param predicate The predicate function. Added to the result if the predicate returns false. * @return A new array with the unique values. */ function unique(array, predicate) { return pushUnique([], array, predicate); } MsftSme.unique = unique; function union() { const result = []; let lastArrayIndex = arguments.length - 2; let predicate = arguments[lastArrayIndex + 1]; // If the predicate is not a function, it means that it is the last array to union. if (!isTypeOf(predicate, functionType)) { predicate = _undefined; lastArrayIndex++; } for (let i = 0; i <= lastArrayIndex; i++) { let source = unique(arguments[i], predicate); // make a smaller set pushUnique(result, source, predicate, true /* source Unique*/); } return result; } MsftSme.union = union; /** * Merge multiple T, T[] into a combine T[] exclude null or undefined arguments. * * @param data, a list fo T, T[] * @returns concattenated array. */ MsftSme.merge = function ( /*...data: T[]*/) { // Don't use TypeScript's built-in "... rest" args syntax for perf-critical // paths because it constructs the args array even if you don't need it, let ret = []; let data = array_filter(arguments, notNullOrUndefined); return array_prototype_concat.apply(ret, data); }; /** * Projects each element of a sequence to a sequence and flattens the resulting sequences into one sequence. * * @param selector The projection function. * @return A flattened array. */ function mapMany(array, selector) { return MsftSme.merge.apply(null, array.map(selector)); } MsftSme.mapMany = mapMany; /** * Sorts an array using a stable sort algorithm. * * This method returns a new array, it does not sort in place. * * @param compare The Compare function. * @return Sorted array. */ function stableSort(array, compare) { const array2 = array.map((v, i) => { return { i: i, v: v }; }); array2.sort((a, b) => { return compare(a.v, b.v) || (a.i - b.i); }); return array2.map(v => v.v); } MsftSme.stableSort = stableSort; function toString(item) { return item ? item.toString() : String(item); } function extendArrayIntoMap(objToExtend, sourceItems, getKeyCallback, getValueCallback, onlyIfNotExist) { getKeyCallback = getKeyCallback || toString; // The use of args here is to reduce the array creation call and make sure the function context this is the sourceItems. let args = [sourceItems, /*item*/ , /*index*/ , ""]; for (let i = 0, l = sourceItems.length; i < l; i++) { let item = sourceItems[i]; args[1] = item; args[2] = i; args[3] = _undefined; let key = call.apply(getKeyCallback, args); if (!onlyIfNotExist || objToExtend[key] === _undefined) { args[3] = key; // This is convient for the get value function to know the key that previous interpreted by getKeyCallback let value = getValueCallback ? call.apply(getValueCallback, args) : item; // Only extend this key if the value return is not undefined. if (value !== _undefined) { objToExtend[key] = value; } } } } MsftSme.extendArrayIntoMap = extendArrayIntoMap; function extendStringMapIntoMap(objToExtend, sourceItems, getValueCallback, onlyIfNotExist) { // The use of args here is to reduce the array creation call and make sure the function context this is the sourceItems. let args = [sourceItems, /*item*/ , /*key*/]; forEachKey(sourceItems, (key, item) => { if (!onlyIfNotExist || objToExtend[key] === _undefined) { args[1] = item; args[2] = key; // This is convient for the get value function to know the key that previous interpreted by getKeyCallback let value = getValueCallback ? call.apply(getValueCallback, args) : (item || key); // Only extend this key if the value return is not undefined. if (value !== _undefined) { objToExtend[key] = value; } } }); } MsftSme.extendStringMapIntoMap = extendStringMapIntoMap; function getStringMapFunc(keys) { if (array_isArray(keys)) { // make a copy of keys so that future changes to the input array do not impact the behavior of the returned function. keys = array_slice(keys); } else { keys = arguments; } return function ( /* arguments*/) { // Most people call .hasOwnProperty or .constructor (which it should not) // since there is no guarantee that any object return to have those function -Expecially in generic function. // http://stackoverflow.com/questions/12017693/why-use-object-prototype-hasownproperty-callmyobj-prop-instead-of-myobj-hasow // Unfortunately, this need to be changed. let ret = {}; array_forEach(arguments, (value, index) => { let key = keys[index]; if (value !== _undefined) { ret[key] = value; } }); return ret; }; } MsftSme.getStringMapFunc = getStringMapFunc; /** * Helper funciton to create a object lightweight constructor * * @param keys the ordered argument keys * * @return The function that will return string map base on the arguments index order of keys */ function applyStringMapFunc(keys) { return getStringMapFunc.apply(_undefined, keys); } MsftSme.applyStringMapFunc = applyStringMapFunc; /** * Helper funciton to create a object of type NameValue<N, T> * * @param name name * @param value value * * @return an object of NameValue<N, T> */ MsftSme.getNameValue = getStringMapFunc("name", "value"); /** * Get a list of typeScript Enum into Array * * @param tsEnumeration The Type script Enum Array * @param sort optional whether to sort by enum's value * @return all NameValue<string, number>[] for this typeScriptEnum */ function getEnumArray(tsEnumeration, sort) { let retVal = []; forEachKey(tsEnumeration, (key, val) => { if (isTypeOf(key, stringType) && isTypeOf(val, numberType)) { array_push(retVal, MsftSme.getNameValue(key, val)); } }); return sort ? retVal.sort((a, b) => { return a.value - b.value; }) : retVal; } MsftSme.getEnumArray = getEnumArray; /** * Coerce an input into an array if it isn't already one. */ function makeArray(input) { if (!array_isArray(input)) { if (isNullOrUndefined(input)) { input = []; } else { input = [input]; } } return input; } MsftSme.makeArray = makeArray; //#endregion Array //#region Date /** * Checks if given dates are equal. * * @param left Left hand side date. * @param left Right hand side date. * @return True if left date is equal to right date. */ function areEqualDates(left, right) { return isDate(left) && isDate(right) && !(left > right || left < right); } MsftSme.areEqualDates = areEqualDates; /** * Round down the date.getTime() to seconds * * @param date. * @return the getTime in seconds */ function toSeconds(date) { // The old function use toString to trim off microseconds to time comparsion for stablesort // return new Date(x.toString()).getTime(); --- this is slow: // http://jsperf.com/truncating-decimals // x = new Date() //Wed Feb 17 2016 12:15:39 GMT- 0800(Pacific Standard Time) //y = new Date(x.toString()).getTime() //1455740139000 //z = (x.getTime() / 1000) | 0 //1455740139 return (date.getTime() / 1000) | 0; } MsftSme.toSeconds = toSeconds; //#endregion Date //#region Math const hexCharsLowerCase = string_split("0123456789abcdef", ""); const hexBytesLower = []; array_forEach(hexCharsLowerCase, (upper) => { array_forEach(hexCharsLowerCase, (lower) => { array_push(hexBytesLower, upper + lower); }); }); const sizeOfGuidInBytes = 20; const tempUintArray = new Uint8Array(sizeOfGuidInBytes); const tempStringArray = new Array(sizeOfGuidInBytes); function applyAndOr(index, and, or) { const temp = tempUintArray[index] & and; tempUintArray[index] = temp | or; } ////// TODO: const globalCrypto = <Crypto>(window.crypto || (<any>window).msCrypto); const globalCrypto = (window.crypto || window.msCrypto); // c.f. rfc4122 (UUID version 4 = xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx) // xx xx xx xx - xx xx - 4x xx - yx xx - xx xx xx xx xx xx // 00 11 22 33 4 55 66 7 88 99 10 11 12 13 14 15 16 17 18 19 function newGuidCrypto() { globalCrypto.getRandomValues(tempUintArray); applyAndOr(8, 0x0F, 0x40); // set upper half of the 8th byte to 0x4 applyAndOr(11, 0x3F, 0x80); // set the two most significant bits of the 11th byte to 10b for (let i = 0; i < sizeOfGuidInBytes; i++) { tempStringArray[i] = hexBytesLower[tempUintArray[i]]; } tempStringArray[4] = tempStringArray[7] = tempStringArray[10] = tempStringArray[13] = "-"; return MsftSme.applyUncurry(string_concat, "", tempStringArray); } /** * HelperReturns hex number string. * * @param len The number of the output string length * @return a hexnumber string of length len */ function getRandomHexString(len) { let tmp; let ret = new Array(len); // This implementation optimization of speed mimimize the cost of Math.random. // equal chance for all number while (len) { tmp = 4294967296 * MathGlobal.random() | 0; // get the max integer our of 32 digit. let times = 8; // for every random number we can harvest 8 times. while (times--) { ret[--len] = hexCharsLowerCase[tmp & 0xF]; // fill from the back. if (len < 0) { return ret; // we filled all the bucket, return now. } tmp >>>= 4; // zero fill right shift to ensure return is always positive number. } } } function newGuidFallback() { // c.f. rfc4122 (UUID version 4 = xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx) const guid = getRandomHexString(36); guid[8] = guid[13] = guid[18] = guid[23] = "-"; // fill in the dash. guid[14] = "4"; // set the 4 in the guid. // "Set the two most significant bits (bits 6 and 7) of the clock_seq_hi_and_reserved to zero and one, respectively" // guid[19] = hexValues[8 + (Math.random() * 4) | 0]; /*clockSequenceHi*/ guid[19] = hexCharsLowerCase[8 + (hexCharsLowerCase.indexOf(guid[19]) & 0x3)]; // Since guid[19] is already random. reused the numbe by get its index rather than another Math.random call. return _applyCall(string_concat, "", guid); } /** * Returns a GUID such as xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx. * * @return New GUID. */ MsftSme.newGuid = globalCrypto ? newGuidCrypto : newGuidFallback; const maxCounter = 0xFFF; function toHexString(counter) { return hexBytesLower[counter >> 4] + hexCharsLowerCase[counter & 0xF]; } function toHex(str) { let result = ''; for (let i = 0; i < str.length; i++) { result += str.charCodeAt(i).toString(16); } return result; } MsftSme.toHex = toHex; /** * Returns a function that can generate globally unique identifiers. * Generates a new guid every 4096 calls and concatenates it with an * auto incrementing number. This maintains a complient GUID 4 format * if no prefix is added. * * @return a globally unique string generating function. */ function getUniqueIdGenerator(prefix) { prefix = prefix ? (prefix + "-") : ""; // use a range for counter that gives us 3 digits with minimum effort let guid = ""; let counter = maxCounter; return () => { if (++counter > maxCounter) { counter = 0; } if (!counter) { guid = prefix + MsftSme.newGuid().substring(0, 33); } return guid + toHexString(counter); }; } MsftSme.getUniqueIdGenerator = getUniqueIdGenerator; /** * Returns a function that can generate unique id under the prefix * Concatenates prefix with an auto incrementing number. * * @return a unique string generating function which return a prefix with auto incrementing number */ function getIdGenerator(prefix) { // use two counters and // limit the size of the lower counter because // toString is expensive for large numbers let counterUpper = -1; let counterLower = maxCounter; let realPrefix = ""; return () => { if (++counterLower > maxCounter) { counterLower = 0; counterUpper++; realPrefix = prefix + counterUpper.toString(16) + "-"; } return realPrefix + toHexString(counterLower); }; } MsftSme.getIdGenerator = getIdGenerator; /** * Returns a globally unique identifier string. * Lighter-weight than newGuid. * * @return a globally unique string. */ MsftSme.getUniqueId = getUniqueIdGenerator(); /** * Rounds a number to the specified precision. * * @param number The number to round. * @param precision The precision to round the number to. Defaults to 0. * @returns The rounded number. */ function round(number, precision) { precision = MathGlobal.pow(10, precision || 0); return MathGlobal.round(Number(number) * precision) / precision; } MsftSme.round = round; /** * Truncates a number to the integer part. * * @param value The number to truncate. * @return The integer number. */ function truncate(value) { // Converts to integer by bit operation return value | 0; } MsftSme.truncate = truncate; /** * Returns the result of the boolean exclusive-or operator. * * @param a First operand. * @param b Second operand. * @return true if the arguments have different values, false otherwise. */ function xor(a, b) { return a ? !b : b; } MsftSme.xor = xor; /** * Returns the result of the bitwise and operator. Required because tslint disables * bitwise operators by default. * * @param a First operand. * @param b Second operand. * @return numerical result of applying bitwise AND to a & b */ function applyBitwiseAnd(a, b) { return a & b; } MsftSme.applyBitwiseAnd = applyBitwiseAnd; /** * Returns the result of the bitwise or operator. Required because tslint disables * bitwise operators by default. * * @param a First operand. * @param b Second operand. * @return numerical result of applying bitwise OR to a & b */ function applyBitwiseOr(a, b) { return a | b; } MsftSme.applyBitwiseOr = applyBitwiseOr; //#endregion Map //#region Number /** * Generates a random integer between min and max inclusive. * * @param min The minimum integer result. * @param max The maximum integer result. * @return A random integer. */ function random(min, max) { if (min === _undefined || max === _undefined || min > max) { return; } return MathGlobal.floor(MathGlobal.random() * (max - min + 1)) + min; } MsftSme.random = random; //#endregion Number //#region Object /** * Determines whether an object has a property with the specified name. * @param target the object to check. * @param v A property name. */ MsftSme.hasOwnProperty = object_hasOwnProperty; /** * Determines whether an object has an enumerable property with the specified name. * @param target the object to check. * @param v A property name. */ MsftSme.propertyIsEnumerable = object_propertyIsEnumerable; /** * Returns a boolean reflecting whether two scalar values (not object-typed, not array-typed, not function-typed) * are equal. Accounts for the fact that JavaScript Date derives from Object. * The caller is responsible for supplying exclusively number-, string- or Date-typed values here. * * @param left The first scalar value. * @param right The second scalar value. * @return A boolean reflecting whether the two scalar values are equal. */ function areEqual(left, right) { return left === right || areEqualDates(left, right); } MsftSme.areEqual = areEqual; /** * Verifies that two arrays are equal. * * @param array1 The array to check. * @param array2 The array to compare the first array to. * @returns A value indicating whether or not the two arrays are equal. */ function arrayEquals(array1, array2) { if (array1 === array2) { return true; } else if (!array1 || !array2) { return false; } else if (array1.length !== array2.length) { return false; } else { for (let i = 0; i < array1.length; i++) { if (array1[i] !== array2[i]) { return false; } } return true; } } MsftSme.arrayEquals = arrayEquals; function getTypeOf(x) { let typeOfX = typeof x; if (typeOfX === objectType) { if (x === null) { typeOfX = "null"; } else if (array_isArray(x)) { typeOfX = "array"; } else if (isDate(x)) { typeOfX = "date"; } } return typeOfX; } MsftSme.getTypeOf = getTypeOf; /** * Checks for deep equality of two object. * * @param a Object 1 * @param b Object 2 * @param mapFunc Optional parameter, used convert value conversion use on the value to compare * @return true if both of the object contains the same information; false otherwise; */ // eslint-disable-next-line unused-imports/no-unused-vars function deepEqualsMap(a, b, mapFunc) { if (mapFunc) { a = mapFunc(a); b = mapFunc(b); } if (a === b) { return true; } else if (!a || !b) { return false; } let typeofInput = getTypeOf(a); if (typeofInput !== getTypeOf(b)) { return false; } switch (typeofInput) { case "array": const aArray = a; return a.length === b.length && aArray.every((v, index) => deepEqualsMap(v, b[index], mapFunc)); case "date": return a.getTime() === b.getTime(); case objectType: const keysOfInput = object_keys(a); return keysOfInput.length === keysLength(b) && keysOfInput.every((key) => deepEqualsMap(a[key], b[key], mapFunc)); default: // basic type that failed the === check return false; } } /** * Checks if a given value is a javascript object or not. * * @param value Value to test. * @return True if value is an object, false otherwise. */ function isObject(value) { return typeof value === objectType; } MsftSme.isObject = isObject; /** * Checks if a given value is a plain object or not. * * @param value Value to test. * @return True if value is an object, false otherwise. */ function isPlainObject(value) { return getTypeOf(value) === objectType; } MsftSme.isPlainObject = isPlainObject; /** * Maps each value of the input object. Values that map to null or undefined are skipped. * * @param obj Input object whose properties are to be mapped. * @param callback Invoked for each property of the object to perform the mapping. * @param arg An Optional value that can be passed to callback. * @return An array of mapped values. */ function map(obj, callback, arg) { let callBackArgs = [obj, /*item*/ , /*key*/ , arg]; let keys = object_keys(obj); let ret = keys.map((key) => { callBackArgs[1] = obj[key]; // item; callBackArgs[2] = key; // key; return call.apply(callback, callBackArgs); }); // Flatten any nested arrays and exclude null, undefined items. return MsftSme.merge.apply(null, ret); } MsftSme.map = map; /** * Shallow copy from a key/value pairs object. * * @param to An un-typed object to be populated. * @param from An un-typed object with values to populate. * @param scopes Scoped down the list for shallowCopy */ function shallowCopyFromObject(to, from, scopes) { // http://jsperf.com/shallowcopyobjects/3 scopes = scopes || object_keys(from); for (let i = 0; i < scopes.length; i++) { let key = scopes[i]; let value = from[key]; if (value !== _undefined) { to[key] = value; } } } MsftSme.shallowCopyFromObject = shallowCopyFromObject; /** * Merges a property from a destination object from a source object * @param dest The destination object * @param src The source object * @param propName The name of the property to assign */ function deepAssignProperty(dest, src, propName) { let value = src[propName]; // if there is no value, dont assign. if (isNullOrUndefined(value)) { return; } if (!isObject(value)) { /** * If the src prop is not an object then set the destination prop directly. */ dest[propName] = value; } else if (isObject(value) && isEmpty(value)) { /** * If the object has no children prop then set the destination prop directly. * * Handles a special case of a date object as it cannot be shallow copied in * the same major as any other supported type. * Shown here: https://github.com/davidmarkclements/rfdc/blob/master/index.js */ if (value instanceof Date) { dest[propName] = new Date(value); } else { // Generic object will be shallow copied as it has no properties. dest[propName] = array_isArray(value) ? [...value] : { ...value }; } } else if (isObject(value) && !MsftSme.hasOwnProperty(dest, propName) || (typeof (dest[propName]) !== typeof (value))) { /** If the src prop is an object and the prop is not defined, create new object * and copy over src. * * Or if the dest and src prop do not match types, * pave over dest with src. */ dest[propName] = deepAssignInternal(array_isArray(value) ? [] : {}, value); } else { // Otherwise merge to src prop with the dest prop. dest[propName] = deepAssignInternal(dest[propName], value); } } /** * Internal method for merging one object with another * @param dest The destination object * @param src The source object */ function deepAssignInternal(dest, src) { // if the to destination is the same object as the source, save some time by just returning if (dest === src) { return dest; } // loop through the src objects properties and merge them deeply to the dest for (let propName in src) { if (MsftSme.hasOwnProperty(src, propName)) { deepAssignProperty(dest, src, propName); } } // loop through the src symbols properties and merge them deeply to the dest if (Object.getOwnPropertySymbols) { let symbols = Object.getOwnPropertySymbols(src); symbols.forEach(symbol => { if (MsftSme.propertyIsEnumerable(src, symbol)) { deepAssignProperty(dest, src, symbol); } }); } // return the destination object return dest; } /** * Merges a set of source objects to a destination object. Does not * handle objects with circular references. * * Supported types: * - Object * - Array * - Number * - String * - null * - Date * * @param dest The destination object * @param sources the source objects */ function deepAssign(dest, ...sources) { // merge all sources into the dest object in order sources.forEach(src => { return deepAssignInternal(dest, src || {}); }); // return the dest object return dest; } MsftSme.deepAssign = deepAssign; /** * Copies a source object with any nested objects. Does not * handle objects with circular references. * @param dest The source object * * Supported types: * - Object * - Array * - Number * - String * - null * - Date * * @return a new object that is a copy of the source object */ function deepCopy(source) { return deepAssign({}, source); } MsftSme.deepCopy = deepCopy; /** * Searches an object for a value at a given path. * @param object The object to search in * @param path the path to walk * @param firstPass internal use, indicates that this is the first pass. * @returns the object at the end of the path */ function getValueInternal(object, path, firstPass) { // return null if either argument is not provided if (isNullOrUndefined(object) || isNullOrUndefined(path)) { return null; } // always convert path to string let strPath = '' + path; // on the first pass, replace delimiters in the object path with * if (firstPass) { strPath = strPath.replace(/\]\.|\.|\[|\]/g, "*"); } // find the next delimiter let index = strPath.indexOf('*'); // if there are no more delimiters, return the object at the path if (index === -1) { return object[strPath]; } // split the path at the delimiter. Use the first segment as our next object and the second segment for the remaining path let next = strPath.slice(0, index); let remainingPath = strPath.slice(index + 1); if (object[next] !== undefined && remainingPath.length > 0) { // dive in recursively to the next object return getValueInternal(object[next], remainingPath, false); } // we found our target object. Return it return object[next]; } /** * Searchs an object for a value at a given path. * @param object The object to search in * @param path the path to walk * @returns the object at the end of the path */ function getValue(object, path) { // call our internal get value function return getValueInternal(object, path, true); } MsftSme.getValue = getValue; /** * Sets a value in an object at a given path * @param object The object to search in * @param path the path to walk * @param value the value to set at object.path * @param firstPass internal use, indicates that this is the first pass. * @returns the object at the end of the path */ function setValueInternal(object, path, value, firstPass) { if (isNullOrUndefined(object)) { object = {}; } if (isNullOrUndefined(path)) { return null; } // always convert path to string let strPath = '' + path; // on the first pass, replace delimiters in the object path with * if (firstPass) { strPath = strPath.replace(/\]\.|\.|\[|\]/g, "*"); } // find the next delimiter let index = strPath.indexOf('*'); // if there are no more delimiters, return the object at the path if (index === -1) { object[strPath] = value; return object; } // split the path at the delimiter. Use the first segment as our next object and the second segment for the remaining path let next = strPath.slice(0, index); let remainingPath = strPath.slice(index + 1); if (remainingPath.length > 0) { // dive in recursively to the next object return setValueInternal(object[next], remainingPath, value, false); } object[next] = value; return object; } /** * Sets a value in an object at a given path * @param object The object to search in * @param path the path