{"version":3,"names":["HASH_224","HASH_256","HASH_384","HASH_512","HASH_512_224","HASH_512_256","ROUNDS_256","ROUNDS_512","HEX_DIGITS","Rotate","nValue","nShifts","Sigma","nA","nB","nC","Sum","pWords","nIndex","Aggregate","Conglomerate","pBuffer","nOffset","pInputs","nInput","length","Compress","pHash","pIntermediate","nT0","nT1","nT2","nT4","nT","nIntermediate","Hash","sData","bHash512","nBits","sHash","slice","nLength","nBuffer","bBuffer","nPosition","nCountA","nCountB","pExpand","pShift","nChar","charCodeAt","nT3","nShift","pSum","charAt","SHA2_512","QrType","QrInfoProfileOrTicket","constructor","qrType","roleId","timeCode","saleId","this","hexPairToBase64","n1","n2","toNumber","ch","hexToBase64","text","uint8","Uint8Array","i","ch1","ch2","btoa","String","fromCharCode","apply","testHash","hashOnce","hashIterative","password","salt","iterations","hash","multiSha","user","loginRetrieveProfile","targetNetworkId","email","pwHash","jwtIds","jwtFull","AuthStore","log","url","populateHttpLoginParams","EliteConfig","urlGProfile","fetchAndReturnCors","then","res","result","completeUserCreation","jwt","firstName","lastName","searchParams","append","resendValidationEmail","createUserRequest","urlEndpoint","URL","checkNetwork","isJwtExpired","exp","Date","now","LoginError","LoadedState","attemptLoginThen","retrieveRole","relaxed","requiresAdmin","autoLogin","_jwt","role","isUserAdmin","err","isLoggedIn","LoginMailUnknown","LoginRequired","RefreshTokenExpired","showError","onSuccess","onError","rememberMeEmail","DataStorages","auto","getItem","Debug","trace","substring","indexOf","jwtFromLocalStorage","jwtFromSessionStorage","getInstance","setJwtAccess","extractJwtUnsafe","getRefreshToken","getAccessToken","asyncGet","jwtAccess","UsersProfileStore","loadMyLastProfileRole","refreshJwtAccessOnly","jwtRole","setLicenseParams","effectiveParams","RealTimeManager","setNewJwt","publicRole","stopBubbling","e","stopPropagation","showContent","content","elExternal","document","getElementById","externalContent","getAccessJwtProvider","get","getAccessJwtTokenAsync","provider","euDateFromMillis","time","addTime","date","str","getDate","getMonth","getFullYear","getHours","getMinutes","ignoreError","_err","_opt","ignoreSuccess","_text","_status","downloadOnSuccess","blob","fileName","createObjectURL","link","createElement","href","setAttribute","body","appendChild","click","removeChild","extractError","trim","startsWith","obj","JSON","parse","status","userMessage","_a","_embedded","errors","_c","_b","message","msg","_d","_g","_f","replace","error","httpStatus","alert","handleEnter","onEnter","key","Language","replaceIfNeeded","localUrl","replacingUrl","removePort","idxColon","idxSlash","strPort","setupUrl","environmentName","baseUrl","removePorts","staticUrl","replaceWith","blogUrl","encryptUrl","feedbackUrl","menuUrl","urlGNewsChannels","urlGLandingStats","urlGGdprAnonymous","profileRoleUrl","profileRoleAdminUrl","domainsUrl","inboxUrl","contractsUrl","contractsAdminUrl","calendarUrl","logosDir","jsonDir","squireDir","statsUrl","iconsUrl","photosUrl","photosCommentsUrl","photosStatsUrl","videoStreamUrl","eventsUrl","eventsTicketsUrl","eventsTicketsPaymentsUrl","mediaUrl","mediaPaymentsUrl","contactsUrl","commentsUrl","financialNewsUrl","realtimeWebSocket","cssUrl","imagesUrl","urlGProfilePort","language","ENGLISH","NETWORK_ID","MEDIUM_RESOLUTION","AutoLog","dataStore","Map","loginCallbacks","instance","push","producer","profile","accessTokenExtracted","refreshTokenExtracted","isssedMs","getReason","set","parts","extract","setItem","session","jsonProfile","stringify","forEach","callback","_name","JwtEvent","LOGIN","jwtAccessToken","jwtRefreshToken","jwtRefreshUrl","JwtProducer","profileFromLocalStorage","type","params","licenseParams","getLicenseParams","extractNumberFromParams","attributeName","idx","idx2","extractQuotas","numMB","numPhotos","withAccessToken","logOut","removeItemsWithPrefix","inMemory","clear","LOGOUT","getRememberMeEmail","setRememberMeEmail","addLoginCallback","name","cleanCallbacks","CacheEntry","data","expiration","callbacks","Array","Cache","loadFunction","lifeMs","cache","getName","put","notifyCallbacks","info","getTime","_info","cachedValue","getAsync","value","newLength","getUnsafe","CacheOneValue","KEY","_key","active","createWebSocket","that","webSocket","lastJwt","WebSocket","realtimeWebSocketUrl","onmessage","BroadcastChannel","cnt","postMessage","onclose","console","refreshWebSocket","onopen","jwtToUse","newJwt","jwtExtracted","expiring","_publicRole","notifyOnline","jwtToken","loadMyLastRoleId","geoLocate","getGeoLocate","mapNames","cacheRoles","owner","getHttp","cacheRolesAlbumConfig","cacheTheme","cacheMyLastRole","roleResult","cacheGeoIp","cachePresets","cacheThemeOptions","resultJson","loadPublicProfileRole","loadPublicAlbumsConfig","byPassCache","loadMyLastProfileRoleJwt","jwtProvider","clearMyLastProfileRole","loadMyLastPublicProfile","localProfileRole","geoLocation","ifAdminOf","onAdmin","saveRole","putHttp","loadTheme","t","loadThemeAsStyle","theme","colors","style","backgroundColor","fontColor","fontFamily","loadThemeOptions","loadPresets","adminLoadNamesFromRoles","roles","getHttpJson","encodeURIComponent","join","names","Object","entries","keys","parseInt","getMyProfileRoleUnsafe","getMyRoleIdUnsafe","ContactsStore","cacheContactsLight","cacheContactsFull","loadContactsLight","loadContactsFull","reason","tokenRenewalInProgress","accessToken","refreshToken","removeBearerFrom","refreshUrl","getProfile","getRefreshUrl","stripFinalUrlPart","secAccessLeft","secAccessDuration","issuedMs","assert","renewAccessToken","newGlobalProfile","jwtRefresh","secRefreshLeft","Promise","reject","urlString","fetchWithMutex","catch","lastIndexOf","downloadHttp","traceId","http","downloadHttpAndOpenOnNewWindow","_url","window","open","customJwtProvider","getHttpJsonChunked","fetchAndReturnCorsChunked","getHttpBody","getHttpBodyJson","putHttpJson","postHttp","_traceId","binaryDownload","postHttpJson","deleteHttp","deleteHttpJson","isError","xhr","response","responseText","endsWith","errObj","method","onSuccessHandler","onErrorHandler","jwtProducer","httpLoadWithXhr","jwtString","XMLHttpRequest","responseType","setRequestHeader","addBearerTo","onreadystatechange","DONE","OK","readyState","onerror","_e","send","_reason","split","atob","returnJsonPromise","dataPromise","json","ok","resolve","networkId","isNaN","jwtAccessExpired","init","mode","headers","Authorization","fetch","prepareFetchInit","async","dataListener","httpLoadWithFetch","chunkTerminator","reader","getReader","decoder","TextDecoder","completeChunk","done","read","chunk","decode","stream","newChunk","NanoUtil","mouseOnArea","sendMouseMoveToNanoGallery","nextImgArrow","querySelector","dispatchEvent","Event","cleanUpNanoGallery","galleryTarget","$nanogallery2","getAttributes","copyAttributes","source","target","accessJwtToken","cleanContent","cleanContentTarget","cleanExternalRender","hideButtons","contentTarget","display","hideCloseButton","hideDeleteButton","hideAddButton","hideTitleContent","hideElement","id","element","recreateNode","addEventListener","el","recreateChildren","newButton","cloneNode","parentNode","replaceChild","hasChildNodes","firstChild","showElement","onCLick","showCloseButton","onClick","showDeleteButton","showTitleContent","getThresholdColor","firstCharUppercase","toUpperCase","iframeResize","ifr","securityMargin","h","contentWindow","scrollHeight","height","encodeBase64ForUrl","base64","getUrlParams","URLSearchParams","location","search","getUrlParam","paramName","setUrlParam","paramValue","reload","urlWithoutParams","pathname","urlParams","newQueryString","toString","newUrl","history","replaceState","deleteUrlParam","delete","uploadAllFiles","files","urlUpload","onPreview","onProgress","additionalParams","filesSuccess","filesDone","filesToDo","file","uploadFile","r","fileOrBlob","formData","FormData","n","isString","bearer","statusText","resizeImageToCanvas","image","maxSize","canvas","width","getContext","drawImage","dataURItoBlob","dataURI","bytes","unescape","mime","max","ia","Blob","resizeFileToBlob","qualityPercentage","FileReader","Image","resizeAfterLoad","dataUrl","toDataURL","no","match","Error","onload","readerEvent","src","readAsDataURL","addJavascriptFile","jsUrl","defer","script","head","createMeta","meta","defineGlobalFunction","fnName","fn","addCssInline","css","styleSheet","cssText","createTextNode","documentElement","activateTheme","override","defaultCss","jsonToCss","extractJson","level1","level2","level3","level4","level5","jsonL1","j","extractCss","prefix","selectOptionByContent","selectControl","expectedValue","mapper","options","selectedIndex","selected","urlMerge","base","other","UserLoginInfo","FuzzyBoolean","fromPositiveNumber","undefined","MAYBE","TRUE","FALSE","CardsWithPosUtil","chooseCurrentCard","cards","getCards","currentCard","enabled","renderCustom","renderFn","ar","px","UriBuilder","uri","includes","build","addUrlParam","with","deepUpdateMap","map","newMap","group","SortingState","SortingInfo","colName","state","ElementsGroup","items","onNotify","register","item","addListener","notify","Refresher","listeners","add","listener","refresh","l","persistent","localStorage","sessionStorage","exec","consumer","persistData","ds","removeItem","allowPersistence","allowDataPersistence","allowConsentPersistence","ALLOW_DATA_PERSISTENCE","userNotified","removeItemsWithPrefixFromStorage","storage","len","backingMap","_initialize","getWeek","onejan","dayOfYear","valueOf","Math","ceil","isValidEmail","emailAddress","mailFormat","readAttribute","inputValue","event","userData","userAgent","navigator","userLanguage","referrer","isMobile","test","maxTouchPoints","msMaxTouchPoints","isScreenVertical","innerHeight","innerWidth","isScreenHorizontal","cssClass","userClass","before","after","KeyboardEvent","bubbles","cancelable","code","userJustSignedUp","removeFromLink","createOnLink","MouseGestureState","lastAction","mouseGesture","handlers","rateLimitMs","originX","originY","touches","clientX","clientY","AlloyFinger","swipe","evt","direction","onLeft","onRight","onTop","onBottom","pinch","onPinch","zoom","isFullScreen","fullscreenElement","enterFullScreen","requestFullscreen","exitFullScreen","exitFullscreen","isDigit","char","fixNumberSort","title","numDigits","number","txtNoDigits","isLocalHost","hostname","combineDateAndTime","datePart","timePart","dt","setHours","hour","minute","getSeconds","getMilliseconds","getMinutesFromMidnight","ensureDateTime","minutesToTime","minutes","hours","floor","mins","formattedHours","padStart","formattedMinutes","extractDate","dateTime","Intl","DateTimeFormat","year","month","day","format","extractDateTime","extractTime","second","hour12","timeString","idxTimeStart","extractTimeRange","date1","date2","onlySameDay","dateOnly1","dateOnly2","time1","time2","groupBy","array","reduce","currentValue","groupKey","joinStrings","separator","objects","skipNUll","filter","it","loadStandardCss","loadCssStyle","path","cssId","rel","retryOperation","operation","retryTimeout","maxTimeout","onFailure","maxTime","attempt","succeeded","setTimeout","ErrorHolder","additionalErrors","requiresValue","controlValue","errorMessage","extraErrorMessage","requiresInteger","isValidInteger","requiresFloat","isValidFloat","correctIf","wrongIf","getAdditionalErrorsNumber","getAdditionalErrors","hasNoErrors","hasErrors","trimmedValue","parsed","parseFloat","protocol","port","uniqueList","list","output","Set","has","setToIndexedMap"],"sources":["src/utils/sha2.ts","src/components/login/global-login/loginUtils.ts","src/utils/utils.ts"],"sourcesContent":["/**\n * ## Module / SHA2\n *\n * Implements the SHA-2 hashing algorithm which is a set of [cryptographic hash functions](http://en.wikipedia.org/wiki/Cryptographic_hash_function). Cryptographic hash functions are mathematical operations run on digital data. By comparing the computed hash to a known and expected hash value, a person can determine the data's integrity. A key aspect of cryptographic hash functions is their one-way nature: given only a computed hash value, it is generally impossible to derive the original data. The SHA-2 family consists of six hash functions with digests (hash values) that are 224, 256, 384 or 512 bits: SHA-224, SHA-256, SHA-384, SHA-512, SHA-512/224, SHA-512/256.\n *\n * SHA-256 and SHA-512 are novel hash functions computed with 32-bit and 64-bit words, respectively. They use different shift amounts and additive constants, but their structures are otherwise virtually identical, differing only in the number of rounds. SHA-224 and SHA-384 are simply truncated versions of the first two, computed with different initial values. SHA-512/224 and SHA-512/256 are also truncated versions of SHA-512, but the initial values are generated using the method described in FIPS PUB 180-4.\n *\n * Source: http://en.wikipedia.org/wiki/SHA-2\n * Author: Mark van den Brink (mark@askaround.nl)\n * License: http://www.opensource.org/licenses/MIT\n */\n\n/**\n * Hash values for SHA-224 (first 32 bits of the fractional parts of the square roots of the first 8 primes 2..19).\n */\nvar HASH_224: number[] = [0xc1059ed8, 0x367cd507, 0x3070dd17, 0xf70e5939, 0xffc00b31, 0x68581511, 0x64f98fa7, 0xbefa4fa4];\n\n/**\n * Hash values for SHA-256 (first 32 bits of the fractional parts of the square roots of the first 8 primes 2..19).\n */\nvar HASH_256: number[] = [0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19];\n\n/**\n * Hash values for SHA-384 (64 bit fractional part of the square roots of the first 8 primes 2..19).\n */\nvar HASH_384: number[] = [0xcbbb9d5d, 0xc1059ed8, 0x629a292a, 0x367cd507, 0x9159015a, 0x3070dd17, 0x152fecd8, 0xf70e5939, 0x67332667, 0xffc00b31, 0x8eb44a87, 0x68581511, 0xdb0c2e0d, 0x64f98fa7, 0x47b5481d, 0xbefa4fa4];\n\n/**\n * Hash values for SHA-512 (64 bit fractional part of the square roots of the first 8 primes 2..19).\n */\nvar HASH_512: number[] = [0x6a09e667, 0xf3bcc908, 0xbb67ae85, 0x84caa73b, 0x3c6ef372, 0xfe94f82b, 0xa54ff53a, 0x5f1d36f1, 0x510e527f, 0xade682d1, 0x9b05688c, 0x2b3e6c1f, 0x1f83d9ab, 0xfb41bd6b, 0x5be0cd19, 0x137e2179];\n\n/**\n * Hash values for SHA-512/224 (64 bit fractional part of the square roots of the first 8 primes 2..19).\n */\nvar HASH_512_224: number[] = [0x8C3D37C8, 0x19544DA2, 0x73E19966, 0x89DCD4D6, 0x1DFAB7AE, 0x32FF9C82, 0x679DD514, 0x582F9FCF, 0x0F6D2B69, 0x7BD44DA8, 0x77E36F73, 0x04C48942, 0x3F9D85A8, 0x6A1D36C8, 0x1112E6AD, 0x91D692A1];\n\n/**\n * Hash values for SHA-512/256 (64 bit fractional part of the square roots of the first 8 primes 2..19).\n */\nvar HASH_512_256: number[] = [0x22312194, 0xFC2BF72C, 0x9F555FA3, 0xC84C64C2, 0x2393B86B, 0x6F53B151, 0x96387719, 0x5940EABD, 0x96283EE2, 0xA88EFFE3, 0xBE5E1E25, 0x53863992, 0x2B0199FC, 0x2C85B8AA, 0x0EB72DDC, 0x81C52CA2];\n\n/**\n * SHA-256 round constants (first 32 bits of the fractional parts of the cube roots of the first 64 primes 2..311).\n */\nvar ROUNDS_256: number[] = [\n 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,\n 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,\n 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,\n 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,\n 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,\n 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,\n 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,\n 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2\n];\n\n/**\n * SHA-512 round constants (first 64 bits of the fractional parts of the cube roots of the first 80 primes 2..409).\n */\nvar ROUNDS_512: number[] = [\n 0x428a2f98, 0xd728ae22, 0x71374491, 0x23ef65cd, 0xb5c0fbcf, 0xec4d3b2f, 0xe9b5dba5, 0x8189dbbc,\n 0x3956c25b, 0xf348b538, 0x59f111f1, 0xb605d019, 0x923f82a4, 0xaf194f9b, 0xab1c5ed5, 0xda6d8118,\n 0xd807aa98, 0xa3030242, 0x12835b01, 0x45706fbe, 0x243185be, 0x4ee4b28c, 0x550c7dc3, 0xd5ffb4e2,\n 0x72be5d74, 0xf27b896f, 0x80deb1fe, 0x3b1696b1, 0x9bdc06a7, 0x25c71235, 0xc19bf174, 0xcf692694,\n 0xe49b69c1, 0x9ef14ad2, 0xefbe4786, 0x384f25e3, 0x0fc19dc6, 0x8b8cd5b5, 0x240ca1cc, 0x77ac9c65,\n 0x2de92c6f, 0x592b0275, 0x4a7484aa, 0x6ea6e483, 0x5cb0a9dc, 0xbd41fbd4, 0x76f988da, 0x831153b5,\n 0x983e5152, 0xee66dfab, 0xa831c66d, 0x2db43210, 0xb00327c8, 0x98fb213f, 0xbf597fc7, 0xbeef0ee4,\n 0xc6e00bf3, 0x3da88fc2, 0xd5a79147, 0x930aa725, 0x06ca6351, 0xe003826f, 0x14292967, 0x0a0e6e70,\n 0x27b70a85, 0x46d22ffc, 0x2e1b2138, 0x5c26c926, 0x4d2c6dfc, 0x5ac42aed, 0x53380d13, 0x9d95b3df,\n 0x650a7354, 0x8baf63de, 0x766a0abb, 0x3c77b2a8, 0x81c2c92e, 0x47edaee6, 0x92722c85, 0x1482353b,\n 0xa2bfe8a1, 0x4cf10364, 0xa81a664b, 0xbc423001, 0xc24b8b70, 0xd0f89791, 0xc76c51a3, 0x0654be30,\n 0xd192e819, 0xd6ef5218, 0xd6990624, 0x5565a910, 0xf40e3585, 0x5771202a, 0x106aa070, 0x32bbd1b8,\n 0x19a4c116, 0xb8d2d0c8, 0x1e376c08, 0x5141ab53, 0x2748774c, 0xdf8eeb99, 0x34b0bcb5, 0xe19b48a8,\n 0x391c0cb3, 0xc5c95a63, 0x4ed8aa4a, 0xe3418acb, 0x5b9cca4f, 0x7763e373, 0x682e6ff3, 0xd6b2b8a3,\n 0x748f82ee, 0x5defb2fc, 0x78a5636f, 0x43172f60, 0x84c87814, 0xa1f0ab72, 0x8cc70208, 0x1a6439ec,\n 0x90befffa, 0x23631e28, 0xa4506ceb, 0xde82bde9, 0xbef9a3f7, 0xb2c67915, 0xc67178f2, 0xe372532b,\n 0xca273ece, 0xea26619c, 0xd186b8c7, 0x21c0c207, 0xeada7dd6, 0xcde0eb1e, 0xf57d4f7f, 0xee6ed178,\n 0x06f067aa, 0x72176fba, 0x0a637dc5, 0xa2c898a6, 0x113f9804, 0xbef90dae, 0x1b710b35, 0x131c471b,\n 0x28db77f5, 0x23047d84, 0x32caab7b, 0x40c72493, 0x3c9ebe0a, 0x15c9bebc, 0x431d67c4, 0x9c100d4c,\n 0x4cc5d4be, 0xcb3e42b6, 0x597f299c, 0xfc657e2a, 0x5fcb6fab, 0x3ad6faec, 0x6c44198c, 0x4a475817\n];\n\n/**\n * Contains the hex digits.\n */\nvar HEX_DIGITS: string = \"0123456789abcdef\";\n\n/**\n * Right-rotate a byte with the specified number of shifts.\n * @param nValue Specifies the byte value.\n * @param nShifts Specifies the number of shifts.\n * @returns Returns the rotated value.\n */\nfunction Rotate(nValue: number, nShifts: number): number {\n \"use strict\";\n\n return (nValue >>> nShifts) | (nValue << (32 - nShifts));\n}\n\n/**\n * Calculates the sigma.\n * @param nValue Specifies the value.\n * @param nA Specifies the number of shifts for the first rotation.\n * @param nB Specifies the number of shifts for the second rotation.\n * @param nC Specifies the right shift.\n * @returns Returns the sigma.\n */\nfunction Sigma(nValue: number, nA: number, nB: number, nC: number): number {\n \"use strict\";\n\n return Rotate(nValue, nA) ^ Rotate(nValue, nB) ^ (nValue >>> nC);\n}\n\n/**\n * Calculates the sum.\n * @param pWords Reference to the words.\n * @param nIndex Specifies the index.\n * @returns Returns the sum.\n */\nfunction Sum(pWords: number[], nIndex: number): number {\n \"use strict\";\n\n return pWords[nIndex & 0x0f] += Sigma(pWords[(nIndex + 14) & 0x0f], 17, 19, 10) + pWords[(nIndex + 9) & 0x0f] + Sigma(pWords[(nIndex + 1) & 0x0f], 7, 18, 3);\n}\n\n/**\n * Aggregates two inputs.\n * @param nA Input A.\n * @param nB Input B.\n * @returns Returns the aggregated value.\n */\nfunction Aggregate(nA: number, nB: number): number {\n \"use strict\";\n\n var nC: number = (nA & 0xffff) + (nB & 0xffff);\n\n nB = (nA >> 16) + (nB >> 16) + (nC >> 16);\n\n return (nB << 16) | (nC & 0xffff);\n}\n\n/**\n * Conglomerates inputs to the buffer.\n * @param pBuffer Reference to the buffer.\n * @param nOffset Specifies the buffer offset.\n * @param pInputs Specifies the inputs.\n */\nfunction Conglomerate(pBuffer: number[], nOffset: number, ...pInputs: number[]): void {\n //\"use strict\";\n\n var nInput: number = 0;\n var nValue: number = 0;\n var nA: number = 0;\n var nB: number = 0;\n\n for (; nInput < pInputs.length / 2; nInput++) {\n nValue = pInputs[nInput];\n\n nA += nValue & 0xffff;\n nB += nValue >>> 16;\n }\n\n nB += nA >>> 16;\n\n pBuffer[nOffset + 1] = (nB << 16) | (nA & 0xffff);\n\n nA = nB >>> 16;\n nB = 0;\n\n for (; nInput < pInputs.length; nInput++) {\n nValue = pInputs[nInput];\n\n nA += nValue & 0xffff;\n nB += nValue >>> 16;\n }\n\n nB += nA >>> 16;\n\n pBuffer[nOffset] = (nB << 16) | (nA & 0xffff);\n}\n\n/**\n * Compress the specified buffer to the hash.\n * @param pHash Reference to the hash data.\n * @param pBuffer Reference to the buffer data.\n */\nfunction Compress(pHash: number[], pBuffer: number[]): void {\n \"use strict\";\n\n var pIntermediate: number[] = [pHash[0], pHash[1], pHash[2], pHash[3], pHash[4], pHash[5], pHash[6], pHash[7]];\n var pWords: number[] = [];\n var nIndex: number = 0;\n\n while (nIndex < 16) {\n pWords[nIndex] = pBuffer[(nIndex << 2) + 3] | (pBuffer[(nIndex << 2) + 2] << 8) | (pBuffer[(nIndex << 2) + 1] << 16) | (pBuffer[nIndex << 2] << 24);\n\n nIndex++;\n }\n\n for (nIndex = 0; nIndex < 64; nIndex++) {\n var nT0: number = pIntermediate[0];\n var nT1: number = pIntermediate[1];\n var nT2: number = pIntermediate[2];\n var nT4: number = pIntermediate[4];\n var nT: number = pIntermediate[7] + (Rotate(nT4, 6) ^ Rotate(nT4, 11) ^ Rotate(nT4, 25)) + ((nT4 & pIntermediate[5]) ^ (~nT4 & pIntermediate[6])) + ROUNDS_256[nIndex] + (nIndex < 16 ? pWords[nIndex] : Sum(pWords, nIndex));\n\n for (var nIntermediate: number = 7; nIntermediate >= 0; nIntermediate--) {\n pIntermediate[nIntermediate] = nIntermediate === 4 ? Aggregate(pIntermediate[3], nT) : nIntermediate === 0 ? Aggregate(nT, (Rotate(nT0, 2) ^ Rotate(nT0, 13) ^ Rotate(nT0, 22)) + ((nT0 & nT1) ^ (nT0 & nT2) ^ (nT1 & nT2))) : pIntermediate[nIntermediate - 1];\n }\n }\n\n for (nIndex = 0; nIndex < 8; nIndex++) {\n pHash[nIndex] += pIntermediate[nIndex];\n }\n}\n\n/**\n * Hashes the supplied string data using SHA-256 or SHA-512.\n * @param sData Specifies the data to hash.\n * @param bHash512 Specifies if SHA-512 instead of SHA-256 should be used.\n * @param nBits Specifies the number of bits of the digests.\n * @returns Returns the hash string.\n */\nfunction Hash(sData: string, bHash512: boolean, nBits: number): string {\n \"use strict\";\n\n var sHash: string = \"\";\n var pHash: number[] = (bHash512 ? (nBits === 224 ? HASH_512_224 : nBits === 256 ? HASH_512_256 : nBits === 384 ? HASH_384 : HASH_512) : (nBits === 224 ? HASH_224 : HASH_256)).slice();\n var nLength: number = typeof sData === \"string\" ? sData.length : 0;\n var pBuffer: number[] = [];\n var nBuffer: number = 0;\n var bBuffer: boolean = true;\n var nIndex: number = 0;\n var nPosition: number = 0;\n var nOffset: number = 0;\n var nCountA: number = 0;\n var nCountB: number = 0;\n var nA: number;\n var nB: number;\n\n if (bHash512) {\n var pExpand: number[] = [0, 0, 0, 0, 0, 0, 0, 0];\n var pShift: number[] = [24, 16, 8, 0];\n var pIntermediate: number[];\n\n do {\n pBuffer[0] = nBuffer;\n\n for (nA = 1; nA <= 32; nA++) {\n pBuffer[nA] = 0;\n }\n\n for (nA = nOffset; nIndex < nLength && nA < 128; ++nIndex) {\n var nChar: number = sData.charCodeAt(nIndex);\n\n if (nChar < 0x80) {\n pBuffer[nA >> 2] |= nChar << pShift[nA++ & 3];\n } else if (nChar < 0x800) {\n pBuffer[nA >> 2] |= (0xc0 | (nChar >> 6)) << pShift[nA++ & 3];\n pBuffer[nA >> 2] |= (0x80 | (nChar & 0x3f)) << pShift[nA++ & 3];\n } else if (nChar < 0xd800 || nChar >= 0xe000) {\n pBuffer[nA >> 2] |= (0xe0 | (nChar >> 12)) << pShift[nA++ & 3];\n pBuffer[nA >> 2] |= (0x80 | ((nChar >> 6) & 0x3f)) << pShift[nA++ & 3];\n pBuffer[nA >> 2] |= (0x80 | (nChar & 0x3f)) << pShift[nA++ & 3];\n } else {\n nChar = 0x10000 + (((nChar & 0x3ff) << 10) | (sData.charCodeAt(++nIndex) & 0x3ff));\n\n pBuffer[nA >> 2] |= (0xf0 | (nChar >> 18)) << pShift[nA++ & 3];\n pBuffer[nA >> 2] |= (0x80 | ((nChar >> 12) & 0x3f)) << pShift[nA++ & 3];\n pBuffer[nA >> 2] |= (0x80 | ((nChar >> 6) & 0x3f)) << pShift[nA++ & 3];\n pBuffer[nA >> 2] |= (0x80 | (nChar & 0x3f)) << pShift[nA++ & 3];\n }\n }\n\n nPosition += nA - nOffset;\n nOffset = nA - 128;\n\n if (nIndex === nLength) {\n pBuffer[nA >> 2] |= [-2147483648, 8388608, 32768, 128][nA & 3];\n ++nIndex;\n }\n\n nBuffer = pBuffer[32];\n\n if (nIndex > nLength && nA < 112) {\n pBuffer[31] = nPosition << 3;\n bBuffer = false;\n }\n\n for (nB = 32; nB < 160; nB += 2) {\n var nT0 = pBuffer[nB - 30];\n var nT1 = pBuffer[nB - 29];\n var nT2 = pBuffer[nB - 4];\n var nT3 = pBuffer[nB - 3];\n\n Conglomerate(pBuffer, nB, pBuffer[nB - 13], pBuffer[nB - 31], ((nT1 >>> 1) | (nT0 << 31)) ^ ((nT1 >>> 8) | (nT0 << 24)) ^ ((nT1 >>> 7) | nT0 << 25), ((nT3 >>> 19) | (nT2 << 13)) ^ ((nT2 >>> 29) | (nT3 << 3)) ^ ((nT3 >>> 6) | nT2 << 26), pBuffer[nB - 14], pBuffer[nB - 32], ((nT0 >>> 1) | (nT1 << 31)) ^ ((nT0 >>> 8) | (nT1 << 24)) ^ (nT0 >>> 7), ((nT2 >>> 19) | (nT3 << 13)) ^ ((nT3 >>> 29) | (nT2 << 3)) ^ (nT2 >>> 6));\n }\n\n pIntermediate = pHash.slice();\n\n pExpand[6] = pIntermediate[2] & pIntermediate[4];\n pExpand[7] = pIntermediate[3] & pIntermediate[5];\n\n for (nA = 0; nA < 160; nA += 8) {\n for (nB = 0; nB < 8; nB += 2) {\n var nShift: number = nB > 0 ? 8 - nB : 0;\n var pSum: number[] = [0, 0, 0, 0];\n\n Conglomerate(pSum, 0, ROUNDS_512[nA + nB + 1], pBuffer[nA + nB + 1], (pIntermediate[9 + nShift] & pIntermediate[nB === 2 ? 9 : 11 + nShift]) ^ (~pIntermediate[9 + nShift] & pIntermediate[nB === 6 ? 15 : 13 - nB]), ((pIntermediate[9 + nShift] >>> 14) | (pIntermediate[8 + nShift] << 18)) ^ ((pIntermediate[9 + nShift] >>> 18) | (pIntermediate[8 + nShift] << 14)) ^ ((pIntermediate[8 + nShift] >>> 9) | (pIntermediate[9 + nShift] << 23)), pIntermediate[15 - nB], ROUNDS_512[nA + nB], pBuffer[nA + nB], (pIntermediate[8 + nShift] & pIntermediate[nB === 2 ? 8 : 10 + nShift]) ^ (~pIntermediate[8 + nShift] & pIntermediate[nB === 6 ? 14 : 12 - nB]), ((pIntermediate[8 + nShift] >>> 14) | (pIntermediate[9 + nShift] << 18)) ^ ((pIntermediate[8 + nShift] >>> 18) | (pIntermediate[9 + nShift] << 14)) ^ ((pIntermediate[9 + nShift] >>> 9) | (pIntermediate[8 + nShift] << 23)), pIntermediate[14 - nB]);\n Conglomerate(pSum, 2, (pExpand[nB + 1] = pIntermediate[1 + nShift] & pIntermediate[nB === 2 ? 1 : 3 + nShift]) ^ (pIntermediate[1 + nShift] & pIntermediate[(nB === 6 ? nB : 4 - nB) + 1]) ^ pExpand[7 - nShift], ((pIntermediate[1 + nShift] >>> 28) | (pIntermediate[nShift] << 4)) ^ ((pIntermediate[nShift] >>> 2) | (pIntermediate[1 + nShift] << 30)) ^ ((pIntermediate[nShift] >>> 7) | (pIntermediate[1 + nShift] << 25)), (pExpand[nB] = pIntermediate[nShift] & pIntermediate[nB === 2 ? 0 : 2 + nShift]) ^ (pIntermediate[nShift] & pIntermediate[nB === 6 ? nB : 4 - nB]) ^ pExpand[6 - nShift], ((pIntermediate[nShift] >>> 28) | (pIntermediate[1 + nShift] << 4)) ^ ((pIntermediate[1 + nShift] >>> 2) | (pIntermediate[nShift] << 30)) ^ ((pIntermediate[1 + nShift] >>> 7) | (pIntermediate[nShift] << 25)));\n Conglomerate(pIntermediate, 14 - nB, pIntermediate[7 - nB], pSum[1], pIntermediate[6 - nB], pSum[0]);\n Conglomerate(pIntermediate, 6 - nB, pSum[3], pSum[1], pSum[2], pSum[0]);\n }\n }\n\n for (nA = 0; nA < 16; nA += 2) {\n Conglomerate(pHash, nA, pHash[nA + 1], pIntermediate[nA + 1], pHash[nA], pIntermediate[nA]);\n }\n } while (bBuffer);\n } else {\n nIndex = ((nCountA >> 3) & 0x3f);\n nOffset = nLength & 0x3f;\n\n if ((nCountA += (nLength << 3)) < (nLength << 3)) {\n nCountB++;\n }\n\n nCountB += nLength >> 29;\n\n for (nA = 0; nA + 63 < nLength; nA += 64) {\n for (nB = nIndex; nB < 64; nB++) {\n pBuffer[nB] = sData.charCodeAt(nPosition++);\n }\n\n Compress(pHash, pBuffer);\n\n nIndex = 0;\n }\n\n for (nA = 0; nA < nOffset; nA++) {\n pBuffer[nA] = sData.charCodeAt(nPosition++);\n }\n\n nIndex = ((nCountA >> 3) & 0x3f);\n pBuffer[nIndex++] = 0x80;\n\n for (nA = nIndex; nA < (nIndex > 56 ? 64 : 56); nA++) {\n pBuffer[nA] = 0;\n }\n\n if (nIndex > 56) {\n Compress(pHash, pBuffer);\n\n for (nA = 0; nA < 56; nA++) {\n pBuffer[nA] = 0;\n }\n }\n\n while (nA < 64) {\n for (nB = 24; nB >= 0; nB -= 8, nA++) {\n pBuffer[nA] = ((nA >= 60 ? nCountA : nCountB) >>> nB) & 0xff;\n }\n }\n\n Compress(pHash, pBuffer);\n }\n\n for (nA = 0; nA < (nBits === 512 ? 16 : nBits === 384 ? 12 : nBits === 256 ? 8 : 7); nA++) {\n for (nB = 28; nB >= 0; nB -= 4) {\n sHash += HEX_DIGITS.charAt((pHash[nA] >>> nB) & 0x0f);\n }\n }\n\n return sHash;\n}\n\n/**\n * Hashes the supplied string data using SHA2-224.\n * @param sData Specifies the data to hash.\n * @returns Returns the hash string.\n */\nexport function SHA2_224(sData: string): string {\n \"use strict\";\n\n return Hash(sData, false, 224);\n}\n\n/**\n * Hashes the supplied string data using SHA2-256.\n * @param sData Specifies the data to hash.\n * @returns Returns the hash string.\n */\nexport function SHA2_256(sData: string): string {\n \"use strict\";\n\n return Hash(sData, false, 256);\n}\n\n/**\n * Hashes the supplied string data using SHA2-384.\n * @param sData Specifies the data to hash.\n * @returns Returns the hash string.\n */\nexport function SHA2_384(sData: string): string {\n \"use strict\";\n\n return Hash(sData, true, 384);\n}\n\n/**\n * Hashes the supplied string data using SHA2-512.\n * @param sData Specifies the data to hash.\n * @returns Returns the hash string.\n */\nexport function SHA2_512(sData: string): string {\n \"use strict\";\n\n return Hash(sData, true, 512);\n}\n\n/**\n * Hashes the supplied string data using SHA2-512/224.\n * @param sData Specifies the data to hash.\n * @returns Returns the hash string.\n */\nexport function SHA2_512_224(sData: string): string {\n \"use strict\";\n\n return Hash(sData, true, 224);\n}\n\n/**\n * Hashes the supplied string data using SHA2-512/256.\n * @param sData Specifies the data to hash.\n * @returns Returns the hash string.\n */\nexport function SHA2_512_256(sData: string): string {\n \"use strict\";\n\n return Hash(sData, true, 256);\n}\n","import {JwtStringProfile} from \"./g-profile\";\nimport {SHA2_512} from '../../../utils/sha2';\nimport {\n AuthStore,\n checkNetwork,\n DataStorages,\n Debug,\n EliteConfig,\n extractJwtUnsafe,\n fetchAndReturnCors, isLoggedIn, isUserAdmin,\n JwtProducer,\n RealTimeManager, showError,\n UsersProfileStore\n} from \"../../../utils/utils\";\n\nexport enum QrType {\n HTTP_PROFILE, JSON_PROFILE, JSON_TICKET\n}\nexport class QrInfoProfileOrTicket {\n constructor(public qrType: QrType, public roleId: number, public timeCode: string, public saleId: string) {}\n}\nexport function testLogin() {\n alert(\"testLogin\");\n}\n\nfunction hexPairToBase64(n1: number, n2: number) {\n return n1 * 16 + n2;\n}\n\nfunction toNumber(ch): number {\n switch (ch) {\n case '0':\n return 0;\n case '1':\n return 1;\n case '2':\n return 2;\n case '3':\n return 3;\n case '4':\n return 4;\n case '5':\n return 5;\n case '6':\n return 6;\n case '7':\n return 7;\n case '8':\n return 8;\n case '9':\n return 9;\n case 'a':\n return 10;\n case 'b':\n return 11;\n case 'c':\n return 12;\n case 'd':\n return 13;\n case 'e':\n return 14;\n case 'f':\n return 15;\n default:\n return 0;\n }\n}\n\nfunction hexToBase64(text: string) {\n var uint8 = new Uint8Array(text.length / 2)\n\n for (let i = 0; i < text.length; i += 2) {\n let ch1 = text.charAt(i);\n let ch2 = text.charAt(i + 1);\n let n1 = toNumber(ch1);\n let n2 = toNumber(ch2);\n\n uint8[i / 2] = hexPairToBase64(n1, n2);\n //alert(ch1 + \" \" + ch2 + \" ==> \" + n1 + \" \" + n2 + \" ==> \" + hexPairToBase64(n1, n2));\n }\n\n return btoa(String.fromCharCode.apply(null, uint8))\n}\n\nexport function testHash(text) {\n return hexToBase64(SHA2_512(text));\n}\n\nfunction hashOnce(text) {\n return hexToBase64(SHA2_512(text));\n //var shaObj = new SHA2(\"SHA-512\", \"TEXT\");\n\n //shaObj.update(text);\n\n //return shaObj.getHash(\"B64\");\n}\n\nfunction hashIterative(password, salt, iterations) {\n let hash = hashOnce(btoa(salt) + '$' + password);\n\n for (let i = 1; i < iterations; i++) {\n hash = hashOnce(hash);\n }\n\n return hash;\n}\n\nexport function multiSha(password, user) {\n return hashIterative(password, user, 100);\n}\n\nexport function loginRetrieveProfile(targetNetworkId: number, email: string, pwHash: string, jwtIds = true, jwtFull = true): Promise {\n AuthStore.log(\"loginRetrieveProfile\" + targetNetworkId)\n let url = populateHttpLoginParams(EliteConfig.urlGProfile + '/loginRetrieveProfile', targetNetworkId, email, pwHash, null, null, jwtIds, jwtFull);\n\n return fetchAndReturnCors(url, true, \"GET\").then(res => res.result);\n}\n\nexport function completeUserCreation(targetNetworkId: number, jwt: string, firstName: string, lastName: string): Promise {\n AuthStore.log(\"completeUserCreation\" + targetNetworkId)\n let url = populateHttpLoginParams(EliteConfig.urlGProfile + '/completeUserCreation', targetNetworkId, null, null, firstName, lastName, true, true);\n\n url.searchParams.append(\"jwtGlobal\", jwt); // jwt valid in global network, not in the current network\n\n return fetchAndReturnCors(url, true, \"PUT\")\n}\n\nexport function resendValidationEmail(targetNetworkId: number, email: string, firstName: string, lastName: string): Promise {\n AuthStore.log(\"resendValidationEmail\" + targetNetworkId)\n let url = populateHttpLoginParams(EliteConfig.urlGProfile + '/resendValidationEmail', targetNetworkId, email, null, firstName, lastName, false, false);\n\n return fetchAndReturnCors(url, true, \"POST\")\n}\n\n\nexport function createUserRequest(targetNetworkId: number, email: string, firstName: string, lastName: string, pwHash: string, jwtIds = true, jwtFull = true): Promise {\n AuthStore.log(\"createUserRequest\" + targetNetworkId)\n let url = populateHttpLoginParams(EliteConfig.urlGProfile + \"/createUserRequest\", targetNetworkId, email, pwHash, firstName, lastName, jwtIds, jwtFull);\n\n return fetchAndReturnCors(url, true, \"POST\")\n}\n\nexport function requestPasswordReset(email: string) {\n AuthStore.log(\"requestPasswordReset\")\n let url = new URL(EliteConfig.urlGProfile + '/requestPasswordReset');\n\n url.searchParams.append('email', email);\n\n fetchAndReturnCors(url, true, \"GET\")\n}\n\n\nfunction populateHttpLoginParams(urlEndpoint: string, targetNetworkId: number, email: string, pwHash: string, firstName: string, lastName: string, jwtIds: boolean, jwtFull: boolean) {\n let url = new URL(urlEndpoint);\n\n url.searchParams.append('targetNetworkId', '' + checkNetwork(targetNetworkId));\n\n if (firstName)\n url.searchParams.append('firstName', firstName);\n if (lastName)\n url.searchParams.append('lastName', lastName);\n\n url.searchParams.append('email', email);\n url.searchParams.append('pwHash', pwHash);\n url.searchParams.append('jwtIds', '' + jwtIds);\n url.searchParams.append('jwtFull', '' + jwtFull);\n\n\n return url;\n}\n\nexport function extractCurrentJwtUnsafe() {\n let rememberMeEmail = DataStorages.auto().getItem('login-email')\n\n if (!rememberMeEmail)\n return null;\n\n let jwt: JwtProducer = AuthStore.jwtFromLocalStorage(rememberMeEmail)\n\n return jwt ? extractJwtUnsafe(jwt.extract()[0]) : null\n}\n\nexport function extractCurrentProfileUnsafe(): string {\n let rememberMeEmail = DataStorages.auto().getItem('login-email')\n\n if (!rememberMeEmail)\n return null;\n\n return AuthStore.profileFromLocalStorage(rememberMeEmail)\n}\n\nfunction isJwtExpired(jwt) {\n return jwt.exp < (Date.now() / 1000)\n}\n\nexport enum LoginError {\n RefreshTokenExpired = \"__REFRESH_TOKEN_EXPIRED__\",\n LoginMailUnknown = \"__MAIL_UNKNOWN__\",\n LoginRequired = \"__LOGIN_REQUIRED__\",\n}\n\nexport enum LoadedState {\n NOT_LOGGED_LOADED,\n NOT_LOGGED_NOT_LOADED,\n LOGGED_LOADED,\n LOGGED_NOT_LOADED\n}\n\nexport function attemptLoginThen(then: (success, message) => void, retrieveRole = false, relaxed = false, requiresAdmin = false) {\n AuthStore.log(\"attemptLoginThen\")\n autoLogin((_jwt, role) => {\n if (requiresAdmin && !isUserAdmin(role))\n then(false, \"Not an administrator\")\n else\n then(true, null)\n }, err => {\n if (relaxed) {\n if (isLoggedIn() && !requiresAdmin) {\n then(true, null)\n return\n }\n }\n if (err && err != LoginError.LoginMailUnknown && err != LoginError.LoginRequired && err != LoginError.RefreshTokenExpired)\n showError(\"Login error: \" + err)\n then(false, err)\n }, retrieveRole)\n}\n\n/** Tries to log in using the data in the localStorage */\nexport function autoLogin(onSuccess, onError, retrieveRole = true) {\n AuthStore.log(\"attemptLoginThen...\")\n let rememberMeEmail = DataStorages.auto().getItem('login-email')\n let jwt: JwtProducer = null\n\n if (rememberMeEmail) {\n Debug.trace(\"remembering: \" + rememberMeEmail.substring(0, rememberMeEmail.indexOf('@')))\n jwt = AuthStore.jwtFromLocalStorage(rememberMeEmail)\n\n if (!jwt) {\n Debug.trace(\"No JWT\")\n if (onError != null)\n onError(LoginError.LoginRequired)\n }\n } else {\n jwt = AuthStore.jwtFromSessionStorage()\n }\n\n if (jwt) {\n Debug.trace(\"jwt retrieved\")\n AuthStore.getInstance().setJwtAccess(rememberMeEmail, jwt)\n\n if (isJwtExpired(extractJwtUnsafe(jwt.getRefreshToken())) &&\n isJwtExpired(extractJwtUnsafe(jwt.getAccessToken()))) {\n Debug.trace(\"jwt expired, ask for refresh\")\n if (onError != null)\n onError(LoginError.RefreshTokenExpired)\n return\n }\n\n jwt.asyncGet(\"autoLogin()\", jwtAccess => {\n Debug.trace(\"jwt access retrieved\")\n if (retrieveRole) {\n UsersProfileStore.getInstance().loadMyLastProfileRole(role => {\n Debug.trace(\"retrieving role\")\n if (role) {\n Debug.trace(\"role retrieved\")\n //console.log(\"AutoLogin jwt: \" + role.jwtRole)\n AuthStore.getInstance().refreshJwtAccessOnly(role.jwtRole)\n AuthStore.getInstance().setLicenseParams(role.effectiveParams)\n }\n\n RealTimeManager.getInstance().setNewJwt(jwtAccess)\n\n if (onSuccess)\n onSuccess(jwtAccess, role ? role.publicRole : null)\n })\n } else\n if (onSuccess)\n onSuccess(jwtAccess, null)\n }, onError)\n } else {\n Debug.trace(\"Mail unknown: \" + rememberMeEmail)\n if (onError != null)\n onError(LoginError.LoginMailUnknown)\n }\n}\n\n","/// Unfortunately the controls cannot extend a class and interfaces are not very useful here\n/// Unfortunately the controls cannot extend a class and interfaces are not very useful here\n\n//import {Components} from \"../components\";\nimport {ExternalRender} from \"../components/external-render/external-render\";\nimport {autoLogin} from \"../components/login/global-login/loginUtils\";\nimport AlloyFinger from 'alloyfinger';\n\n//import {renderToString} from \"@stencil/core/internal/hydrate/runner\";\nexport let stopBubbling = (e) => e.stopPropagation();\n\nexport function curry(f) { // curry(f) does the currying transform\n return function (a) {\n return function (b) {\n return f(a, b);\n };\n };\n}\n\nexport interface EliteController {\n\n}\n\nexport function showContent(content) {\n //let elExternal: any = document.getElementById(\"contentTarget\");\n let elExternal: any = document.getElementById(\"externalRender\");\n\n if (content)\n (elExternal as ExternalRender).externalContent = content;\n //ReactDOM.render(myelement, document.getElementById('root'))\n //elExternal.innerHTML = renderToString(content)\n}\n\n/*export function setCookie(cookieName, cookieValue, expirationDays) {\n let date = new Date();\n\n date.setTime(date.getTime() + (expirationDays * 24 * 60 * 60 * 1000));\n\n let expires = \"expires=\" + date.toUTCString();\n\n document.cookie = cookieName + \"=\" + cookieValue + \";\" + expires + \";path=/\";\n}\n\nexport function getCookie(name) {\n let nameEQ = name + \"=\";\n let ca = document.cookie.split(';');\n\n for (let c of ca) {\n while (c.charAt(0) == ' ')\n c = c.substring(1, c.length);\n\n if (c.indexOf(nameEQ) == 0)\n return c.substring(nameEQ.length, c.length);\n }\n\n return null;\n}*/\n\nexport function getAccessJwtProvider(): JwtProducer {\n return AuthStore.getInstance().get(\"access\")\n}\n\nexport function getAccessJwtTokenAsync(onSuccess: (string) => void) {\n let provider: JwtProducer = getAccessJwtProvider()\n\n if (provider)\n provider.asyncGet(\"getAccessJwtTokenAsync()\", jwtRole => {\n onSuccess(jwtRole)\n }, showError, true)\n}\n\nexport function isLoggedIn(): boolean {\n return getAccessJwtProvider() != null;\n}\n\nexport function euDateFromMillis(time: number, addTime: boolean): string {\n let date = new Date(time)\n let str = date.getDate() + \"/\" + date.getMonth() + \"/\" + date.getFullYear();\n\n if (addTime) {\n str += \" \" + date.getHours() + \":\" + date.getMinutes()\n }\n\n return str;\n}\n\nexport function ignoreError(_err, _opt = null) {\n}\n\nexport function ignoreSuccess(_text, _status = null) {\n}\n\nexport function showSuccess(text) {\n alert(text)\n}\n\nexport function showOnSuccess(blob) {\n let url = URL.createObjectURL(blob);\n window.open(url, \"_blank\")\n}\n\nexport function downloadOnSuccess(blob, fileName: string) {\n let url = URL.createObjectURL(blob);\n const link = document.createElement('a');\n link.href = url;\n link.setAttribute('download', fileName);\n document.body.appendChild(link);\n link.click();\n document.body.removeChild(link);\n}\n\nexport function extractError(err) {\n if (!err || (typeof err == \"string\" && !err.trim().startsWith(\"{\")))\n return err;\n\n try {\n let obj = typeof err == \"string\" ? JSON.parse(err) : err\n\n if (obj.status == 403)\n return \"Access forbidden. Please login.\";\n else if (obj.userMessage)\n return obj.userMessage;\n else if (obj._embedded?.errors && obj._embedded?.errors[0]?.message) {\n var msg = obj._embedded?.errors && obj._embedded?.errors[0]?.message\n return msg.replace(\"Internal Server Error: \", \"\");\n } else if (obj.message)\n return obj.message;\n else if (obj.error)\n return obj.error;\n else\n return err;\n } catch (e) {\n return err\n }\n}\nexport function showError(err, httpStatus = -1) {\n if (!err || err == \"\") {\n if (httpStatus == 403)\n alert(\"User unauthorized. Please login again\")\n\n // Network error\n return\n }\n\n alert(extractError(err))\n}\n\nexport function handleEnter(e, onEnter) {\n if (e.key !== 'Enter')\n return;\n\n onEnter()\n}\n\nexport enum Language {\n ENGLISH, NORWEGIAN\n}\n\nexport class EliteConfig {\n /// NOTE: If adding URLs, please fix the functions replacing them and removing the port, to support multiple environments\n public static blogUrl = \"http://localhost:8093/blog\"\n public static encryptUrl = \"http://localhost:8095/encrypt\"\n public static feedbackUrl = \"http://localhost:8093/feedback\"\n public static menuUrl = \"http://localhost:8093/menu\"\n public static urlGProfilePort = 'http://localhost:8091';\n public static urlGProfile = EliteConfig.urlGProfilePort + '/profile';\n public static urlGNewsChannels = EliteConfig.urlGProfilePort + '/newschannels';\n public static urlGLandingStats = EliteConfig.urlGProfilePort + '/landingstats';\n public static urlGGdprAnonymous = EliteConfig.urlGProfilePort + '/gdpr-anonymous';\n public static profileRoleUrl = \"http://localhost:8092/role\";\n public static profileRoleAdminUrl = \"http://localhost:8092/roleAdmin\";\n public static domainsUrl = \"http://localhost:8092/domains\";\n public static inboxUrl = \"http://localhost:8901/inbox\"\n public static contractsUrl = \"http://localhost:8907/contracts\"\n public static contractsAdminUrl = \"http://localhost:8907/contractsAdmin\"\n public static calendarUrl = \"http://localhost:8907/calendars\"\n public static logosDir = \"http://localhost:8080/logos\"\n public static jsonDir = \"http://localhost:8080/social/json\"\n //public static jsonDir = \"http://localhost:8080/assets\"\n public static squireDir = \"http://localhost:8080/squire\"\n public static statsUrl = \"http://localhost:8902/stats\";\n public static iconsUrl = \"http://localhost:8080/emoji\";\n public static photosUrl = \"http://localhost:8094/photos\";\n public static photosCommentsUrl = \"http://localhost:8094/comments\";\n public static photosStatsUrl = \"http://localhost:8094/stats\";\n public static mediaUrl = \"http://localhost:8094/media\";\n public static videoStreamUrl = \"http://localhost:8921/video-stream\";\n public static eventsUrl = \"http://localhost:8920/events\";\n public static eventsTicketsUrl = \"http://localhost:8920/events-tickets\";\n public static eventsTicketsPaymentsUrl = \"http://localhost:8920/events-tickets/payments\";\n public static mediaPaymentsUrl = \"http://localhost:8094/media/payments\";\n public static contactsUrl = \"http://localhost:8910/contacts\";\n public static commentsUrl = \"http://localhost:8906/comments\";\n public static financialNewsUrl = \"http://localhost:8930/financialnews\"\n public static realtimeWebSocket = \"ws://localhost:8911/realtime/ws\"\n public static cssUrl = \"http://localhost:8080/css\"\n public static imagesUrl = \"http://localhost:8080/images\"\n\n public static language = Language.ENGLISH\n\n /// NOTE: If adding URLs, please fix the functions changing them to support multiple environments\n public static readonly NETWORK_ID = 1\n public static readonly MEDIUM_RESOLUTION = 1024\n public static replacingUrl = null\n public static environmentName = \"unknown\";\n public static staticUrl: string = null;\n\n static replaceIfNeeded(url: string) {\n let localUrl = \"http://localhost\"\n\n return EliteConfig.replacingUrl && url ? url.replace(localUrl, EliteConfig.replacingUrl) : url\n }\n\n static removePort(url: string) {\n let idxColon = url.indexOf(\":\", 8)\n\n if (idxColon < 0)\n return url;\n\n let idxSlash = url.indexOf(\"/\", idxColon + 1)\n\n if (idxSlash < 0)\n return url;\n\n let strPort = url.substring(idxColon, idxSlash)\n\n return url.replace(strPort, \"\")\n }\n\n static setupUrl(environmentName: string, baseUrl: string, removePorts, staticUrl: string) {\n EliteConfig.environmentName = environmentName\n\n //console.log(\"setupUrl(): \" + environmentName +\": \" + baseUrl + \" - \" + staticUrl)\n\n if (baseUrl)\n this.replaceWith(baseUrl, staticUrl)\n\n if (removePorts) {\n EliteConfig.blogUrl = EliteConfig.removePort(EliteConfig.blogUrl)\n EliteConfig.encryptUrl = EliteConfig.removePort(EliteConfig.encryptUrl)\n EliteConfig.feedbackUrl = EliteConfig.removePort(EliteConfig.feedbackUrl)\n EliteConfig.menuUrl = EliteConfig.removePort(EliteConfig.menuUrl)\n EliteConfig.urlGProfile = EliteConfig.removePort(EliteConfig.urlGProfile)\n EliteConfig.urlGNewsChannels = EliteConfig.removePort(EliteConfig.urlGNewsChannels)\n EliteConfig.urlGLandingStats = EliteConfig.removePort(EliteConfig.urlGLandingStats)\n EliteConfig.urlGGdprAnonymous = EliteConfig.removePort(EliteConfig.urlGGdprAnonymous)\n EliteConfig.profileRoleUrl = EliteConfig.removePort(EliteConfig.profileRoleUrl)\n EliteConfig.profileRoleAdminUrl = EliteConfig.removePort(EliteConfig.profileRoleAdminUrl)\n EliteConfig.domainsUrl = EliteConfig.removePort(EliteConfig.domainsUrl)\n EliteConfig.inboxUrl = EliteConfig.removePort(EliteConfig.inboxUrl)\n EliteConfig.contractsUrl = EliteConfig.removePort(EliteConfig.contractsUrl)\n EliteConfig.contractsAdminUrl = EliteConfig.removePort(EliteConfig.contractsAdminUrl)\n EliteConfig.calendarUrl = EliteConfig.removePort(EliteConfig.calendarUrl)\n EliteConfig.logosDir = EliteConfig.removePort(EliteConfig.logosDir)\n EliteConfig.jsonDir = EliteConfig.removePort(EliteConfig.jsonDir)\n EliteConfig.squireDir = EliteConfig.removePort(EliteConfig.squireDir)\n EliteConfig.statsUrl = EliteConfig.removePort(EliteConfig.statsUrl)\n EliteConfig.iconsUrl = EliteConfig.removePort(EliteConfig.iconsUrl)\n EliteConfig.photosUrl = EliteConfig.removePort(EliteConfig.photosUrl)\n EliteConfig.photosCommentsUrl = EliteConfig.removePort(EliteConfig.photosCommentsUrl)\n EliteConfig.photosStatsUrl = EliteConfig.removePort(EliteConfig.photosStatsUrl)\n EliteConfig.videoStreamUrl = EliteConfig.removePort(EliteConfig.videoStreamUrl)\n EliteConfig.eventsUrl = EliteConfig.removePort(EliteConfig.eventsUrl)\n EliteConfig.eventsTicketsUrl = EliteConfig.removePort(EliteConfig.eventsTicketsUrl)\n EliteConfig.eventsTicketsPaymentsUrl = EliteConfig.removePort(EliteConfig.eventsTicketsPaymentsUrl)\n EliteConfig.mediaUrl = EliteConfig.removePort(EliteConfig.mediaUrl)\n EliteConfig.mediaPaymentsUrl = EliteConfig.removePort(EliteConfig.mediaPaymentsUrl)\n EliteConfig.contactsUrl = EliteConfig.removePort(EliteConfig.contactsUrl)\n EliteConfig.commentsUrl = EliteConfig.removePort(EliteConfig.commentsUrl)\n EliteConfig.financialNewsUrl = EliteConfig.removePort(EliteConfig.financialNewsUrl)\n EliteConfig.realtimeWebSocket = EliteConfig.removePort(EliteConfig.realtimeWebSocket)\n EliteConfig.cssUrl = EliteConfig.removePort(EliteConfig.cssUrl)\n EliteConfig.imagesUrl = EliteConfig.removePort(EliteConfig.imagesUrl)\n }\n\n EliteConfig.staticUrl = staticUrl\n }\n\n static replaceWith(replacingUrl: string, staticUrl: string) {\n EliteConfig.replacingUrl = replacingUrl\n let localUrl = \"http://localhost\"\n\n EliteConfig.blogUrl = EliteConfig.blogUrl.replace(localUrl, replacingUrl)\n EliteConfig.encryptUrl = EliteConfig.encryptUrl.replace(localUrl, replacingUrl)\n EliteConfig.feedbackUrl = EliteConfig.feedbackUrl.replace(localUrl, replacingUrl)\n EliteConfig.menuUrl = EliteConfig.menuUrl.replace(localUrl, replacingUrl)\n EliteConfig.urlGProfile = EliteConfig.urlGProfile.replace(localUrl, replacingUrl)\n EliteConfig.urlGNewsChannels = EliteConfig.urlGNewsChannels.replace(localUrl, replacingUrl)\n EliteConfig.urlGLandingStats = EliteConfig.urlGLandingStats.replace(localUrl, replacingUrl)\n EliteConfig.urlGGdprAnonymous = EliteConfig.urlGGdprAnonymous.replace(localUrl, replacingUrl)\n EliteConfig.profileRoleUrl = EliteConfig.profileRoleUrl.replace(localUrl, replacingUrl)\n EliteConfig.profileRoleAdminUrl = EliteConfig.profileRoleAdminUrl.replace(localUrl, replacingUrl)\n EliteConfig.domainsUrl = EliteConfig.domainsUrl.replace(localUrl, replacingUrl)\n EliteConfig.inboxUrl = EliteConfig.inboxUrl.replace(localUrl, replacingUrl)\n EliteConfig.contractsUrl = EliteConfig.contractsUrl.replace(localUrl, replacingUrl)\n EliteConfig.contractsAdminUrl = EliteConfig.contractsAdminUrl.replace(localUrl, replacingUrl)\n EliteConfig.calendarUrl = EliteConfig.calendarUrl.replace(localUrl, replacingUrl)\n EliteConfig.statsUrl = EliteConfig.statsUrl.replace(localUrl, replacingUrl)\n EliteConfig.photosUrl = EliteConfig.photosUrl.replace(localUrl, replacingUrl)\n EliteConfig.photosCommentsUrl = EliteConfig.photosCommentsUrl.replace(localUrl, replacingUrl)\n EliteConfig.photosStatsUrl = EliteConfig.photosStatsUrl.replace(localUrl, replacingUrl)\n EliteConfig.videoStreamUrl = EliteConfig.videoStreamUrl.replace(localUrl, replacingUrl)\n EliteConfig.eventsUrl = EliteConfig.eventsUrl.replace(localUrl, replacingUrl)\n EliteConfig.eventsTicketsUrl = EliteConfig.eventsTicketsUrl.replace(localUrl, replacingUrl)\n EliteConfig.eventsTicketsPaymentsUrl = EliteConfig.eventsTicketsPaymentsUrl.replace(localUrl, replacingUrl)\n EliteConfig.mediaUrl = EliteConfig.mediaUrl.replace(localUrl, replacingUrl)\n EliteConfig.mediaPaymentsUrl = EliteConfig.mediaPaymentsUrl.replace(localUrl, replacingUrl)\n EliteConfig.contactsUrl = EliteConfig.contactsUrl.replace(localUrl, replacingUrl)\n EliteConfig.commentsUrl = EliteConfig.commentsUrl.replace(localUrl, replacingUrl)\n EliteConfig.financialNewsUrl = EliteConfig.financialNewsUrl.replace(localUrl, replacingUrl)\n EliteConfig.realtimeWebSocket = EliteConfig.realtimeWebSocket.replace(localUrl, replacingUrl)\n\n if (staticUrl) {\n EliteConfig.logosDir = EliteConfig.logosDir.replace(localUrl, staticUrl)\n EliteConfig.jsonDir = EliteConfig.jsonDir.replace(localUrl, staticUrl)\n EliteConfig.squireDir = EliteConfig.squireDir.replace(localUrl, staticUrl)\n EliteConfig.iconsUrl = EliteConfig.iconsUrl.replace(localUrl, staticUrl)\n EliteConfig.cssUrl = EliteConfig.cssUrl.replace(localUrl, staticUrl)\n EliteConfig.imagesUrl = EliteConfig.imagesUrl.replace(localUrl, staticUrl)\n }\n }\n}\n\nexport class AutoLog {\n time: number\n message: string\n\n constructor(message: string) {\n this.time = Date.now();\n this.message = message;\n }\n}\n\n// @ts-ignore\nexport class AuthStore {\n private static instance: AuthStore = new AuthStore();\n private dataStore: Map = new Map();\n private licenseParams: any\n private loginCallbacks: Map void /* false to remove the listener */ )> = new Map()\n email: string\n public log: AutoLog[] = []\n\n private constructor() {\n }\n\n public static getInstance(): AuthStore {\n return AuthStore.instance;\n }\n\n public static log(message: string) {\n AuthStore.getInstance().log.push(new AutoLog(message))\n}\n\n\n // Should we support more than email?\n public setJwtAccess(email: string, producer: JwtProducer, profile: any = null): void {\n let accessTokenExtracted = extractJwtUnsafe(producer.getAccessToken())\n let refreshTokenExtracted = extractJwtUnsafe(producer.getRefreshToken())\n\n if (accessTokenExtracted.isssedMs < refreshTokenExtracted.isssedMs) {\n // This should not happened, but I saw it happening several times\n AuthStore.log(\"setJwtAccess: AccessToken emitted before RefreshToken by \" + (refreshTokenExtracted.isssedMs - accessTokenExtracted.isssedMs) + \" ms - \" + producer.getReason())\n }\n this.dataStore.set(\"access\", producer)\n this.email = email\n\n let parts = producer.extract()\n\n //console.log(\"Part 0: \" + parts[0])\n DataStorages.auto().setItem(\"jwtAccessToken/\" + email, parts[0]);\n DataStorages.auto().setItem(\"jwtRefreshToken/\" + email, parts[1]);\n DataStorages.auto().setItem(\"jwtRefreshUrl/\" + email, parts[2]);\n DataStorages.session().setItem(\"jwtAccessTokenSession\", parts[0]);\n DataStorages.session().setItem(\"jwtRefreshTokenSession\", parts[1]);\n DataStorages.session().setItem(\"jwtRefreshUrlSession\", parts[2]);\n\n if (profile) {\n let jsonProfile = JSON.stringify(profile)\n DataStorages.auto().setItem(\"profile/\" + email, jsonProfile);\n DataStorages.session().setItem(\"profileSession\", jsonProfile);\n }\n\n this.loginCallbacks.forEach((callback, _name ) => callback(JwtEvent.LOGIN, producer))\n }\n\n static jwtFromLocalStorage(email: string): JwtProducer {\n Debug.trace(\"jwtFromLocalStorage()\")\n let jwtAccessToken = DataStorages.auto().getItem(\"jwtAccessToken/\" + email);\n let jwtRefreshToken = DataStorages.auto().getItem(\"jwtRefreshToken/\" + email);\n let jwtRefreshUrl = DataStorages.auto().getItem(\"jwtRefreshUrl/\" + email);\n let profile = DataStorages.auto().getItem(\"profile/\" + email);\n\n Debug.trace(\"token retrieved\")\n jwtRefreshUrl = EliteConfig.replaceIfNeeded(jwtRefreshUrl)\n\n if (jwtAccessToken && jwtRefreshToken && jwtRefreshUrl) {\n Debug.trace(\"creating producer\")\n return new JwtProducer(jwtAccessToken, jwtRefreshToken, jwtRefreshUrl, profile, \"jwtFromLocalStorage\")\n }\n\n Debug.trace(\"no tokens in local storage: AT-\" + (jwtAccessToken != null) + \" RT-\" + (jwtRefreshToken != null) + \" RU-\" + (jwtRefreshUrl != null) + \" \")\n return null\n }\n\n static jwtFromSessionStorage(): JwtProducer {\n Debug.trace(\"jwtFromSessionStorage()\")\n let jwtAccessToken = DataStorages.session().getItem(\"jwtAccessTokenSession\");\n let jwtRefreshToken = DataStorages.session().getItem(\"jwtRefreshTokenSession\");\n let jwtRefreshUrl = DataStorages.session().getItem(\"jwtRefreshUrlSession\");\n let profile = DataStorages.session().getItem(\"profileSession\");\n\n Debug.trace(\"token retrieved\")\n jwtRefreshUrl = EliteConfig.replaceIfNeeded(jwtRefreshUrl)\n\n if (jwtAccessToken && jwtRefreshToken && jwtRefreshUrl) {\n Debug.trace(\"creating producer\")\n return new JwtProducer(jwtAccessToken, jwtRefreshToken, jwtRefreshUrl, profile, \"jwtFromSessionStorage\")\n }\n\n Debug.trace(\"no tokens in session storage: AT-\" + (jwtAccessToken != null) + \" RT-\" + (jwtRefreshToken != null) + \" TU-\" + (jwtRefreshUrl != null) + \" \")\n return null\n }\n\n static profileFromLocalStorage(email: string): string {\n // localStorage\n return DataStorages.auto().getItem(\"profile/\" + email);\n }\n\n public get(type: string): JwtProducer {\n return this.dataStore.get(type);\n }\n\n public setLicenseParams(params: any) {\n this.licenseParams = params\n }\n\n public getLicenseParams() {\n return this.licenseParams\n }\n\n private extractNumberFromParams(attributeName: string) {\n let str = attributeName + \"\\\":\"\n let idx = this.licenseParams.indexOf(attributeName)\n\n if (idx < 0)\n return null\n\n idx += str.length\n let idx2 = idx;\n\n while (this.licenseParams.charAt(idx2) != ',' && this.licenseParams.charAt(idx2) != '}')\n idx2++\n\n return this.licenseParams.substring(idx, idx2)\n }\n\n public extractQuotas() {\n return {numMB: this.extractNumberFromParams(\"numMB\"), numPhotos: this.extractNumberFromParams(\"numPhotos\")}\n }\n\n public refreshJwtAccessOnly(jwtAccessToken: any) {\n let jwt = getAccessJwtProvider()\n\n AuthStore.log(\"refreshJwtAccessOnly: jwt is \" + (jwt ? \"not null\" : \"null\"))\n\n if (jwt) {\n this.setJwtAccess(this.email, jwt.withAccessToken(jwtAccessToken))\n }\n }\n\n public logOut() {\n this.dataStore.set(\"access\", null)\n\n DataStorages.removeItemsWithPrefix(\"jwt\")\n DataStorages.removeItemsWithPrefix(\"profile/\")\n DataStorages.removeItemsWithPrefix(\"profileSession\")\n DataStorages.removeItemsWithPrefix(\"userJustSignedUp\")\n DataStorages.inMemory().clear()\n\n // FIXME: circular dependency; there should be a list of items to preserve on logout\n /*let rememberMeEmail = DataStorages.auto().getItem('login-email')\n let paymentProviderId = sessionStorage.getItem(ShoppingCartContentDto.PROVIDER_ID_SESSION_STORAGE_NAME)\n let shoppingCart = sessionStorage.getItem(\"shopping-cart\")\n DataStorages.auto().clear()\n DataStorages.session().clear()\n DataStorages.auto().setItem('login-email', rememberMeEmail)\n UsersProfileStore.getInstance().clearMyLastProfileRole()\n\n if (paymentProviderId)\n sessionStorage.setItem(ShoppingCartContentDto.PROVIDER_ID_SESSION_STORAGE_NAME, paymentProviderId)\n if (shoppingCart)\n sessionStorage.setItem(\"shopping-cart\", shoppingCart)*/\n\n this.loginCallbacks.forEach((callback, _name ) => callback(JwtEvent.LOGOUT, null))\n }\n\n public getRememberMeEmail(): string {\n return DataStorages.auto().getItem('login-email')\n }\n\n public setRememberMeEmail(email: string): void {\n DataStorages.auto().setItem('login-email', email)\n }\n\n public addLoginCallback(name: string, callback: (event: JwtEvent, jwt: JwtProducer) => void): void {\n this.loginCallbacks.set(name, callback)\n }\n\n public cleanCallbacks() {\n this.loginCallbacks.clear()\n }\n}\n\nclass CacheEntry {\n data: V\n expiration: number\n callbacks: Array;\n\n constructor(data: V, expiration: number) {\n this.data = data;\n this.expiration = expiration;\n this.callbacks = new Array();\n }\n}\n\nexport function insertAfter(existingNode, newNode) {\n existingNode.parentNode.insertBefore(newNode, existingNode.nextSibling);\n}\n\nexport function insertBefore(existingNode, newNode) {\n existingNode.parentNode.insertBefore(newNode, existingNode);\n}\n\nexport function removeAllChildNodes(parent, exceptChild) {\n let removed = false;\n\n while (parent && parent.firstChild) {\n if (parent.firstChild == exceptChild)\n removed = true;\n parent.removeChild(parent.firstChild);\n }\n\n if (removed)\n parent.appendChild(exceptChild)\n}\n\nexport function htmlToElement(html) {\n let template = document.createElement('template');\n template.innerHTML = html.trim();\n return template.content.firstChild;\n}\n\nclass Cache {\n private loadFunction: (key, onSuccess: (value) => void, onError: (error) => void) => void;\n private lifeMs: number;\n // FIXME: limit the number of entries stored\n private cache: { [key: string]: CacheEntry; } = {}\n private name\n\n constructor(loadFunction: (key, onSuccess: (value) => void, onError: (error) => void) => void, lifeMs: number, name: string) {\n this.loadFunction = loadFunction;\n this.lifeMs = lifeMs;\n this.name = name\n }\n\n public getName() {\n return this.name\n }\n\n public put(key: any, result: any, notifyCallbacks: boolean = true, info: string = null) {\n if (this.cache[key]) {\n this.cache[key].data = result;\n this.cache[key].expiration = new Date().getTime() + this.lifeMs;\n info += \", cache present\"\n } else {\n this.cache[key] = new CacheEntry(result, new Date().getTime() + this.lifeMs)\n }\n info += \", no cache\"\n\n if (notifyCallbacks)\n this.notifyCallbacks(key, result, info)\n }\n\n private notifyCallbacks(key: any, result: any, _info: string = null) {\n let cachedValue: CacheEntry = this.cache[key]\n let callbacks = cachedValue.callbacks ? [...cachedValue.callbacks] : cachedValue.callbacks\n\n if (callbacks) {\n cachedValue.callbacks.length = 0;\n //console.log(\"Notify \" + callbacks.length + \" of \" + key + \": \" + JSON.stringify(result) + \" - info: \" + info)\n\n for (let i = 0; i < callbacks.length; i++) {\n if (callbacks[i])\n callbacks[i](result)\n }\n } //else\n //console.log(\"No callbacks to notify \" + \" of \" + key + \": \" + JSON.stringify(result) + \" - info: \" + info)\n }\n\n public getAsync(key: any, callback: (result) => void = null, onError: (error, status) => void = null): void {\n let cachedValue: CacheEntry = this.cache[key]\n let value = null\n let info = \"Initial caches value: \" + (cachedValue ? JSON.stringify(cachedValue.data) : null)\n\n // if cachedValue.data is null, it means there is a loading in place\n if (cachedValue && cachedValue.data && cachedValue.expiration >= new Date().getTime()) {\n value = cachedValue.data\n info += \", not expired and not null\"\n }\n\n if (value) {\n //console.log(\"getAsync() Calling callback immediately: \" + info)\n callback(value)\n } else {\n if (!cachedValue) {\n cachedValue = this.cache[key] = new CacheEntry(null, 0)\n info += \", cached Value null\"\n }\n\n let newLength = cachedValue.callbacks.push(callback);\n\n //alert(\"GetAsync: \" + key + \" for \" + this.name + \" new Length: \" + newLength)\n\n if (newLength == 1) // The value should be loaded only once\n this.loadFunction(key, result => {\n //console.log(\"loadFunction() value on \" + key + \": \" + JSON.stringify(result))\n this.put(key, result, true, info + \", loadFunction() value\")\n }, err => {\n //console.log(\"loadFunction() error on \" + key + \": \" + err)\n this.put(key, null, true, info + \", loadFunction() error\")\n if (onError)\n onError(err, -1)\n })\n }\n }\n\n public getUnsafe(key: any): any {\n let cachedValue: CacheEntry = this.cache[key]\n\n return cachedValue ? cachedValue.data : null\n }\n}\n\nclass CacheOneValue {\n private cache: Cache;\n private KEY = \"_DUMMY_\";\n\n constructor(loadFunction: (onSuccess: (V) => void, onError: (error, status) => void) => void, lifeMs: number, name: string) {\n this.cache = new Cache((_key, onSuccess: (V) => void, onError: (error, status) => void) => loadFunction(onSuccess, onError), lifeMs, name)\n }\n\n public put(result: any, notifyCallbacks: boolean = true, info: string = null) {\n this.cache.put(this.KEY, result, notifyCallbacks, info ? info + \", CacheOneValue.put()\" : \"CacheOneValue.put()\")\n }\n\n public getAsync(callback: (result) => void = null, onError: (error, status) => void = null): void {\n this.cache.getAsync(this.KEY, callback, onError)\n }\n\n public getUnsafe(): any {\n return this.cache.getUnsafe(this.KEY)\n }\n}\n\nexport class RealTimeManager {\n private static instance: RealTimeManager = new RealTimeManager();\n private static realtimeWebSocketUrl = EliteConfig.realtimeWebSocket + \"/\"\n private webSocket: WebSocket\n public active = true\n private lastJwt: string\n private newJwt: string\n\n constructor() {\n }\n\n public static getInstance(): RealTimeManager {\n return RealTimeManager.instance;\n }\n\n private createWebSocket(jwt: string) {\n let that = this\n\n if (this.active && !this.webSocket && jwt && jwt != that.lastJwt) {\n this.webSocket = new WebSocket(RealTimeManager.realtimeWebSocketUrl + jwt);\n this.webSocket.onmessage = function (msg) {\n let obj = JSON.parse(msg.data)\n //alert(msg.type + \" - \" + msg.data + \" - \" + obj.cnt);\n new BroadcastChannel(obj.cnt).postMessage(obj)\n };\n this.webSocket.onclose = function () {\n console.log(\"WebSocket closed!\")\n that.webSocket = null\n that.refreshWebSocket();\n }\n\n this.webSocket.onopen = function () {\n console.log(\"WebSocket opened!\")\n that.lastJwt = jwt\n //this.send(\"state?/103\")\n }\n }\n }\n\n private refreshWebSocket() {\n let jwtToUse = this.newJwt ? this.newJwt : this.lastJwt\n let jwtExtracted = extractJwtUnsafe(jwtToUse)\n\n if (!jwtExtracted)\n return\n\n let expiring = jwtExtracted.exp < (Date.now() / 1000 + 3)\n\n if (expiring) { // No one refreshed the JWT\n //console.log(\"refreshWebSocket() - JWT expired - autoLogin()\")\n autoLogin((jwtAccess, _publicRole) => {\n //console.log(\"refreshWebSocket() - got JWT from autoLogin()\")\n RealTimeManager.getInstance().notifyOnline(jwtAccess);\n }, () => ignoreError)\n } else {\n //console.log(\"refreshWebSocket() - JWT expired - autoLogin()\")\n this.lastJwt = null\n this.createWebSocket(jwtToUse)\n }\n }\n\n public notifyOnline(jwtToken: string) {\n if (this.active)\n this.createWebSocket(jwtToken)\n }\n\n public setNewJwt(jwtAccess: string) {\n this.newJwt = jwtAccess\n }\n}\n\nexport function loadMyLastProfileRole(onSuccess: (role) => void, onError: (err) => void = null, byPassCache = false) {\n UsersProfileStore.getInstance().loadMyLastProfileRole(onSuccess, onError, byPassCache)\n}\n\nexport function loadMyLastPublicProfile(onSuccess: (publicRole) => void) {\n UsersProfileStore.getInstance().loadMyLastPublicProfile(onSuccess)\n}\n\nexport function loadMyLastRoleId(onSuccess: (publicRole) => void) {\n UsersProfileStore.getInstance().loadMyLastRoleId(onSuccess)\n}\n\nexport function geoLocate(onSuccess: (geoLocation) => void) {\n UsersProfileStore.getInstance().getGeoLocate(onSuccess)\n}\n\nexport class UsersProfileStore {\n private static instance: UsersProfileStore = new UsersProfileStore();\n private cacheRoles: Cache;\n private cacheRolesAlbumConfig: Cache;\n private cacheTheme: Cache;\n private cacheMyLastRole: CacheOneValue;\n private cacheThemeOptions: CacheOneValue;\n private cachePresets: CacheOneValue;\n private cacheGeoIp: CacheOneValue;\n private mapNames: Map = new Map() // role => full name\n\n constructor() {\n this.cacheRoles = new Cache((owner, onSuccess: (result: any) => void) => {\n getHttp(EliteConfig.profileRoleUrl + \"/public/role/\" + owner, (text, _status) => {\n onSuccess(JSON.parse(text).result)\n }, showError, \"UsersProfileStore:cacheRoles\")\n }, 300000, \"cacheRoles\")\n\n this.cacheRolesAlbumConfig = new Cache((owner, onSuccess: (result: any) => void) => {\n getHttp(EliteConfig.profileRoleUrl + \"/public/role/\" + owner + \"/albumsConfig\", (text, _status) => {\n onSuccess(JSON.parse(text).result)\n }, showError, \"UsersProfileStore:cacheRolesConfig\")\n }, 300000, \"cacheRolesAlbumConfig\")\n\n this.cacheTheme = new Cache((owner, onSuccess: (result: any) => void, onError: (result: any) => void) => {\n getHttp(EliteConfig.profileRoleUrl + \"/public/role/\" + owner + \"/theme\", (text, _status) => {\n onSuccess(JSON.parse(text).result)\n }, (text, _status) => {\n onError(text)\n }, \"UsersProfileStore:cacheTheme\")\n }, 86400000, \"cacheTheme\")\n\n this.cacheMyLastRole = new CacheOneValue((onSuccess: (result: any) => void, onError: (error, status) => void) => {\n getHttp(EliteConfig.profileRoleUrl + \"/lastRole\", (text, _status) => {\n let roleResult = JSON.parse(text).result\n\n //console.log(\"Last role: \" + JSON.stringify(roleResult))\n onSuccess(roleResult)\n }, onError == null ? showError : onError, \"UsersProfileStore:cacheMyLastRole\")\n }, 300000, \"cacheMyLastRole\")\n\n this.cacheGeoIp = new CacheOneValue((onSuccess: (result: any) => void) => {\n getHttp(EliteConfig.profileRoleUrl + \"/public/geo\", (text, _status) => {\n onSuccess(JSON.parse(text).result)\n }, showError, \"UsersProfileStore:cacheGeoIp\")\n }, 300000, \"cacheGeoIp\")\n\n this.cachePresets = new CacheOneValue((onSuccess: (result: any) => void) => {\n getHttp(EliteConfig.profileRoleUrl + \"/public/presets\", (text, _status) => {\n onSuccess(JSON.parse(text).result)\n }, showError, \"UsersProfileStore:cachePresets\")\n }, 300000, \"cachePresets\")\n\n this.cacheThemeOptions = new CacheOneValue((onSuccess: (result: any) => void) => {\n getHttp(EliteConfig.profileRoleUrl + \"/public/themesOptions\", (text, _status) => {\n onSuccess(JSON.parse(text).resultJson)\n }, showError)\n getHttp(EliteConfig.profileRoleUrl + \"/lastRole\", (text, _status) => {\n onSuccess(JSON.parse(text).result)\n }, showError, \"UsersProfileStore:cacheThemeOptions\")\n }, 300000, \"cacheThemeOptions\")\n }\n\n public static getInstance(): UsersProfileStore {\n return UsersProfileStore.instance;\n }\n\n public loadPublicProfileRole(roleId, onSuccess: (role) => void) {\n if (roleId)\n this.cacheRoles.getAsync(roleId, onSuccess)\n }\n\n public loadPublicAlbumsConfig(owner, onSuccess: (role) => void) {\n this.cacheRolesAlbumConfig.getAsync(owner, onSuccess)\n }\n\n public loadMyLastProfileRole(onSuccess: (role) => void, onError: (error, status) => void = null, byPassCache = false) {\n if (byPassCache)\n this.cacheMyLastRole.put(null, false, \"loadMyLastProfileRole()-byPassCache\")\n\n this.cacheMyLastRole.getAsync(onSuccess, onError)\n }\n\n // Uses a custom JWT Provider and Cache bypass\n public loadMyLastProfileRoleJwt(jwtProvider: JwtProducer, onSuccess: (role) => void, onError: (error, status) => void = null) {\n getHttp(EliteConfig.profileRoleUrl + \"/lastRole\", (text, _status) => {\n let roleResult = JSON.parse(text).result\n\n this.cacheMyLastRole.put(roleResult, true, \"loadMyLastProfileRole()-byPassCache\")\n onSuccess(roleResult)\n }, onError == null ? showError : onError, \"UsersProfileStore:cacheMyLastRole\", jwtProvider)\n }\n\n public clearMyLastProfileRole() {\n this.cacheMyLastRole.put(null, false, \"clearMyLastProfileRole()\")\n }\n\n public loadMyLastPublicProfile(onSuccess: (publicRole) => void) {\n this.cacheMyLastRole.getAsync(localProfileRole => onSuccess(localProfileRole.publicRole))\n }\n\n public loadMyLastRoleId(onSuccess: (roleId) => void) {\n this.cacheMyLastRole.getAsync(localProfileRole => {\n if (localProfileRole?.publicRole?.roleId)\n onSuccess(localProfileRole.publicRole.roleId)\n })\n }\n\n public getGeoLocate(onSuccess: (geoLocation) => void) {\n this.cacheGeoIp.getAsync(geoLocation => onSuccess(geoLocation))\n }\n\n public ifAdminOf(owner, onAdmin: () => void) {\n loadMyLastRoleId(roleId => {\n if (roleId == owner)\n onAdmin()\n })\n }\n\n public saveRole(role, onSuccess: () => void) {\n putHttp(EliteConfig.profileRoleUrl + \"/role\", onSuccess, showError, role)\n }\n\n public loadTheme(owner, onSuccess: (result: any) => void) {\n this.cacheTheme.getAsync(owner, t => {\n onSuccess(t)\n })\n }\n\n public loadThemeAsStyle(roleId: number, onSuccess: (theme: any, style: any) => void) {\n this.loadTheme(roleId, (theme) => {\n let colors = JSON.parse(theme.colors)\n let style = {\n '--background-color': colors?.content?.backgroundColor,\n '--content-color': colors?.content?.fontColor,\n '--text-color': colors?.content?.fontColor,\n '--font-family': colors?.content?.fontFamily,\n }\n console.log(\"style: \" + JSON.stringify(style))\n onSuccess(theme, style)\n })\n }\n\n public loadThemeOptions(onSuccess: (themeOptions) => void) {\n this.cacheThemeOptions.getAsync(onSuccess)\n }\n\n public loadPresets(onSuccess: (preset) => void) {\n this.cachePresets.getAsync(onSuccess)\n }\n\n public adminLoadNamesFromRoles(roles: number[], onSuccess: (names: Map) => void) {\n getHttpJson(EliteConfig.profileRoleAdminUrl + \"/names/\" + encodeURIComponent(roles.join(\",\")), names => {\n names = new Map(Object.entries(names.result))\n\n for (let name of names.keys()) {\n let roleId = parseInt(name, 10)\n this.mapNames.set(roleId, names.get(name))\n names.set(roleId, names.get(name))\n }\n\n onSuccess(names)\n }, showError)\n }\n\n public getMyProfileRoleUnsafe() {\n return this.cacheMyLastRole.getUnsafe()\n }\n\n public getMyRoleIdUnsafe() {\n let localProfileRole = this.cacheMyLastRole.getUnsafe()\n\n return localProfileRole ? localProfileRole.publicRole.roleId : -1;\n }\n}\n\nexport class ContactsStore {\n private static instance: ContactsStore = new ContactsStore();\n private cacheContactsLight: CacheOneValue;\n private cacheContactsFull: CacheOneValue;\n\n constructor() {\n this.cacheContactsLight = new CacheOneValue((onSuccess: (result: any) => void) => {\n getHttp(EliteConfig.contactsUrl + \"/contacts/light\", (text, _status) => {\n onSuccess(JSON.parse(text).result)\n }, showError, \"ContactsStore:cacheContactsLight\")\n }, 300000, \"cacheContactsLight\")\n\n this.cacheContactsFull = new CacheOneValue((onSuccess: (result: any) => void) => {\n getHttp(EliteConfig.contactsUrl + \"/contacts/full\", (text, _status) => {\n onSuccess(JSON.parse(text).result)\n }, showError, \"ContactsStore:cacheContactsFull\")\n }, 300000, \"cacheContactsFull\")\n }\n\n public static getInstance(): ContactsStore {\n return ContactsStore.instance;\n }\n\n public loadContactsLight(onSuccess: (contacts) => void) {\n this.cacheContactsLight.getAsync(onSuccess)\n }\n\n public loadContactsFull(onSuccess: (contacts) => void) {\n this.cacheContactsFull.getAsync(onSuccess)\n }\n}\n\nexport enum JwtEvent {\n LOGIN, LOGOUT\n}\n\nexport class JwtProducer {\n private readonly accessToken: string;\n // @ts-ignore\n private readonly accessTokenExtracted: any;\n // @ts-ignore\n private readonly refreshToken: string;\n private readonly refreshTokenExtracted: any;\n // @ts-ignore\n private readonly refreshUrl: string\n // @ts-ignore\n private readonly profile: string\n private tokenRenewalInProgress: Promise | null = null;\n private readonly reason: string\n\n public constructor(jwtAccessToken: string, jwtRefreshToken: string, jwtRefreshUrl: string, profile: string, reason: string) {\n this.accessToken = jwtAccessToken\n this.accessTokenExtracted = jwtAccessToken ? extractJwtUnsafe(jwtAccessToken) : null\n this.refreshToken = removeBearerFrom(jwtRefreshToken)\n this.refreshTokenExtracted = jwtRefreshToken ? extractJwtUnsafe(jwtRefreshToken) : null\n this.refreshUrl = jwtRefreshUrl\n this.profile = profile\n this.reason = reason\n\n //console.log(\"accessToken: \" + this.accessToken)\n //console.log(\"accessTokenExtracted: \" + JSON.stringify(this.accessTokenExtracted))\n }\n\n public getRefreshToken() {\n return this.refreshToken\n }\n\n public getAccessToken() {\n return this.accessToken\n }\n\n public getProfile(): string {\n return this.profile;\n }\n\n public getReason(): string {\n return this.reason\n }\n\n public extract() {\n //alert(\"Serialize url: \" + this.refreshUrl)\n return [this.accessToken, this.refreshToken, this.refreshUrl]\n }\n\n public getRefreshUrl() {\n let urlGProfile = stripFinalUrlPart(EliteConfig.urlGProfile);\n\n return urlGProfile + this.refreshUrl\n }\n\n /*public serialize(): string {\n //alert(\"Serialize url: \" + this.refreshUrl)\n return this.accessToken + \"|\" + this.refreshToken + \"|\" + this.refreshUrl\n }*/\n\n /*public static deserialize(str: string): JwtProducer {\n let parts = str.split(\"|\")\n\n if (parts.length != 3)\n return null\n\n //alert(\"DeSerialize url: \" + parts[2])\n return new JwtProducer(parts[0], parts[1], parts[2])\n }*/\n\n public withAccessToken(jwtAccessToken: string): JwtProducer {\n return new JwtProducer(jwtAccessToken, this.refreshToken, this.refreshUrl, this.profile, \"withAccessToken\");\n }\n\n public get(): string {\n return this.accessToken;\n }\n\n public asyncGet(reason: string, onSuccess: (jwt: string, reason: string) => void, onError: (error, status) => void, retrieveRole = true): void {\n if (this.accessTokenExtracted.isssedMs < this.refreshTokenExtracted.isssedMs) {\n // This should not happened, but I saw it happening several times\n AuthStore.log(\"AccessToken emitted before RefreshToken by \" + (this.refreshTokenExtracted.isssedMs - this.accessTokenExtracted.isssedMs) + \" ms\")\n AuthStore.getInstance().logOut()\n onError(\"Login issue, please login again\", 403)\n return\n }\n let now = new Date().getTime();\n let secAccessLeft = this.accessTokenExtracted.exp - now / 1000\n let secAccessDuration = this.accessTokenExtracted.exp - this.accessTokenExtracted.issuedMs / 1000\n\n //console.assert(secAccessDuration > 0, \"accessTokenExtracted: \" + this.accessTokenExtracted.exp + \" - issuedAt: \" + (this.accessTokenExtracted.issuedMs / 1000))\n console.assert(secAccessDuration > 0, JSON.stringify(this.accessTokenExtracted))\n AuthStore.log(\"asyncGet() - secAccessLeft: \" + secAccessLeft + \" - secAccessDuration:\" + secAccessDuration)\n console.log(new Date() + \" - asyncGet() - secAccessLeft: \" + secAccessLeft + \" - secAccessDuration:\" + secAccessDuration)\n\n if (secAccessLeft < secAccessDuration / 2) {\n let jwt: Promise = this.renewAccessToken(\"asyncGet() - \" + reason + \" retrieve role: \" + retrieveRole)\n\n jwt.then(newGlobalProfile => {\n {\n //console.log(\"JWT before renewal: \" + JSON.stringify(this.accessToken))\n //console.log(\"JWT after renewal: \" + JSON.stringify(result))\n\n // If we retrieve the role, this will be overridden\n AuthStore.getInstance().setJwtAccess(AuthStore.getInstance().email, new JwtProducer(newGlobalProfile.jwtAccess, newGlobalProfile.jwtRefresh, newGlobalProfile.jwtRefreshUrl, null, \"asyncGet\"));\n\n if (retrieveRole) {\n UsersProfileStore.getInstance().loadMyLastProfileRole(localProfileRole => {\n //let jwtProvider = new JwtProducer(globalProfile.jwtAccess, globalProfile.jwtRefresh, globalProfile.jwtRefreshUrl, globalProfile.profile, \"asyncGet\")\n //console.log(\"JWT asyncGet() received role: \" + JSON.stringify(role))\n\n if (localProfileRole) {\n AuthStore.getInstance().refreshJwtAccessOnly(localProfileRole.jwtRole)\n AuthStore.getInstance().setLicenseParams(localProfileRole.effectiveParams)\n }\n\n let jwtAccess = localProfileRole?.jwtRole ? localProfileRole?.jwtRole : newGlobalProfile.jwtAccess\n //AuthStore.getInstance().setJwtAccess(AuthStore.getInstance().email, new JwtProducer(jwtAccess, globalProfile.jwtRefresh, globalProfile.jwtRefreshUrl, globalProfile.profile, \"asyncGet\"))\n\n onSuccess(jwtAccess, localProfileRole?.jwtRole? \"JWT Role\" : \"JWT Role null, using JWT Access\")\n }, onError, true)\n } else\n onSuccess(newGlobalProfile.jwtAccess, \"JWT Access\")\n }\n }, err => {\n if (onError)\n onError(err.userMessage, err.status)\n }\n )\n } else {\n return onSuccess(this.accessToken, \"NotExpired\");\n }\n }\n\n // TODO: is it ok that it is not used?\n /*public checkExpiration() {\n let now = new Date().getTime();\n let secAccessLeft = this.accessTokenExtracted.exp - now / 1000\n let secAccessDuration = this.accessTokenExtracted.exp - this.accessTokenExtracted.issuedMs / 1000\n\n console.assert(secAccessDuration > 0)\n\n if (secAccessLeft < secAccessDuration / 2) {\n let jwt: Promise = this.renewAccessToken(\"checkExpiration()\")\n\n jwt.then(text => alert(\"JWT after renewal: \" + text))\n\n return true\n }\n\n return true;\n }*/\n\n private renewAccessToken(reason): Promise {\n let now = new Date().getTime();\n let refreshTokenExtracted = extractJwtUnsafe(this.refreshToken)\n let secRefreshLeft = refreshTokenExtracted.exp - now / 1000\n\n if (secRefreshLeft <= 0 ) {\n AuthStore.getInstance().logOut()\n let msg = \"Please login again, we could not refresh your credentials as they are too old.\"\n alert(msg)\n return Promise.reject(msg)\n }\n\n // FIXME: retrieve, somehow\n AuthStore.log(\"renewAccessToken(): \" + reason)\n let urlGProfile = stripFinalUrlPart(EliteConfig.urlGProfile);\n // e.g. /profile/renew/1/ACCESS\n let urlString = urlGProfile + this.refreshUrl + \"/\" + EliteConfig.NETWORK_ID + \"/ACCESS\"\n\n try {\n let url = new URL(urlString)\n console.log(\"Fetch renewAccessToken(): \" + reason)\n //alert(\"Renew Access token - JWT Provider: \" + JSON.stringify(this))\n\n return this.fetchWithMutex(url);\n } catch (e) {\n console.log(\"renewAccessToken() - error: \" + e)\n return Promise.reject(\"Error \" + e)\n }\n }\n\n // Use a mutex or lock to avoid parallel requests\n private fetchWithMutex(url: URL) {\n if (this.tokenRenewalInProgress) {\n return this.tokenRenewalInProgress;\n }\n\n // Store the renewal process to prevent duplicate calls\n this.tokenRenewalInProgress = fetchAndReturnCors(url, true, \"GET\", this.refreshToken, this.accessToken)\n .then(res => {\n this.tokenRenewalInProgress = null; // Clear the flag once done\n return res.result;\n })\n .catch(err => {\n this.tokenRenewalInProgress = null; // Clear the flag in case of an error\n throw err;\n });\n\n return this.tokenRenewalInProgress;\n }\n}\n\nfunction stripFinalUrlPart(url: string): string {\n let idx = url.lastIndexOf('/')\n\n return url.substring(0, idx)\n}\n\n\nexport function format(first: string, middle: string, last: string): string {\n return (\n (first || '') +\n (middle ? ` ${middle}` : '') +\n (last ? ` ${last}` : '')\n );\n}\n\nexport function downloadHttp(url: string, onSuccess: (blob, status) => void, onError: (text, status) => void, body: string = null, traceId = null) {\n return http('GET', url, body, onSuccess, onError ? onError : function (text, status) {\n alert(\"Error: \" + status + \" - \" + text)\n }, getAccessJwtProvider(), traceId, true)\n}\n\n\nexport function downloadHttpAndOpenOnNewWindow(url: string, traceId = null) {\n downloadHttp(url, (blob, _status) => {\n let _url = URL.createObjectURL(blob);\n window.open(_url, \"_blank\")\n }, showError, traceId)\n}\n\n\nexport function getHttp(url: string, onSuccess: (text, status) => void, onError: (text, status) => void, traceId: string = null, customJwtProvider: JwtProducer = null) {\n return http('GET', url, null, onSuccess, onError ? onError : function (text, status) {\n alert(\"Error: \" + status + \" - \" + text)\n }, customJwtProvider ? customJwtProvider : getAccessJwtProvider(), traceId)\n}\n\nexport function getHttpJson(url: string, onSuccess: (obj, status) => void, onError: (text, status) => void, traceId = null) {\n return http('GET', url, null,\n (text, status) => {\n if (onSuccess) onSuccess(JSON.parse(text), status)\n }, onError ? onError : function (text, status) {\n alert(\"Error: \" + status + \" - \" + text)\n }, getAccessJwtProvider(), traceId)\n}\n\nexport function getHttpJsonChunked(url: string, onSuccess: (obj, status) => void, onError: (text, status) => void) {\n fetchAndReturnCorsChunked(url, \"GET\", (obj, status) => {\n try {\n let text = JSON.parse(obj)\n if (onSuccess)\n onSuccess(text, status)\n } catch(e) {\n alert(obj)\n console.log(\"Exception on getHttpJsonChunked() - a: \" + e + \"\\n reason: \" + obj.substring(0, 10) + \" ==> \" + obj.substring(obj.length - 10))\n console.log(\"Exception on getHttpJsonChunked() - b: \" + obj.length + \": \" + obj.charAt(obj.length-2) + \" - \"+ obj.charAt(obj.length-1))\n }\n } , (text, status) => {\n console.log(\"Network error on : getHttpJsonChunked(): \" + text + \" - \" + status)\n if (onError)\n onError(text, status)\n }, getAccessJwtProvider())\n}\n\nexport function getHttpChunked(url: string, onSuccess: (obj, status) => void, onError: (text, status) => void) {\n fetchAndReturnCorsChunked2(url, \"GET\", onSuccess, onError, getAccessJwtProvider())\n}\n\nexport function getHttpBody(url: string, onSuccess: (text, status) => void, onError: (text, status) => void, body = null, traceId = null) {\n return http('GET', url, body, onSuccess, onError ? onError : function (text, status) {\n alert(\"Error: \" + status + \" - \" + text)\n }, getAccessJwtProvider(), traceId)\n}\n\nexport function getHttpBodyJson(url: string, onSuccess: (text, status) => void, onError: (text, status) => void, body = null, traceId = null) {\n return getHttpBody(url, (text, status) => {\n if (onSuccess) onSuccess(JSON.parse(text), status)\n }, onError, JSON.stringify(body), traceId)\n}\n\nexport function putHttp(url: string, onSuccess: (text, status) => void, onError: (text, status) => void, body = null, traceId = null) {\n return http('PUT', url, body, onSuccess, onError ? onError : function (text, status) {\n alert(\"Error: \" + status + \" - \" + text)\n }, getAccessJwtProvider(), traceId)\n}\n\nexport function putHttpJson(url: string, onSuccess: (obj, status) => void, onError: (text, status) => void, body = null, traceId = null) {\n return http('PUT', url, body, (text, status) => {\n if (onSuccess) onSuccess(JSON.parse(text), status)\n }, onError ? onError : function (text, status) {\n alert(\"Error: \" + status + \" - \" + text)\n }, getAccessJwtProvider(), traceId)\n}\n\nexport function postHttp(url: string, onSuccess: (text, status) => void, onError: (text, status) => void, body = null, _traceId = null, binaryDownload = false) {\n let jwtProvider = getAccessJwtProvider()\n return http('POST', url, body, onSuccess, onError ? onError : function (text, status) {\n alert(\"Error: \" + status + \" - \" + text)\n }, jwtProvider, /*traceId +*/ jwtProvider ? \" provider non null \" : \" provider null \", binaryDownload)\n}\n\nexport function postHttpJson(url: string, onSuccess: (obj, status) => void, onError: (text, status) => void, body = null, _traceId = null) {\n let jwtProvider = getAccessJwtProvider()\n return http('POST', url, body, (text, status) => {\n if (onSuccess) onSuccess(JSON.parse(text), status)\n }, onError ? onError : function (text, status) {\n alert(\"Error: \" + status + \" - \" + text)\n }, jwtProvider, /*traceId +*/ jwtProvider ? \" provider non null \" : \" provider null \")\n}\n\nexport function deleteHttp(url: string, onSuccess: (text, status) => void, onError: (text, status) => void, body = null, traceId = null) {\n return http('DELETE', url, body, onSuccess, onError ? onError : function (text, status) {\n alert(\"Error: \" + status + \" - \" + text)\n }, getAccessJwtProvider(), traceId)\n}\n\nexport function deleteHttpJson(url: string, onSuccess: (text, status) => void, onError: (text, status) => void, body = null, traceId = null) {\n return http('DELETE', url, body, (text, status) => {\n if (onSuccess) onSuccess(JSON.parse(text), status)\n }, onError ? onError : function (text, status) {\n alert(\"Error: \" + status + \" - \" + text)\n }, getAccessJwtProvider(), traceId)\n}\n\nfunction isError(xhr: XMLHttpRequest, binaryDownload: boolean) {\n var res = binaryDownload ? xhr.response : xhr.responseText\n\n if (binaryDownload)\n return xhr.status >= 400\n\n if (res.startsWith(\"{\") && res.endsWith(\"}\")) {\n try {\n let errObj = JSON.parse(res)\n\n if (errObj && (errObj.status == \"ERROR\" || errObj.status == \"STOP\" || errObj.status == \"MISSING\"))\n return true\n } catch (e) {\n\n }\n }\n\n return false\n}\n\nfunction http(method: string, url: string, body: string, onSuccessHandler: (response, status) => void, onErrorHandler: (response, status) => void, jwtProducer: JwtProducer, traceId: string, binaryDownload: boolean = false) {\n function httpLoadWithXhr(jwtString: string) {\n let xhr = new XMLHttpRequest();\n\n xhr.open(method, url);\n\n if (binaryDownload)\n xhr.responseType = 'blob';\n\n if (jwtString) {\n //console.log(\"XHR loading \" + method + \" with JWT \" + (traceId || \"\") + \": \" + url);\n xhr.setRequestHeader(\"Authorization\", addBearerTo(jwtString));\n } else {\n //console.log(\"XHR loading \" + method + \" without JWT\" + (traceId || \"\") + \": \" + url);\n }\n\n if (traceId) {\n console.log(\"Calling GET on \" + url + \":\" + traceId);\n xhr.setRequestHeader(\"E-TraceId\", traceId);\n }\n\n try {\n // Track the state changes of the request.\n xhr.onreadystatechange = function () {\n let DONE = 4; // readyState 4 means the request is done.\n let OK = 200; // status 200 is a successful return.\n\n if (xhr.readyState === DONE) {\n if (xhr.status === OK) {\n if (isError(xhr, binaryDownload)) {\n console.log(\"utils.http() - error: \" + xhr.status + (binaryDownload ? \"\" : \" - \" + xhr.responseText) + \" - url: \" + url)\n if (onErrorHandler) {\n onErrorHandler(binaryDownload ? \"Http error: \" + xhr.status : xhr.responseText, xhr.status)\n }\n } else {\n //console.log(\"ok: \" + xhr.responseText + \" - traceId:\" + traceId)\n\n if (onSuccessHandler) {\n onSuccessHandler(binaryDownload ? xhr.response : xhr.responseText, xhr.status)\n }\n }\n } else {\n console.log(\"utils.http() - error: \" + xhr.status + (binaryDownload ? \"\" : \" - \" + xhr.responseText))\n if (onErrorHandler) {\n onErrorHandler(binaryDownload ? \"Http error: \" + xhr.status : xhr.responseText, xhr.status)\n }\n }\n }\n };\n\n xhr.onerror = function (_e) {\n if (onErrorHandler)\n onErrorHandler(\"Network error while loading \" + url , -1)\n else\n alert(\"Network error loading \" + url);\n };\n\n if (method == \"GET\" && body) {\n xhr.setRequestHeader(\"X-Request-Body\", body);\n xhr.send(null);\n } else {\n if ((method == \"PUT\" || method == \"POST\" || method == \"DELETE\") && (typeof (body) === 'object')) {\n xhr.setRequestHeader(\"Content-Type\", \"application/json\");\n xhr.send(JSON.stringify(body));\n } else\n xhr.send(body);\n }\n } catch (e) {\n alert(\"Exception \" + e)\n }\n }\n\n if (jwtProducer) {\n jwtProducer.asyncGet(\"http() - \" + url, (jwtString, _reason) => {\n httpLoadWithXhr(jwtString);\n //}, onError)\n }, _reason => {\n httpLoadWithXhr(null)\n })\n } else {\n httpLoadWithXhr(null);\n }\n}\n\nexport function extractJwtUnsafe(jwt: string) {\n if (!jwt)\n return null\n\n let parts = jwt.split(\".\");\n\n return JSON.parse(atob(parts[1]))\n}\n\n// Error handling\nexport function handleError(error) {\n let errorMessage;\n\n if (error.error instanceof ErrorEvent) {\n // Get client-side error\n errorMessage = error.error.message;\n } else {\n // Get server-side error\n errorMessage = `Error Code: ${error.status}\\nMessage: ${error.message}`;\n }\n window.alert(errorMessage);\n //return throwError(errorMessage);\n}\n\nexport function returnJsonPromise(log: boolean) {\n return (response) => {\n let dataPromise = response.json();\n\n if (log)\n console.log(dataPromise);\n\n return dataPromise.then((data) => {\n if (data.ok === false) {\n return Promise.reject(data);\n }\n\n return Promise.resolve(data);\n }\n )\n };\n}\n\nexport function checkNetwork(networkId: number) {\n if (!networkId || isNaN(networkId) || networkId < 0)\n throw \"Invalid network id: \" + networkId;\n\n return networkId;\n}\n\nexport function fetchAndReturnCors(url: URL, log: boolean, method: string, jwt: string = null, jwtAccessExpired: string = null): Promise {\n let init: RequestInit = method === \"GET\" ? {method: 'GET', mode: 'cors'} :\n {method: method, mode: 'cors', cache: 'no-cache'};\n\n if (jwt) {\n //console.log(\"Fetching \" + method + \" with JWT: \" + url);\n if (jwtAccessExpired)\n init.headers = {\n \"Authorization\": addBearerTo(jwt),\n \"Elite-Jwt-Access\": jwtAccessExpired\n };\n else\n init.headers = {\"Authorization\": addBearerTo(jwt)};\n } //else\n //console.log(\"Fetching \" + method + \" without JWT: \" + url);\n\n return fetch('' + url, init)\n .then(returnJsonPromise(log), err => {\n if (log)\n console.log(err);\n });\n}\n\nexport function fetchAndReturnCorsChunked2(urlString: string, method: string, dataListener: (obj, status) => void, onError: (text, status) => void, jwtProducer: JwtProducer = null) {\n async function httpLoadWithFetch(jwtString: string) {\n let init = prepareFetchInit(method, jwtString);\n\n fetch(urlString, init)\n .then((response) => {\n if (!response.ok) {\n if (onError)\n onError(response.text(), response.status)\n return\n }\n\n const reader = response.body.getReader();\n const decoder = new TextDecoder('utf-8');\n\n return new ReadableStream({\n start(controller) {\n function push() {\n reader.read().then(({ done, value}) => {\n if (done) {\n controller.close();\n return;\n }\n // Get the data and send it to the browser via the controller\n controller.enqueue(value);\n let text = decoder.decode(value, { stream: true })\n dataListener(text, response.status)\n\n push();\n });\n }\n\n push();\n },\n });\n })\n .then((stream) =>\n // Respond with our stream\n new Response(stream, {headers: {\"Content-Type\": \"text/html\"}}).text(),\n )\n .then((result) => {\n // Do things with result\n console.log(result);\n });\n }\n\n if (jwtProducer) {\n jwtProducer.asyncGet(\"http() - \" + urlString, (jwtString, _reason) => {\n httpLoadWithFetch(jwtString);\n }, _reason => {\n httpLoadWithFetch(null)\n })\n } else {\n httpLoadWithFetch(null);\n }\n}\n\n\nfunction prepareFetchInit(method: string, jwtString: string) {\n let init: RequestInit = method === \"GET\" ? {\n method: 'GET',\n mode: 'cors'\n } :\n {\n method: method,\n mode: 'cors',\n cache: 'no-cache'\n };\n\n if (jwtString) {\n init.headers = {\"Authorization\": addBearerTo(jwtString)};\n }\n return init;\n}\n\nexport async function fetchAndReturnCorsChunked(urlString: string, method: string, dataListener: (obj, status) => void, onError: (text, status) => void, jwtProducer: JwtProducer = null): Promise {\n async function httpLoadWithFetch(jwtString: string) {\n let init = prepareFetchInit(method, jwtString);\n let chunkTerminator = \"_||_\"\n\n try {\n const response = await fetch('' + new URL(urlString), init);\n\n if (!response.ok) {\n if (onError)\n onError(response.text(), response.status)\n return\n }\n\n const reader = response.body.getReader();\n const decoder = new TextDecoder('utf-8');\n\n let completeChunk = '';\n\n while (true) {\n const {\n value,\n done\n } = await reader.read();\n let chunk = decoder.decode(value, {stream: true})\n if (done) {\n //alert(\"Chunks done\")\n break;\n }\n\n completeChunk += chunk;\n\n // Process the chunk only if it ends with a newline or other delimiter\n if (completeChunk.endsWith(chunkTerminator)) {\n let newChunk = completeChunk.substring(0, completeChunk.length - chunkTerminator.length)\n let idx = newChunk.lastIndexOf(chunkTerminator) // SOmetimes we can get both the versions in the same read\n if (idx >= 0)\n newChunk = newChunk.substring(idx + chunkTerminator.length)\n\n dataListener(newChunk, response.status)\n completeChunk = ''; // Reset for the next chunk\n }\n }\n } catch(e) {\n if (onError)\n onError(e, 0)\n }\n }\n\n if (jwtProducer) {\n jwtProducer.asyncGet(\"http() - \" + urlString, (jwtString, _reason) => {\n httpLoadWithFetch(jwtString);\n }, _reason => {\n httpLoadWithFetch(null)\n })\n } else {\n httpLoadWithFetch(null);\n }\n}\n\nexport function cleanId(id: string, role: number) {\n let prefix = role + \"}\";\n\n return id.startsWith(prefix) ? id.substring(prefix.length) : id\n}\n\nexport class NanoUtil {\n static mouseOnArea: boolean = false\n}\n\nexport function sendMouseMoveToNanoGallery() {\n let nextImgArrow = document.querySelector(\".selImgNext\");\n\n if (nextImgArrow) {\n return nextImgArrow.dispatchEvent(new Event('mousemove'));\n }\n\n return false;\n}\n\nexport function cleanUpNanoGallery(galleryTarget) {\n if (galleryTarget) {\n try {\n // @ts-ignore\n $nanogallery2(galleryTarget, 'destroy')\n } catch (e) {\n\n }\n }\n}\n\nexport function defaultTargetNanoGallery() {\n return document.getElementById(\"contentTarget\")\n}\n\n/*export function itemsSelectedNanoGallery(galleryTarget) {\n // @ts-ignore\n //alert(jQuery.nanogallery2)\n if (galleryTarget) {\n console.log(\"Methods of nanoGallery target: \" + getMethods(galleryTarget))\n\n try {\n // @ts-ignore\n return $nanogallery2(galleryTarget, 'itemsSelectedGet')\n } catch (e) {\n }\n }\n}*/\n\nexport function getMethods(obj) {\n let properties = new Set()\n let currentObj = obj\n do {\n Object.getOwnPropertyNames(currentObj).map(item => properties.add(item))\n } while ((currentObj = Object.getPrototypeOf(currentObj)))\n\n // @ts-ignore\n return [...properties.keys()].filter(item => typeof obj[item] === 'function')\n}\n\nexport function getAttributes(obj) {\n return obj ? Object.entries(obj) : []\n /*for (const [key, value] of Object.entries(obj)) {\n alert(key + \": \" + JSON.stringify(value))\n }*/\n}\n\nexport function copyAttributes(source, target) {\n for (const [key, value] of Object.entries(source)) {\n target[key] = value\n }\n\n return target\n}\n\n\nexport function mergeJson(obj1: any, obj2: any) {\n const result = {};\n\n Object.keys(obj1)\n .forEach(key => result[key] = obj1[key]);\n\n Object.keys(obj2)\n .forEach(key => result[key] = obj2[key]);\n\n return result;\n}\n\n// Not used\n// From https://spin.atomicobject.com/2018/09/10/javascript-concurrency/\nexport const notConcurrent = (proc: () => PromiseLike) => {\n let inFlight: Promise | false = false;\n\n return () => {\n if (!inFlight) {\n inFlight = (async () => {\n try {\n return await proc();\n } finally {\n inFlight = false;\n }\n })();\n }\n return inFlight;\n };\n};\n\nexport function addBearerTo(accessJwtToken: string): string {\n let idx = accessJwtToken.lastIndexOf(\"Bearer \");\n\n if (idx < 0)\n return \"Bearer \" + accessJwtToken\n\n return accessJwtToken.substring(idx)\n}\n\nexport function removeBearerFrom(jwtToken: string): string {\n if (jwtToken == null)\n return null\n\n let idx = jwtToken.lastIndexOf(\"Bearer \");\n\n if (idx < 0)\n return jwtToken\n\n return jwtToken.substring(idx + 7)\n}\n\nexport function cleanContent(cleanContentTarget = true, cleanExternalRender = true, hideButtons = true) {\n let contentTarget = document.getElementById(\"contentTarget\")\n\n if (cleanContentTarget && contentTarget) {\n contentTarget.style.display = 'none';\n }\n\n if (cleanExternalRender) {\n let elExternal: any = document.getElementById(\"externalRender\");\n\n (elExternal as ExternalRender).externalContent = \"\";\n }\n\n if (hideButtons) {\n hideCloseButton();\n hideDeleteButton();\n hideAddButton();\n hideTitleContent();\n }\n\n return contentTarget\n}\n\nfunction hideElement(id: string) {\n let element = document.getElementById(id)\n\n if (element) {\n element = recreateNode(element);\n element.style.display = 'none';\n element.addEventListener(\"click\", () => {\n })\n }\n\n return element\n}\n\n// Clone a node, removing all the listeners\nexport function recreateNode(el, recreateChildren = false) {\n let newButton\n\n if (recreateChildren) {\n newButton = el.cloneNode(true)\n el.parentNode.replaceChild(newButton, el);\n } else {\n newButton = el.cloneNode(false);\n\n while (el.hasChildNodes()) newButton.appendChild(el.firstChild);\n el.parentNode.replaceChild(newButton, el);\n }\n\n return newButton\n}\n\nexport function showElement(id: string, onCLick = null): HTMLElement {\n let element = document.getElementById(id)\n\n if (element) {\n element = recreateNode(element);\n element.style.display = 'inline';\n\n if (onCLick)\n element.addEventListener(\"click\", onCLick)\n }\n\n return element\n}\n\nexport function hideCloseButton() {\n hideElement(\"closeContent\")\n}\n\nexport function showCloseButton(onClick) {\n showElement(\"closeContent\", onClick)\n}\n\nexport function hideDeleteButton() {\n hideElement(\"deleteContent\")\n}\n\nexport function showDeleteButton(onClick) {\n showElement(\"deleteContent\", onClick)\n}\n\nexport function hideAddButton() {\n hideElement(\"addContent\")\n}\n\nexport function showAddButton(onClick) {\n showElement(\"addContent\", onClick)\n}\n\nexport function hideTitleContent() {\n hideElement(\"titleContent\")\n}\n\nexport function showTitleContent(content) {\n let elExternal: any = showElement(\"titleContent\");\n\n (elExternal as ExternalRender).externalContent = content;\n}\n\nexport function stringifyCircular(item) {\n let cache = []\n\n return JSON.stringify(item, (_key, value) => {\n if (typeof value === 'object' && value !== null) {\n // Duplicate reference found, discard key\n if (cache.includes(value)) return;\n\n // Store value in our collection\n cache.push(value);\n }\n return value;\n });\n}\n\nexport function getThresholdColor(value: number) {\n if (value > 0.90)\n return \"red\"\n\n if (value > 0.75)\n return \"orange\"\n\n return \"green\"\n}\n\nexport function firstCharUppercase(str: string) {\n return str.substring(0, 1).toUpperCase() + str.substring(1)\n}\n\n/** Resizes an iframe, to be able to show all the content; it's not super precise, but it should work */\nexport function iframeResize(ifr, securityMargin = 15) {\n let h = ifr.contentWindow.document.body.scrollHeight\n ifr.style.height =\n (h + securityMargin) + 'px';\n\n //alert(ifr.contentWindow.document.body.clientHeight + \" vs \" + h)\n}\n\n// Base64 is not URL friendly, and Spring boot complains about \"/\"; this functions fixes it client side\n// Server side you need to use CryptoHash.decodeUriForBase64() or CryptoHash.decodeUriBase64AsString()\nexport function encodeBase64ForUrl(base64: string) {\n return base64.replace(/\\+/g, \".\").replace(/\\//g, \"_\").replace(/=/g, \"-\");\n}\n\n// Base64 is not URL friendly, and Spring boot complains about \"/\"; this functions fixes it client side\n// Server side you need to use CryptoHash.encodeUriBase64AsString()\nexport function decodeBase64FromUrl(base64: string) {\n return base64.replace(/\\./g, \"+\").replace(/_/g, \"\\/\").replace(/-/g, \"=\");\n}\n\nexport function getUrlParams(): URLSearchParams {\n return new URLSearchParams(window.location.search)\n}\n\nexport function getUrlParam(paramName: string) {\n return getUrlParams().get(paramName)\n}\n\nexport function setUrlParam(paramName: string, paramValue, reload: boolean = false /* if false, the URL will be changed without reload*/) {\n const urlWithoutParams = new URL(window.location.href).pathname;\n const urlParams = new URLSearchParams(window.location.search);\n\n urlParams.set(paramName, paramValue);\n\n const newQueryString = urlParams.toString();\n const newUrl = `${urlWithoutParams}?${newQueryString}`\n\n if (reload)\n window.location.href = newUrl\n else\n window.history.replaceState(null, '', newUrl);\n}\n\nexport function deleteUrlParam(paramName: string, reload: boolean = false /* if false, the URL will be changed without reload*/) {\n const urlWithoutParams = new URL(window.location.href).pathname;\n const urlParams = new URLSearchParams(window.location.search);\n\n urlParams.delete(paramName);\n\n const newQueryString = urlParams.toString();\n const newUrl = `${urlWithoutParams}?${newQueryString}`\n\n if (reload)\n window.location.href = newUrl\n else\n window.history.replaceState(null, '', newUrl);\n}\n\nexport function uploadAllFiles(files: FileList, urlUpload: string, onPreview: (File) => void, onProgress: (filesDone, filesSuccess, filesToDo, result) => void, additionalParams: Map = null) {\n let filesSuccess = 0\n\n let filesDone = 0\n let filesToDo = files.length\n\n for (let i = 0; i < files.length; i++) {\n let file = files[i];\n\n if (onPreview)\n onPreview(file);\n // Can we make it faster, sending all the files, while tracking the progress?\n uploadFile(file, urlUpload, r => {\n let res = JSON.parse(r)\n\n if (res.ok)\n filesSuccess++\n else\n alert(\"Error: \" + res.userMessage)\n filesDone++\n\n if (onProgress)\n onProgress(filesDone, filesSuccess, filesToDo, res)\n }, r => {\n filesDone++\n if (onProgress)\n onProgress(filesDone, filesSuccess, filesToDo, null)\n alert(\"Error: \" + r)\n }, additionalParams);\n }\n}\n\nexport function uploadFile(fileOrBlob, urlUpload: string, onSuccess, onError, additionalParams: Map = null) {\n let formData = new FormData();\n\n formData.append('file', fileOrBlob);\n formData.append('params', AuthStore.getInstance().getLicenseParams());\n\n let n = 0\n if (additionalParams) {\n for (let [key, value] of additionalParams.entries()) {\n console.assert(key != \"file\")\n console.assert(key != \"params\")\n n++\n formData.append(key, isString(value) ? String(value) : JSON.stringify(value));\n }\n\n if (n == 0) {\n for (let key in additionalParams) {\n let value = additionalParams[key]\n console.assert(key != \"file\")\n console.assert(key != \"params\")\n formData.append(key, isString(value) ? String(value) : JSON.stringify(value));\n }\n }\n }\n\n getAccessJwtTokenAsync(bearer => {\n fetch(urlUpload, {\n method: 'POST',\n body: formData,\n headers: {\n \"Authorization\": addBearerTo(bearer)\n }\n })\n .then((r) => {\n if (r.status == 200) {\n if (onSuccess)\n r.text().then(onSuccess)\n } else {\n if (onError)\n r.text().then(onError)\n else\n alert(\"Error \" + r.status + \": \" + r.statusText)\n }\n /* Done. Inform the user */\n })\n .catch((e) => {\n alert(e) /* Error. Inform the user */\n })\n })\n}\n\nexport function resizeImageToCanvas(image: HTMLImageElement, maxSize: number): HTMLCanvasElement {\n const canvas = document.createElement('canvas') as HTMLCanvasElement;\n let width = image.width;\n let height = image.height;\n\n if (width > height) {\n if (width > maxSize) {\n height *= maxSize / width;\n width = maxSize;\n }\n } else {\n if (height > maxSize) {\n width *= maxSize / height;\n height = maxSize;\n }\n }\n\n canvas.width = width;\n canvas.height = height;\n canvas.getContext('2d').drawImage(image, 0, 0, width, height);\n\n return canvas\n}\n\nexport function dataURItoBlob(dataURI: string): Blob {\n const bytes = dataURI.split(',')[0].indexOf('base64') >= 0 ?\n atob(dataURI.split(',')[1]) :\n unescape(dataURI.split(',')[1]);\n const mime = dataURI.split(',')[0].split(':')[1].split(';')[0];\n const max = bytes.length;\n const ia = new Uint8Array(max);\n\n for (var i = 0; i < max; i++)\n ia[i] = bytes.charCodeAt(i);\n\n return new Blob([ia], {type: mime});\n}\n\n\nexport function resizeFileToBlob(file: File, maxSize: number, qualityPercentage: number = 0.8): Promise {\n const reader = new FileReader();\n const image = new Image();\n\n const resizeAfterLoad: () => Blob = () => {\n let canvas = resizeImageToCanvas(image, maxSize)\n let dataUrl = canvas.toDataURL('image/jpeg', qualityPercentage);\n\n return dataURItoBlob(dataUrl);\n };\n\n return new Promise((ok, no) => {\n if (!file.type.match(/image.*/)) {\n no(new Error(\"Not an image\"));\n return;\n }\n\n reader.onload = (readerEvent: any) => {\n image.onload = () => ok(resizeAfterLoad());\n image.src = readerEvent.target.result;\n };\n reader.readAsDataURL(file);\n })\n}\n\nexport function addCssFile(cssFile: string) {\n if (cssFile) {\n let lnk = document.createElement('link');\n\n lnk.href = EliteConfig.cssUrl + \"/\" + cssFile;\n lnk.rel = 'stylesheet';\n lnk.type = 'text/css';\n\n (document.head || document.documentElement).appendChild(lnk);\n }\n}\n\nexport function addJavascriptFile(jsUrl: string, onload: () => void, async = true, defer = false) {\n if (jsUrl) {\n let script = document.createElement('script');\n\n if (onload)\n script.onload = onload\n\n script.async = async\n if (defer)\n script.defer = true\n\n script.src = jsUrl;\n\n document.head.appendChild(script);\n }\n}\n\nexport function createMeta(name: string, content: string) {\n let meta: HTMLMetaElement = document.createElement(\"meta\")\n\n meta.name = name\n meta.content = content\n document.head.appendChild(meta)\n}\n\nexport function defineGlobalFunction(fnName, fn) {\n window[fnName] = fn\n}\n\nexport function addCssInline(css: string) {\n if (css) {\n let style = document.createElement('style');\n\n style.type = 'text/css';\n //@ts-ignore\n if (style.styleSheet) {\n // This is required for IE8 and below.\n //@ts-ignore\n style.styleSheet.cssText = css;\n } else {\n style.appendChild(document.createTextNode(css));\n }\n\n (document.head || document.documentElement).appendChild(style);\n }\n\n\n}\n\nexport function activateTheme(roleId: number, callback: () => void) {\n if (location.href.startsWith(\"http://foqoos.com\") || location.href.startsWith(\"https://foqoos.com\"))\n roleId = -1\n\n let override = getUrlParam(\"withCss\")\n\n if (override)\n roleId = parseInt(override)\n\n UsersProfileStore.getInstance().loadTheme(roleId, (res: any) => {\n let css = res.defaultCss\n\n if (res.colors) {\n css += jsonToCss(JSON.parse(res.colors))\n }\n\n addCssInline(css)\n callback()\n })\n}\n\nexport function extractJson(json: any, level1: string = null, level2: string = null, level3: string = null, level4: string = null, level5: string = null) {\n //@ts-ignore\n //alert(\"Full: \" + level1 + \" - \" + level2 + \" - \" + level3 + \" - \" + JSON.stringify(json))\n\n let jsonL1 = null\n if (level1 && level1.startsWith(\"+\")) {\n level1 = level1.substring(1)\n let j = JSON.parse(json)\n //alert(\"Level 1 - 1a: \" + j)\n //@ts-ignore\n jsonL1 = j[level1]\n //alert(\"Level 1 - 1b: \" + JSON.stringify(jsonL1))\n } else {\n //@ts-ignore\n jsonL1 = json[level1]\n //alert(\"Level 1 - 2: \" + jsonL1)\n }\n\n //@ts-ignore\n if (jsonL1) {\n //@ts-ignore\n if (level2) {\n //@ts-ignore\n return extractJson(jsonL1, level2, level3, level4, level5)\n } else\n //@ts-ignore\n return jsonL1\n }\n\n return json\n}\n\nfunction extractCss(json: any, level1: string, level2: string, prefix: string) {\n //@ts-ignore\n if (json && json[level1] && json[level1][level2]) {\n //@ts-ignore\n return prefix + json[level1][level2] + \";\\n\"\n }\n\n return \"\"\n}\n\nfunction jsonToCss(colors: any) {\n let css = \"\"\n\n css += extractCss(colors, \"menu\", \"backgroundColor\", \"--menu-bk:\")\n css += extractCss(colors, \"menu\", \"fontColor\", \"--menu-color:\")\n css += extractCss(colors, \"content\", \"backgroundColor\", \"--content-bk:\")\n css += extractCss(colors, \"content\", \"fontColor\", \"--content-color:\")\n css += extractCss(colors, \"buttons\", \"radius\", \"--buttons-radius:\")\n\n return \":root {\\n\" + css + \"\\n}\"\n}\n\nexport function toBoolean(str: string) {\n return \"true\" == str.toLowerCase()\n}\n\nexport function selectOptionByContent(selectControl: HTMLSelectElement, expectedValue: string, mapper: (HTMLOptionElement) => string = null) {\n if (selectControl) {\n for (let i = 0; i < selectControl.options.length; i++) {\n let value = mapper ? mapper(selectControl.options[i]) : selectControl.options[i].value\n if (value == expectedValue) {\n selectControl.selectedIndex = i\n selectControl.options[i].selected = true;\n\n return i\n }\n }\n\n return -1;\n }\n return 0;\n}\n\nexport function urlMerge(base: string, other) {\n if (other)\n return base + (base.endsWith(\"/\") ? \"\" : \"/\") + other\n\n if (base.endsWith(\"/\"))\n return base.substring(0, base.length - 1)\n\n return base\n}\n\nexport function result(data): any {\n return JSON.parse(data)?.result\n}\n\n/*export function getMenuCss(backgroundColor: string) {\n return \":root {\\n\" +\n \"--menu-bk: \" + backgroundColor + \"\\n\" +\n \"}\"\n}*/\n\nexport class UserLoginInfo {\n public givenName: string\n public familyName: string\n public fullName: string\n public email: string\n public jwtToken: string\n}\n\nexport function isString(value) {\n return typeof value === 'string' || value instanceof String;\n}\n\nexport enum FuzzyBoolean {\n TRUE, FALSE, MAYBE\n}\n\nexport namespace FuzzyBoolean {\n export function fromPositiveNumber(value: number): FuzzyBoolean {\n if (value < 0 || value == undefined || value == null)\n return FuzzyBoolean.MAYBE\n\n if (value > 0)\n return FuzzyBoolean.TRUE\n\n return FuzzyBoolean.FALSE\n }\n}\n\nexport class CarouselCard {\n public title?: string\n public text?: string\n public html?: string\n public imageUrl?: string\n public enabled?: FuzzyBoolean\n}\n\nexport interface CardsWithPos {\n cards: CarouselCard[] | string\n currentCard: number\n showButtonsAbove: boolean\n\n renderButtons(): any\n}\n\nexport class CardsWithPosUtil {\n public static chooseCurrentCard(obj: CardsWithPos): CarouselCard[] {\n let cards = CardsWithPosUtil.getCards(obj)\n\n if (obj.cards && obj.currentCard < 0) {\n for (let i = 0; i < cards.length; i++) {\n if (cards[i].enabled === FuzzyBoolean.MAYBE) {\n obj.currentCard = -1\n\n return cards\n }\n\n if (cards[i].enabled !== FuzzyBoolean.FALSE) {\n obj.currentCard = i\n\n return cards\n }\n }\n\n obj.currentCard = 0;\n }\n\n return cards\n }\n\n public static getCards(obj: CardsWithPos): CarouselCard[] {\n return typeof obj.cards == 'string' ? JSON.parse(obj.cards) : obj.cards\n }\n\n public static renderCustom(obj: CardsWithPos, renderFn: (card: CarouselCard, pos: number) => any): any {\n if (!obj.cards)\n return \"\"\n\n let cards = CardsWithPosUtil.getCards(obj)\n let ar = []\n\n for (let i = 0; i < cards.length; i++) {\n ar.push(renderFn(cards[i], i))\n }\n\n return ar\n }\n}\n\nexport function px(value) {\n if (!value)\n return null\n\n if (typeof value == 'string' && value.endsWith(\"%\"))\n return value\n\n return value + \"px\"\n}\n\n\nexport class UriBuilder {\n private uri: string\n\n constructor(url: string) {\n this.uri = url.includes('?') ? url : url + \"?\";\n }\n\n with(paramName: string, value: string): UriBuilder {\n if (paramName) {\n this.uri += this.uri.endsWith(\"?\") ? \"\" : \"&\"\n\n this.uri += paramName + \"=\"\n this.uri += encodeURIComponent(value)\n }\n\n return this\n }\n\n build(): string {\n return this.uri\n }\n}\n\nexport function addUrlParam(url: string, paramName: string, paramValue: string): string {\n return new UriBuilder(url).with(paramName, paramValue).build()\n}\n\nexport function formFieldByName(form: HTMLFormElement, fieldName: string) {\n for (let f of Array.from(form.elements)) {\n if (f.getAttribute(\"name\") == fieldName)\n return f\n }\n\n return null\n}\n\nexport function formFieldById(form: HTMLFormElement, fieldId: string) {\n for (let f of Array.from(form.elements)) {\n if (f.id == fieldId)\n return f\n }\n\n return null\n}\n\nexport class Debug {\n public static active: boolean = false\n\n public static trace(str: string): void {\n AuthStore.log(str)\n if (Debug.active)\n console.log(str)\n }\n}\n\nexport function deepUpdateMap(map: Map, key: string, value: any): Map {\n let newMap = new Map();\n\n for (let i in map)\n newMap[i] = map[i];\n\n newMap[key] = value\n\n return newMap\n}\n\nexport function isUserAdmin(role): boolean {\n // FIXME: A creator should work as well\n return \"ADMIN\" === role?.group?.toUpperCase() || \"NETWORK-ADMIN\" === role?.group?.toUpperCase()\n}\n\nexport enum SortingState {\n UNSORTED, ASCENDING, DESCENDING\n}\n\nexport class SortingInfo {\n colName: string\n state: SortingState\n\n constructor(colName: string, state: SortingState) {\n this.colName = colName;\n this.state = state;\n }\n}\n\nexport class ElementsGroup {\n readonly items: Type[] = []\n readonly onNotify: ((info: TypeInfo) => void)[] = []\n readonly name: string\n\n constructor(name: string = null) {\n this.name = name;\n }\n\n register(item: Type) {\n this.items.push(item)\n }\n\n addListener(onNotify: (info: TypeInfo) => void) {\n this.onNotify.push(onNotify)\n }\n\n notify(info: TypeInfo) {\n this.onNotify.forEach(notify => notify(info))\n }\n}\n\nexport class Refresher {\n private listeners: ((any) => void)[] = []\n\n\n add(listener: (any) => void) {\n this.listeners.push(listener)\n }\n\n refresh(obj: any = null) {\n for (let l of this.listeners)\n l(obj)\n }\n}\n\nexport interface DataStorage {\n setItem(key: string, value: string): void;\n\n clear(): void;\n\n /** Returns the current value associated with the given key, or null if the given key does not exist. */\n getItem(key: string): string | null;\n\n removeItem(key: string): void;\n}\n\nexport class DataStorages {\n private static ALLOW_DATA_PERSISTENCE: string = \"allowDataPersistence\"\n private static persistData: boolean = false\n private static map: DataStorage = new class implements DataStorage {\n backingMap: Map = new Map()\n\n clear(): void {\n this.backingMap.clear()\n }\n\n getItem(key: string): string {\n return this.backingMap.get(key)\n }\n\n removeItem(key: string): void {\n this.backingMap.delete(key)\n }\n\n setItem(key: string, value: string): void {\n this.backingMap.set(key, value)\n }\n }\n\n // @ts-ignore\n private static _initialize = (() => {\n let allowDataPersistence = DataStorages.persistent().getItem(DataStorages.ALLOW_DATA_PERSISTENCE)\n\n if (allowDataPersistence === \"true\")\n DataStorages.persistData = true\n })();\n\n public static persistent(): DataStorage {\n return localStorage // If it looks like a duck...\n }\n\n public static session(): DataStorage {\n return sessionStorage // If it looks like a duck...\n }\n\n public static inMemory(): DataStorage {\n return DataStorages.map\n }\n\n private static exec(consumer: (ds: DataStorage) => void) {\n consumer(DataStorages.inMemory())\n\n if (DataStorages.persistData)\n consumer(DataStorages.persistent())\n }\n\n public static auto(): DataStorage {\n return new class implements DataStorage {\n clear(): void {\n DataStorages.exec(ds => ds.clear())\n }\n\n getItem(key: string): string | null {\n if (DataStorages.persistData) {\n let value = DataStorages.persistent().getItem(key);\n\n if (value)\n return value\n }\n\n return DataStorages.inMemory().getItem(key)\n }\n\n removeItem(key: string): void {\n DataStorages.exec(ds => ds.removeItem(key))\n }\n\n setItem(key: string, value: string): void {\n DataStorages.exec(ds => ds.setItem(key, value))\n }\n }\n }\n\n public static allowPersistence(allowDataPersistence: boolean, allowConsentPersistence: boolean) {\n DataStorages.persistData = allowDataPersistence\n\n if (!allowDataPersistence)\n DataStorages.persistent().clear()\n\n if (allowDataPersistence || allowConsentPersistence) {\n DataStorages.persistent().setItem(DataStorages.ALLOW_DATA_PERSISTENCE, \"\" + allowDataPersistence)\n }\n }\n\n public static userNotified(): boolean {\n let allowDataPersistence = DataStorages.persistent().getItem(DataStorages.ALLOW_DATA_PERSISTENCE)\n\n return allowDataPersistence != null\n }\n\n public static removeItemsWithPrefix(prefix: string) {\n DataStorages.removeItemsWithPrefixFromStorage(sessionStorage, prefix)\n DataStorages.removeItemsWithPrefixFromStorage(localStorage, prefix)\n }\n\n private static removeItemsWithPrefixFromStorage(storage: Storage, prefix: string) {\n let len = storage.length\n\n for(let i=0; i)[attributeName];\n }\n\n return undefined;\n}\n\nexport function inputValue(event) {\n return (event.target as HTMLInputElement).value\n}\n\nexport function userData() {\n return {\n userAgent: navigator.userAgent,\n language: navigator.language || (navigator as any).userLanguage,\n referrer: document.referrer\n }\n}\n\nexport function isMobile() {\n if (/Mobi|Android/i.test(navigator.userAgent)) {\n return true\n }\n\n if ('ontouchstart' in window || navigator.maxTouchPoints > 0 || ((navigator as any).msMaxTouchPoints && (navigator as any).msMaxTouchPoints > 0)) {\n return true\n }\n}\n\nexport function isScreenVertical() {\n return window.innerHeight > window.innerWidth\n}\n\nexport function isScreenHorizontal() {\n return !isScreenVertical()\n}\n\nexport function cssClass(userClass = null) {\n let before = userClass ? userClass + \" \" : \"\"\n let after = isMobile() ? \" mobile\" : \"\"\n\n if (isScreenVertical())\n return before +\"vertical\" + after\n\n return before + \"horizontal\" + after\n}\n\n// Can be used to store some temporarily values\nexport function setTemporarilyPersistedPageValue(pageName: string, key: string, value: string, timeout: number) {\n let threshold: number = timeout ? Date.now() + timeout : 0\n sessionStorage.setItem(pageName + \"/\" + key, value + \"|\" + threshold)\n}\n\nexport function getTemporarilyPersistedPageValue(pageName: string, key: string, remove: boolean) {\n let value: string = sessionStorage.getItem(pageName + \"/\" + key)\n\n if (!value)\n return null\n\n if (remove)\n sessionStorage.removeItem(pageName + \"/\" + key)\n\n let idx = value.lastIndexOf(\"|\")\n\n if (idx > 0) {\n let threshold: number = parseInt(value.substring(idx + 1))\n\n if (Date.now() > threshold)\n return null;\n\n return value.substring(0, idx)\n }\n\n return value;\n}\n\nexport const enterKeyDownEvent = new KeyboardEvent('keydown', {\n bubbles: true,\n cancelable: true,\n key: 'Enter',\n code: 'Enter',\n});\n\nexport const escapeKeyDownEvent = new KeyboardEvent('keydown', {\n bubbles: true,\n cancelable: true,\n key: 'Escape',\n code: 'Escape',\n});\n\nexport function isPunctuationOrSpace(str: string) {\n return str == \" \" || str == \";\" || str == \":\" || str == \".\" || str == \",\"\n}\n\nexport function userJustSignedUp(removeFromLink = false) : boolean {\n let createOnLink = window.location.href.includes(\"userCreated=true\")\n\n if (createOnLink)\n DataStorages.session().setItem(\"userJustSignedUp\", \"true\")\n\n if (removeFromLink && createOnLink) {\n window.location.href = window.location.href.replace(\"userCreated=true\", \"\")\n }\n\n if (createOnLink)\n return true\n\n return DataStorages.session().getItem(\"userJustSignedUp\") == \"true\";\n}\n\nexport function limitString(str: string, maxLen: number, maxCharactersToDrop = 20): string {\n if (str.length <= maxLen)\n return str\n\n let len = maxLen\n\n while(!isPunctuationOrSpace(str.charAt(len)) && len > maxCharactersToDrop - 20)\n len--\n\n return str.substring(0, len) + \"...\"\n}\n\nexport interface GestureHandlers {\n onLeft: () => void\n onRight: () => void\n onTop: () => void\n onBottom: () => void\n onPinch: (zoom, originX, originY) => void\n}\n\nclass MouseGestureState {\n static lastAction = 0\n}\n\nexport function mouseGesture(element, handlers: GestureHandlers, rateLimitMs=100) {\n let originX = 0\n let originY = 0\n\n element.addEventListener('touchstart', (e) => {\n if (e.touches.length === 2) {\n // Calculate the midpoint of the two fingers\n originX = (e.touches[0].clientX + e.touches[1].clientX) / 2;\n originY = (e.touches[0].clientY + e.touches[1].clientY) / 2;\n }\n });\n\n new AlloyFinger(element, {\n swipe: (evt) => {\n let now = Date.now()\n\n if (now < MouseGestureState.lastAction + rateLimitMs)\n return\n\n //console.log(MouseGestureState.lastAction + \" vs \" + now)\n\n MouseGestureState.lastAction = now\n\n if (evt.direction == 'Left' && handlers.onLeft)\n handlers.onLeft()\n else if (evt.direction == 'Right' && handlers.onRight)\n handlers.onRight()\n else if (evt.direction == 'Up' && handlers.onTop)\n handlers.onTop()\n else if (evt.direction == 'Down' && handlers.onBottom)\n handlers.onBottom()\n },\n pinch: (evt) => {\n let now = Date.now()\n\n //alert(\"Pinch\" + evt.zoom + \" \" + evt.direction + \" \" + evt.scale)\n if (now < MouseGestureState.lastAction + rateLimitMs)\n return\n\n MouseGestureState.lastAction = now\n\n if (handlers.onPinch)\n handlers.onPinch(evt.zoom, originX, originY)\n }\n });\n}\n\nexport function downloadUrl(imageUrl: string, newFileName: string) {\n let init: any = {\n method: 'GET',\n mode: 'cors'\n }\n\n fetch(imageUrl, init)\n .then(response => response.blob()) // Convert the response to a Blob\n .then(blob => {\n // Create a URL for the Blob\n const url = URL.createObjectURL(blob);\n\n // Create a link element for the download\n const link = document.createElement(\"a\");\n link.href = url;\n link.download = newFileName; // Set the suggested filename with the new extension\n\n // Append the link, click it, and remove it from the DOM\n document.body.appendChild(link);\n link.click();\n document.body.removeChild(link);\n\n // Clean up by revoking the Blob URL\n URL.revokeObjectURL(url);\n })\n .catch(error => console.error('Error downloading:', error));\n}\n\nexport function isFullScreen() {\n return document.fullscreenElement != null\n}\n\nexport function enterFullScreen(onError: (err) => void = null) {\n if (!document.fullscreenElement) {\n // If the document is not in fullscreen mode, request fullscreen mode\n document.documentElement.requestFullscreen().catch(err => {\n if (onError)\n onError(err)\n });\n }\n}\n\nexport function exitFullScreen() {\n if (document.fullscreenElement && document.exitFullscreen) {\n document.exitFullscreen()\n }\n}\n\nexport function toggleFullScreen(onError: (err) => void = null) {\n if (!document.fullscreenElement)\n enterFullScreen(onError)\n else\n exitFullScreen()\n}\n\nexport function isDigit(char: string) {\n return char >='0' && char <= '9'\n}\n\nexport function fixNumberSort(title: string): string {\n let numDigits = 0\n let number = ''\n\n if (!isDigit(title.charAt(title.length-1)))\n return title\n\n\n while (numDigits<=3 && isDigit(title.charAt(title.length-1-numDigits))) {\n number = title.charAt(title.length-1-numDigits) + number\n numDigits++\n }\n\n if (numDigits > 3)\n return title // Probably already taken care of\n\n let txtNoDigits = title.substring(0, title.length-numDigits)\n\n while(numDigits <=3) {\n number = '0' + number\n numDigits++\n }\n\n return txtNoDigits + number\n}\n\nexport function isLocalHost(): boolean {\n return (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1' || window.location.hostname.startsWith('192.168.') || window.location.hostname.startsWith('10.'))\n}\nexport function loadImage(url) {\n return new Promise((resolve, reject) => {\n const img = new Image();\n img.crossOrigin = 'Anonymous'; // This is important for cross-origin images\n img.onload = () => resolve(img);\n img.onerror = () => reject(new Error(`Failed to load image from URL: ${url}`));\n img.src = url;\n });\n}\n\nexport function rotateImage90Degrees(image) {\n return new Promise((resolve, reject) => {\n const canvas = document.createElement('canvas');\n const ctx = canvas.getContext('2d');\n\n // Swap canvas width and height for 90 degree rotation\n canvas.width = image.naturalHeight;\n canvas.height = image.naturalWidth;\n\n // Move the context's origin to the center of the canvas\n ctx.translate(canvas.width / 2, canvas.height / 2);\n\n // Rotate the canvas context by 90 degrees\n ctx.rotate(90 * Math.PI / 180);\n\n // Draw the image, offsetting by half its width and height to center it\n ctx.drawImage(image, -image.naturalWidth / 2, -image.naturalHeight / 2);\n\n // Convert canvas to blob\n canvas.toBlob(blob => {\n if (blob) {\n resolve(blob);\n } else {\n reject(new Error('Canvas to Blob conversion failed.'));\n }\n }, 'image/png');\n });\n}\n\nexport function hasNamedSlot(element: HTMLElement, slotName: string): boolean {\n const slot = element.querySelector(`slot[name=\"${slotName}\"]`) as any;\n\n return slot && slot.assignedNodes && slot.assignedNodes().length > 0;\n}\n\nexport function generateRandomRedirectUrl() {\n let url = window.location.href\n let idx = url.indexOf('?')\n\n if (idx && idx > 0)\n url = url.substring(0, idx)\n\n return url + \"?\" + Math.random()\n}\n\nexport function stringifyMap(obj) : string {\n if (obj == null)\n return null\n\n if (obj.entries) {\n return JSON.stringify(Array.from(obj.entries()))\n }\n\n return JSON.stringify(getAttributes(obj))\n}\n\nexport function combineDateAndTime(datePart: Date | string, timePart: Date | string | any): Date {\n if (!datePart)\n return dt\n\n var dt = new Date(datePart)\n\n if (!timePart)\n return dt\n\n if (typeof timePart === \"string\") {\n if (timePart.length >= 5 && timePart[2] === \":\") {\n dt.setHours(parseInt(timePart.substring(0, 2), 10), parseInt(timePart.substring(3, 5), 10), 0, 0)\n }\n } else if (timePart.hour && timePart.minute)\n dt.setHours(timePart.hour, timePart.minute, 0, 0)\n else if (typeof timePart.getHours === 'function')\n dt.setHours(timePart.getHours(), timePart.getMinutes(), timePart.getSeconds(), timePart.getMilliseconds())\n\n return dt\n}\n\nexport function getMinutesFromMidnight(date: Date) {\n return date.getHours() * 60 + date.getMinutes();\n}\n\nexport function ensureDateTime(value) {\n let date = new Date(value);\n\n if (isNaN(date.getTime())) {\n try {\n date = new Date(Date.parse(value));\n } catch (error) {\n console.error(\"Invalid date format:\", value);\n return null;\n }\n }\n\n return date;\n}\n\nexport function minutesToTime(minutes) {\n if (minutes < 0)\n minutes = 0\n if (minutes > 60 * 24)\n minutes = 60 * 24\n\n let hours = Math.floor(minutes / 60);\n let mins = minutes % 60;\n\n let formattedHours = hours.toString().padStart(2, '0');\n let formattedMinutes = mins.toString().padStart(2, '0');\n\n return `${formattedHours}:${formattedMinutes}`;\n}\n\nexport function extractDate(dateTime: string | Date): string {\n if (!dateTime)\n return null\n\n if (dateTime instanceof Date) {\n //return dateTime.toISOString().split('T')[0]\n return new Intl.DateTimeFormat('en-CA', { year: 'numeric', month: '2-digit', day: '2-digit'}).format(dateTime);\n }\n\n let idx = dateTime.indexOf('T')\n\n if (idx<0)\n return dateTime\n\n return dateTime.substring(0, idx)\n}\n\nexport function extractDateTime(dateTime: string) {\n if (!dateTime)\n return null\n\n let idx = dateTime.indexOf(':')\n\n if (idx<0)\n return dateTime\n\n idx = dateTime.indexOf(':', idx + 1)\n\n if (idx<0)\n return dateTime\n\n return dateTime.substring(0, idx).replace('T', ' ')\n}\n\nexport function extractTime(dateTime: string | Date | any): string {\n if (!dateTime)\n return null\n\n if (dateTime instanceof Date) {\n //return dateTime.toTimeString().substring(0, 5)\n return new Intl.DateTimeFormat('en-CA', { hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false }).format(dateTime);\n }\n\n if (dateTime.timeString)\n return dateTime.timeString\n\n console.log(\"extractDateTime(): \" + dateTime)\n dateTime = extractDateTime(dateTime)\n if (!dateTime)\n return null\n\n let idxTimeStart = dateTime.indexOf(' ')\n\n if (idxTimeStart<0)\n return dateTime\n\n return dateTime.substring(idxTimeStart+1)\n}\n\nexport function extractDateTimeRange(date1: string, date2: string) {\n let dateOnly1 = extractDate(date1)\n let dateOnly2 = extractDate(date2)\n\n if (!dateOnly1 || !dateOnly2)\n return null\n\n if (dateOnly1 !== dateOnly2)\n return dateOnly1 + \" - \" + dateOnly2\n\n let time1 = extractTime(date1)\n let time2 = extractTime(date2)\n\n if (!time1 || !time2)\n return dateOnly1 + \" - \" + dateOnly2\n\n\n return dateOnly1 + \" \" + time1 + \"-\" + time2\n}\n\nexport function extractTimeRange(date1: string, date2: string, onlySameDay: boolean = false) {\n let dateOnly1 = extractDate(date1)\n let dateOnly2 = extractDate(date2)\n\n if (!dateOnly1 || !dateOnly2)\n return null\n\n if (dateOnly1 !== dateOnly2 && onlySameDay)\n return extractDateTime(date1) + \" - \" + extractDateTime(date2)\n\n let time1 = extractTime(date1)\n let time2 = extractTime(date2)\n\n if (!time1 || !time2)\n return dateOnly1 + \" - \" + dateOnly2\n\n\n return time1 + \"-\" + time2\n}\n\nexport function groupBy>(array: T[], key: keyof T): Record {\n return array.reduce((result, currentValue) => {\n const groupKey = String(currentValue[key]);\n (result[groupKey] = result[groupKey] || []).push(currentValue);\n return result;\n }, {} as Record);\n}\n\nexport function joinStrings(separator: string, objects: string[], skipNUll = false /*warning, for keys this might not be good*/) {\n if (objects == null)\n return \"\"\n\n if (skipNUll)\n objects = objects.filter(it => it != null)\n else\n objects = objects.map(it => it != null ? it : \"\")\n\n return objects.join(separator)\n}\n\nexport function loadStandardCss() {\n loadCssStyle(\"global.css\")\n if (isMobile())\n loadCssStyle(\"mobile.css\")\n}\n\nexport function loadCssStyle(name, path = \"global/\") {\n const cssId = name.replace(\".css\", \"-css\"); // ID to prevent multiple additions\n\n if (!document.getElementById(cssId)) {\n const link = document.createElement('link');\n link.rel = 'stylesheet';\n link.href = path + name; // Adjust the path as needed\n link.id = cssId; // Assign the ID\n document.head.appendChild(link);\n }\n}\n\nexport function retryOperation(\n operation: (callback: (succeeded: boolean) => void) => void,\n retryTimeout: number,\n maxTimeout: number,\n onSuccess: () => void,\n onFailure: () => void,\n): void {\n let maxTime = Date.now() + maxTimeout;\n\n function attempt(): void {\n // Check if END_TIMEOUT has been passed\n if (Date.now() > maxTime) {\n console.error(\"Operation timed out\");\n if (onFailure) onFailure();\n return;\n }\n\n // Perform the operation\n operation((succeeded: boolean) => {\n if (succeeded) {\n if (onSuccess) onSuccess();\n } else {\n // Retry after retryTimeout if not successful\n window.setTimeout(attempt, retryTimeout);\n }\n });\n }\n\n // Start the first attempt\n attempt();\n}\n\nexport class ErrorHolder {\n private additionalErrors: string[] = []\n constructor(public error: string = null) {}\n\n // To use in controls that needs the user to enter a value\n // returns true if this value requires a value and did not get it\n requiresValue(controlValue: string | number, errorMessage: string, extraErrorMessage: string = null): boolean {\n if (this.error) {\n if (!controlValue || (typeof controlValue == 'string' && controlValue.length == 0)) {\n this.additionalErrors.push(extraErrorMessage ? extraErrorMessage + errorMessage : errorMessage);\n\n return true\n }\n\n return false;\n }\n\n if (controlValue && (typeof controlValue == 'string' && controlValue.length>0)) {\n return false;\n }\n\n if (controlValue && (typeof controlValue == 'number')) {\n return false;\n }\n\n this.error = extraErrorMessage ? extraErrorMessage + errorMessage : errorMessage\n\n return true;\n }\n\n requiresInteger(controlValue: string | number, errorMessage: string, extraErrorMessage: string = null): boolean {\n if (controlValue && typeof controlValue == 'string' && controlValue.trim().length > 0) {\n if (!isValidInteger(controlValue)) {\n this.error = \"Invalid number: \" + controlValue\n return true\n }\n\n return false\n }\n\n return this.requiresValue(controlValue, errorMessage, extraErrorMessage)\n }\n\n requiresFloat(controlValue: string | number, errorMessage: string, extraErrorMessage: string = null): boolean {\n if (controlValue && typeof controlValue == 'string' && controlValue.trim().length > 0) {\n if (!isValidFloat(controlValue)) {\n this.error = \"Invalid number: \" + controlValue\n return true\n }\n\n return false\n }\n\n return this.requiresValue(controlValue, errorMessage, extraErrorMessage)\n }\n\n correctIf(controlValue: boolean, errorMessage: string, extraErrorMessage: string = null): boolean {\n return this.requiresValue(controlValue === true ? \"TRUE\" : null, errorMessage, extraErrorMessage)\n }\n\n wrongIf(controlValue: boolean, errorMessage: string, extraErrorMessage: string = null): boolean {\n return this.requiresValue(controlValue === true ? null : \"TRUE\", errorMessage, extraErrorMessage)\n }\n\n getAdditionalErrorsNumber(): number {\n return this.additionalErrors.length\n }\n\n getAdditionalErrors() {\n return this.additionalErrors\n }\n\n hasNoErrors(): boolean {\n return this.error == null\n }\n\n hasErrors(): boolean {\n return this.error != null\n }\n}\n\nexport function isValidInteger(value: string): boolean {\n const trimmedValue = value.trim();\n const parsed = parseInt(trimmedValue, 10);\n\n return !isNaN(parsed) && parsed.toString() === trimmedValue;\n}\n\nexport function isValidFloat(value: string): boolean {\n const trimmedValue = value.trim();\n const parsed = parseFloat(trimmedValue);\n\n return !isNaN(parsed);\n}\n\nexport function baseUrl() {\n const { protocol, hostname, port } = window.location;\n\n return port && port !== \"80\" && port !== \"443\"\n ? `${protocol}//${hostname}:${port}`\n : `${protocol}//${hostname}`;\n }\n\n export function uniqueList(list: string[]): string[] {\n let output: string [] = []\n let set = new Set()\n\n list.forEach(it => {\n if (!set.has(it)) {\n set.add(it)\n output.push(it)\n }\n })\n\n return output\n }\n\n // It takes a set, converts it to a list, then makes a map converting the value to the index in the list\n export function setToIndexedMap(set: Set) {\n var uniqueList = [... set]\n var map: Map = new Map()\n\n for (let i=0; i