/**
* Utility functions for @unitybase/ub-pub module
*
* @module utils
* @memberOf module:@unitybase/ub-pub
* @author UnityBase team
*/
/* global FileReader, Blob */
const i18n = require('./i18n').i18n
/**
* see docs in ub-pub main module
*
* @private
* @param {object} objectTo The receiver of the properties
* @param {...object} objectsFrom The source(s) of the properties
* @returns {object} returns objectTo
*/
module.exports.apply = function (objectTo, objectsFrom) {
Array.prototype.forEach.call(arguments, function (obj) {
if (obj && obj !== objectTo) {
Object.keys(obj).forEach(function (key) {
objectTo[key] = obj[key]
})
}
})
return objectTo
}
const FORMAT_RE = /{(\d+)}/g
/**
* see docs in ub-pub main module
*
* @private
* @param {string} stringToFormat The string to be formatted.
* @param {...*} values The values to replace tokens `{0}`, `{1}`, etc in order.
* @returns {string} The formatted string.
*/
module.exports.format = function (stringToFormat, ...values) {
return stringToFormat.replace(FORMAT_RE, function (m, i) {
return values[i]
})
}
/**
* see docs in ub-pub main module
*
* @private
* @param {string} namespacePath
* @returns {object} The namespace object.
*/
module.exports.ns = function (namespacePath) {
let root = window
let part, j, subLn
const parts = namespacePath.split('.')
for (j = 0, subLn = parts.length; j < subLn; j++) {
part = parts[j]
if (!root[part]) root[part] = {}
root = root[part]
}
return root
}
/**
* see docs in ub-pub main module
*
* @private
* @param {Date|string} value
* @returns {Date}
*/
module.exports.iso8601Parse = function (value) {
return value ? new Date(value) : null
}
/**
* see docs in ub-pub main module
*
* @private
* @param {*} v Value to convert
* @returns {boolean|null}
*/
module.exports.booleanParse = function (v) {
if (typeof v === 'boolean') return v
if ((v === undefined || v === null || v === '')) return null
return v === 1
}
/**
* see docs in ub-pub main module
*
* @private
* @param {File|ArrayBuffer|string|Blob|Array} data
* @returns {Promise<string>} resolved to data converted to base64 string
*/
module.exports.base64FromAny = function (data) {
return new Promise((resolve, reject) => {
if (typeof Buffer === 'function') {
const res = Buffer.from(data).toString('base64')
resolve(res)
} else {
const reader = new FileReader()
const blob = (data instanceof Blob) ? data : new Blob([data])
reader.addEventListener('loadend', function () {
resolve(reader.result.split(',', 2)[1]) // remove data:....;base64, from the beginning of string //TODO -use indexOf
})
reader.addEventListener('error', function (event) {
reject(event)
})
reader.readAsDataURL(blob)
}
})
}
/**
* see docs in ub-pub main module
*
* @private
* @param {File} file
* @returns {Promise<Uint8Array>} resolved to file content as Uint8Array
*/
module.exports.file2Uint8Array = function (file) {
return new Promise((resolve, reject) => {
const reader = new FileReader()
reader.onload = function () {
resolve(new Uint8Array(reader.result))
}
reader.onerror = function (reason) {
reject(reason)
}
reader.readAsArrayBuffer(file)
})
}
const BASE64STRING = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
const BASE64ARR = [];
(function () {
for (let i = 0, l = BASE64STRING.length - 1; i < l; i++) {
BASE64ARR.push(BASE64STRING[i])
}
})()
const BASE64DECODELOOKUP = new Uint8Array(256);
(function () {
for (let i = 0, l = BASE64STRING.length; i < l; i++) {
BASE64DECODELOOKUP[BASE64STRING[i].charCodeAt(0)] = i
}
})()
/**
* see docs in ub-pub main module
*
* @private
* @param {string} base64
* @returns {ArrayBuffer}
*/
module.exports.base64toArrayBuffer = function (base64) {
let bufferLength = base64.length * 0.75
const len = base64.length
let p = 0
let encoded1, encoded2, encoded3, encoded4
if (base64[base64.length - 1] === '=') {
bufferLength--
if (base64[base64.length - 2] === '=') bufferLength--
}
const arrayBuffer = new ArrayBuffer(bufferLength)
const bytes = new Uint8Array(arrayBuffer)
for (let i = 0; i < len; i += 4) {
encoded1 = BASE64DECODELOOKUP[base64.charCodeAt(i)]
encoded2 = BASE64DECODELOOKUP[base64.charCodeAt(i + 1)]
encoded3 = BASE64DECODELOOKUP[base64.charCodeAt(i + 2)]
encoded4 = BASE64DECODELOOKUP[base64.charCodeAt(i + 3)]
bytes[p++] = (encoded1 << 2) | (encoded2 >> 4)
bytes[p++] = ((encoded2 & 15) << 4) | (encoded3 >> 2)
bytes[p++] = ((encoded3 & 3) << 6) | (encoded4 & 63)
}
return arrayBuffer
}
/**
* UnityBase client-side exception.
* Such exceptions will not be showed as unknown error in {@link UB#showErrorWindow UB.showErrorWindow}
*
* @example
// in adminUI will show message box with text:
// "Record was locked by other user. It\'s read-only for you now"
throw new UB.UBError('lockedBy')
* @param {string} message Message can be either localized message or locale identifier - in this case UB#showErrorWindow translate message using {@link UB#i18n}
* @param {string} [detail] Error details
* @param {number} [code] Error code (for server-side errors)
* @extends {Error}
*/
function UBError (message, detail, code) {
this.name = 'UBError'
this.detail = detail
this.code = code
this.message = message || 'UBError'
if (Error.captureStackTrace) {
Error.captureStackTrace(this, UBError)
} else {
this.stack = (new Error()).stack
}
}
UBError.prototype = new Error()
UBError.prototype.constructor = UBError
module.exports.UBError = UBError
/**
* Quiet exception. Global error handler does not show this exception for user. Use it for silently reject promise
*
* @param {string} [message] Message
* @param {string} [detail] Error details
* @extends {Error}
*/
function UBAbortError (message, detail) {
this.name = 'UBAbortError'
this.detail = detail
this.code = 'UBAbortError'
this.message = message || 'UBAbortError'
if (Error.captureStackTrace) {
Error.captureStackTrace(this, UBAbortError)
} else {
this.stack = (new Error()).stack
}
}
UBAbortError.prototype = new Error()
UBAbortError.prototype.constructor = UBAbortError
module.exports.UBAbortError = UBAbortError
const TEST_ERROR_MESSAGE_RE = /<<<.*?>>>/
const PARSE_ERROR_MESSAGE_RE = /(?:^|")<<<(.*?)>>>(?:\|(\[[^\]]*]))?(?:$|")/
const SIMPLE_PARSE_ERROR_MESSAGE_RE = /<<<(.*)>>>/
function parseAndTranslateUBErrorMessage (errMsg) {
// RegExp for fast detection of error message
if (!TEST_ERROR_MESSAGE_RE.test(errMsg)) {
return i18n(errMsg)
}
// RegExp for full detection of error message by patterns, using parts
const match = errMsg.match(PARSE_ERROR_MESSAGE_RE)
if (!match) {
// Shall never happen
return i18n(errMsg)
}
// Problem is that errMsg is JSON-encoded object, and we are looking for a string inside it.
const [msgUnparsed, msgPart, argsPart] = match
if (!argsPart) {
// No parameters passed, just JSON.parse content inside <<<>>>
return i18n(JSON.parse('"' + msgPart + '"'))
}
// JSON.parse the whole part in quotes, to decode JSON as a string
const msgStr = JSON.parse(msgUnparsed[0] === '"' ? msgUnparsed : '"' + msgUnparsed + '"')
const index = msgStr.indexOf('|')
if (index === -1) {
// Shall never happen
return i18n(msgStr)
}
const msg = msgStr.substring(3, index - 3) // Use "substring" to get part inside <<<>>>, knowing index of |
const argsStr = msgStr.substr(index + 1) // Get all the part after |
const args = JSON.parse(argsStr) // Parse it. If it fails - it fails, we expect server to return correct JSON
if (Array.isArray(args)) {
args.unshift(msg)
return i18n.apply(null, args)
} else {
// Object or a single value
return i18n(msg, args)
}
}
module.exports.parseAndTranslateUBErrorMessage = parseAndTranslateUBErrorMessage
const SERVER_ERROR_CODES = {
1: 'ubErrNotImplementedErrnum',
2: 'ubErrRollbackedErrnum',
3: 'ubErrNotExecutedErrnum',
4: 'ubErrInvaliddataforrunmethod',
5: 'ubErrInvaliddataforrunmethodlist',
6: 'ubErrNoMethodParameter',
7: 'ubErrMethodNotExist',
8: 'ubErrElsAccessDeny',
9: 'ubErrElsInvalidUserOrPwd',
10: 'ubErrElsNeedAuth',
11: 'ubErrNoEntityParameter',
13: 'ubErrNoSuchRecord',
14: 'ubErrInvalidDocpropFldContent',
15: 'ubErrEntityNotExist',
16: 'ubErrAttributeNotExist',
17: 'ubErrNotexistEntitymethod',
18: 'ubErrInvalidSetdocData',
19: 'ubErrSoftlockExist',
20: 'ubErrNoErrorDescription',
21: 'ubErrUnknownStore',
22: 'ubErrObjdatasrcempty',
23: 'ubErrObjattrexprbodyempty',
24: 'ubErrNecessaryfieldNotExist',
25: 'ubErrRecordmodified',
26: 'ubErrNotexistnecessparam',
27: 'ubErrNotexistfieldlist',
28: 'ubErrUpdaterecnotfound',
29: 'ubErrNecessaryparamnotexist',
30: 'ubErrInvalidstoredirs',
31: 'ubErrNofileinstore',
32: 'ubErrAppnotsupportconnection',
33: 'ubErrAppnotsupportstore',
34: 'ubErrDeleterecnotfound',
35: 'ubErrNotfoundlinkentity',
36: 'ubErrEntitynotcontainmixinaslink',
37: 'ubErrEssnotinherfromessaslink',
38: 'ubErrInstancedatanameisreadonly',
39: 'ubErrManyrecordsforsoftlock',
40: 'ubErrNotfoundidentfieldsl',
41: 'ubErrInvalidlocktypevalue',
42: 'ubErrLockedbyanotheruser',
43: 'ubErrInvalidwherelistinparams',
44: 'ubErrRecnotlocked',
45: 'ubErrManyrecordsforchecksign',
46: 'ubErrNotfoundparamnotrootlevel',
47: 'ubErrCantcreatedirlogmsg',
48: 'ubErrCantcreatedirclientmsg',
49: 'ubErrConnectionNotExist',
50: 'ubErrDirectUnityModification',
51: 'ubErrCantdelrecthisvalueusedinassocrec',
52: 'ubErrAssocattrnotfound',
53: 'ubErrAttrassociationtoentityisempty',
54: 'ubErrNotfoundconforentityinapp',
55: 'ubErrNewversionrecnotfound',
56: 'ubErrElsAccessDenyEntity',
57: 'ubErrAlsAccessDenyEntityattr',
58: 'ubErrDatastoreEmptyentity',
// 59: "ubErrCustomerror"
67: 'ubErrTheServerHasExceededMaximumNumberOfConnections',
69: 'ubErrFtsForAppDisabled',
72: 'ubErrElsPwdIsExpired',
73: 'ELS_USER_NOT_FOUND',
74: 'VALUE_MUST_ME_UNIQUE'
}
module.exports.SERVER_ERROR_CODES = SERVER_ERROR_CODES
function parseUBAuthError (rejectReason, authParams) {
if (!rejectReason || (rejectReason instanceof Error)) {
return rejectReason
}
// http error
const errDescription = rejectReason.data || rejectReason // in case of server-side error we got a {data: {errMsg: ..}..}
const errInfo = {
errMsg: errDescription.errMsg,
errCode: errDescription.errCode,
errDetails: errDescription.errMsg
}
if (rejectReason.status === 403) {
// in case exception text is wrapped in <<<>>> - use it, else replace to default "access deny"
// for Access deny error on the auth stage transform it to Invalid user or pwd
if (!TEST_ERROR_MESSAGE_RE.test(errInfo.errMsg) || (errInfo.errMsg === '<<<Access deny>>>')) {
errInfo.errMsg = (authParams.authSchema === 'UB') ? 'msgInvalidUBAuth' : 'msgInvalidCertAuth'
}
} else if (rejectReason.status === 0) {
errInfo.errMsg = 'serverIsBusy'
errInfo.errDetails = 'network error'
} else {
if (!errInfo.errMsg) { errInfo.errMsg = 'unknownError' } // internalServerError
}
if (TEST_ERROR_MESSAGE_RE.test(errInfo.errMsg)) {
errInfo.errMsg = JSON.parse('"' + errInfo.errMsg.match(SIMPLE_PARSE_ERROR_MESSAGE_RE)[1] + '"')
}
const codeMsg = SERVER_ERROR_CODES[errInfo.errCode]
if (codeMsg) {
errInfo.errDetails = codeMsg + ' ' + errInfo.errDetails
}
return new UBError(errInfo.errMsg, errInfo.errDetails, errInfo.errCode)
}
module.exports.parseUBAuthError = parseUBAuthError
/**
* Parse error and translate message using {@link UB#i18n i18n}
*
* @param {string|object|Error|UBError} errMsg message to show
* @param {string} [errCode] (Optional) error code
* @param {string} [entityCode] (Optional) entity code
* @param {string} detail erro detail
* @returns {{errMsg: string, errCode: *, entityCode: *, detail: *|string}}
*/
module.exports.parseUBError = function (errMsg, errCode, entityCode, detail) {
let errDetails = detail || ''
if (errMsg && errMsg instanceof UBError) {
errCode = errMsg.code
errDetails = errMsg.detail
if (errMsg.stack) {
errDetails += '<br/>stackTrace:' + errMsg.stack
}
errMsg = errMsg.message
} else if (errMsg instanceof Error) {
if (errMsg.stack) {
errDetails += '<br/>stackTrace:' + errMsg.stack
}
errMsg = errMsg.toString()
} else if (errMsg && (typeof errMsg === 'object')) {
errCode = errMsg.errCode
entityCode = errMsg.entity
errMsg = errMsg.errMsg ? errMsg.errMsg : JSON.stringify(errMsg)
errDetails = errMsg.detail || errDetails
}
return {
errMsg: i18n(errMsg),
errCode,
entityCode,
detail: errDetails
}
}
/**
* Log message to console (if console available)
*
* @function
* @param {...*} msg
*/
module.exports.log = function log (msg) {
if (console) console.log.apply(console, arguments)
}
/**
* Log error message to console (if console available)
*
* @function
* @param {...*} msg
*/
module.exports.logError = function logError (msg) {
if (console) {
console.error.apply(console, arguments)
}
}
/**
* Log warning message to console (if console available)
*
* @function
* @param {...*} msg
*/
module.exports.logWarn = function logWarn (msg) {
if (console) {
console.warn.apply(console, arguments)
}
}
/**
* Log debug message to console.
* Since it binds to console, can also be used to debug Promise resolving in this way
*
* @example
UB.get('timeStamp').then(UB.logDebug);
* @function
* @param {...*} msg
*/
module.exports.logDebug = console.info.bind(console)
const userAgent = (typeof navigator !== 'undefined' && navigator.userAgent) ? navigator.userAgent.toLowerCase() : 'nodeJS'
/** @type {string} */
module.exports.userAgent = userAgent.toLowerCase()
/** @type {boolean} */
module.exports.isChrome = /\bchrome\b/.test(userAgent)
/** @type {boolean} */
module.exports.isWebKit = /webkit/.test(userAgent)
/** @type {boolean} */
module.exports.isGecko = !/webkit/.test(userAgent) && /gecko/.test(userAgent)
/** @type {boolean} */
module.exports.isOpera = /opr|opera/.test(userAgent)
/** @type {boolean} */
module.exports.isMac = /macintosh|mac os x/.test(userAgent)
/** @type {boolean} */
module.exports.isSecureBrowser = /\belectron\b/.test(userAgent)
/** @type {boolean} */
module.exports.isReactNative = (typeof navigator !== 'undefined' && navigator.product === 'ReactNative')
/** @type {boolean} */
module.exports.isNodeJS = /nodeJS/.test(userAgent)
/**
* localDataStorage keys used by @unitybase-ub-pub (in case of browser environment)
*/
module.exports.LDS_KEYS = {
/**
* Authentication schema used by user during last logon
*/
LAST_AUTH_SCHEMA: 'lastAuthType',
/**
* In case stored value is 'true' then login using Negotiate without prompt
*/
SILENCE_KERBEROS_LOGIN: 'silenceKerberosLogin',
/**
* Last logged-in username (login)
*/
LAST_LOGIN: 'lastLogin',
/**
* In case stored value is 'true' then used call logout directly (i.e. press logout button)
*/
USER_DID_LOGOUT: 'userDidLogout',
/**
* In case document url is set using URI Schema, for example `document.location.href="ms-word:ofv|u|http://...."`
* window.onbeforeunload should skip call og App.logout(). Since we do not have access to the target URL inside onbeforeunload event
* caller must set this flar in localstorage to `true` to prevent log out of current user
*/
PREVENT_CALL_LOGOUT_ON_UNLOAD: 'preventLogoutOnUnload',
/**
* locale, preferred by user. Empty in case of first login
*/
PREFERRED_LOCALE: 'preferredLocale',
/**
* User preferred uData keys, passed as prefUData URL param during `/auth` handshake (for example organization ID in case used assigned to several og them)
* Server-side on('login') event handler MUST verify passed preferred keys and can apply it into uData
*/
PREFFERED_UDATA_PREFIX: 'UDATA_PREFFERED_'
}