Singleton instance of UnityBase application. Allow direct access to the database connections, blob stores, HTTP endpoints (full control on HTTP request & response) registration, read domain and server config.

Mixes EventEmitter, and emit:

  • launchEndpoint:before with parameters: (req, resp, endpointName)
  • endpointName + ':before' event before endpoint handler execution
  • endpointName + ':after' event in case neither exception is raised nor App.preventDefault() is called
  • launchEndpoint:after with parameters: (req, resp, endpointName, defaultPrevented)

To prevent endpoint handler execution App.preventDefault() can be used inside :before handler.

  
      const App = require('@unitybase/ub').App
// Register public (accessible without authentication) endpoint
App.registerEndpoint('echoToFile', echoToFile, false)
// write custom request body to file FIXTURES/req and echo file back to client
// @ param {THTTPRequest} req
// @ param {THTTPResponse} resp
function echoToFile (req, resp) {
  var fs = require('fs')
  fs.writeFileSync(path.join(FIXTURES, 'req'), req.read('bin'))
  resp.statusCode = 200
  resp.writeEnd(fs.readFileSync(path.join(FIXTURES, 'req'), {encoding: 'bin'}))
}
//Before getDocument requests
//@ param {THTTPRequest} req
//@ param {THTTPResponse} resp
function doSomethingBeforeGetDocumentCall(req, resp){
  console.log('User with ID', Session.userID, 'try to get document')
}
// Adds hook called before each call to getDocument endpoint
App.on('getDocument:before', doSomethingBeforeGetDocumentCall)
//
//After getDocument requests
//@ param {THTTPRequest} req
//@ param {THTTPResponse} resp
function doSomethingAfterGetDocumentCall(req, resp){
  params = req.parsedParameters
  console.log('User with ID', Session.userID, 'obtain document using params',  params)
}
App.on('getDocument:after', doSomethingAfterGetDocumentCall)
  
Mixes In:

# new ServerApp ()

Members

# blobStores static

BLOB stores methods. For usage examples see:

# dbConnections : Object.<string, DBConnection> static

Databases connections pool

# defaultLang : string static

Application default language

# domainInfo : UBDomain static

Extended information about application domain (metadata)

# emitterEnabled : boolean deprecated static

Is event emitter enabled for App singleton. Default is false

Starting from 1.11 this property ignored (always TRUE)

# endpointContext : object static

Endpoint context. Application logic can store here some data what required during single HTTP method call; Starting from UB@5.17.9 server reset App.endpointContext to {} after endpoint implementation execution, so in the beginning of execution it's always empty

App.endpointContext.MYMODEL_mykey = 'some value we need to share between different methods during a single user request handling'

# externalURL : string static

URL that the User from the internet will use to access your server. To be used in case server is behind a reverse proxy

# fsObserve static

Observe a file system operation time (exposed as prometheus unitybase_fs_operation_duration_seconds histogram).

WARNING do not use a full file path - use a folder name or better a mnemonic name (BLOB store name for example). Amount of metric labels SHOULD be as small as possible. The same is true for operation`s names.

See fileSystemBlobStore for real life usage example.

# httpCallObserve static

Observe an HTTP client operation time (exposed as prometheus unitybase_httpext_duration_seconds histogram).

http module automatically observe each request, passing host as uri parameter. Method can be called manually in case some part of path should be observed also.

WARNING do not use a full path - use a part what identify an endpoint without parameters. Amount of metric labels SHOULD be as small as possible. The same is true for operation`s names.

# localIPs static

List of a local server IP addresses CRLF (or CR for non-windows) separated

# package : object static

Application package.json content (parsed)

# serverConfig : object static

Server configuration - result of argv.getServerConfiguration

Name Type Description
httpServer object

HTTP server config

application object
application.name string
application.defaultLang string
application.domain object
application.domain.models Array
application.domain.supportedLanguages Array.<string>
application.customSettings object
uiSettings object

Section uiSettings of ubConfig

security object

# serverPublicCert : string static

Defense edition only, Base64 encoded public server certificate

Contains non-empty value in case security.dstu.trafficEncryption === true and key name defined in security.dstu.novaLib.keyName

# serverURL : string static

Full URl HTTP server is listen on (if HTTP server enabled, else - empty string)

# staticPath : string static

Full path to application static folder if any, '' if static folder not set

Methods

# authFromRequest (noHTTPBodyInRespboolean, doSetOutCookieboolean, fallbackAuthTokenoptstring) → boolean static

Try retrieve or create new session from request headers. Return true if success, false if more auth handshakes is required. In case of invalid credential throw security exception

Arguments:
  • noHTTPBodyInResp: boolean

    If true do not write a uData to the HTTP response

  • doSetOutCookie: boolean

    If true set an out authorization cookie on success response (Negotiate only)

  • fallbackAuthToken: string

    Optional fallback auth token. If passed - will be used by UB schema authorization if no other token (in headers\cookies\sessin_signature URI parameter) found

# confirmSession2faSecret (sessionIDstring, secretoptstring) → boolean | string static

Confirm 2FA secret previously sets by Session.setExpected2faSecret. If secrets match - reset session expected secret and returns true. After this endpoints execution became allowed; If secret does not match - return string with one of the reason (do not expose this reason to caller!):

  • 'sessionNotFound': provided sessionID not exists
  • 'sessionIsSystem': provided sessionID is for system session, such sessions can't be a subject of 2FA
  • 'wrongSecret': provided secret do not match session 2FA secret
  • 'wrongSecretUserLocked': provided secret do not match session 2FA secret many times, so session is closed and user is locked If secret is empty - return confirmation status true if 2fa is confirmed, false if not, string with reason in case of error

Arguments:
  • sessionID: string
  • secret = '': string

    if secret is empty - status request

# dbCommit (connectionNameoptstring) → boolean static

Commit active database transaction if any. In case connectionName is not passed will commit all active transactions for all connections. Return true if transaction is committed, or false if database not in use or no active transaction

Arguments:

# dbInTransaction (connectionNamestring) → boolean static

Check database are used in current endpoint context and DB transaction is already active

Arguments:

# dbRollback (connectionNameoptstring) → boolean static

Rollback active database transaction if any. In case connectionName is not passed will rollback all active transactions for all connections. Return true if transaction is rollback'ed, or false if database not in use or no active transaction

Arguments:

# dbStartTransaction (connectionNamestring) → boolean static

Start a transaction for a specified database. If database is not used in this context will create a connection to the database and start transaction.

For Oracle with DBLink first statement to DBLink'ed table must be either update/insert/delete or you MUST manually start transaction to prevent "ORA-01453: SET TRANSACTION be first statement"

Arguments:

# deleteFromFTSIndex (entityNamestring, instanceIDnumber) static

Delete row from FTS index for exemplar with instanceID of entity entityName (mixin fts must be enabled for entity)

Arguments:

# els (entityCodestring, methodCodestring, rolesIDsoptArray.<number>) → boolean static

Check Entity-Level-Security for specified entity/method

Arguments:
  • entityCode: string
  • methodCode: string
  • rolesIDs: Array.<number>

    If not passed - current user session is used for roles (faster)

  
      if App.els('uba_user', 'insert'){
       // do something
}
  

# enterCriticalSection (csIndexnumber) static

Waits for ownership of the specified critical section object. The function returns when the calling thread is granted ownership.

** IMPORTANT** A thread must call App.leaveCriticalSection once for each time that it entered the critical section.

Arguments:
  • csIndex: number

    A critical section index returned by App.registerCriticalSection

# getUISettings () → string static deprecated

Use App.serverConfig.uiSettings: Object instead

Return stringify JSON specified in serverConfig.uiSettings

# getUserSessionsCount (userIDnumber) → number static

Return session count for specified user, including current session

Return:

session count for specified user, including current session

Arguments:

# globalCacheGet (keystring, optionsoptobject) → string static

Get value from global cache. Global cache shared between all threads and, if redis.useForGlobalCache=true in config - between all servers in server group

Return '' (empty string) in case key not present in cache.

For multi-tenancy environments key is automatically appended by tenantID (own cache for each tenant)

Arguments:
  • key: string

    Key to retrieve

  • options: object
    • ignoreTenantsboolean

      for multi-tenancy environment. If true - do not append key by tenantID

# globalCachePut (keystring, valuestring | null, optionsoptobject) → boolean static

Put value to global cache. Global cache shared between all threads and, if redis.useForGlobalCache=true in config - between all servers in server group

For multi-tenancy environments key is automatically appended by tenantID (own cache for each tenant)

Return:

return true if key is set, otherwise - false (for example when nx=true and key exists)

Arguments:
  • key: string

    key to hold the string value

  • value: string| null

    Value to put into this key. If === null then key will be remover from cache

  • options: object
    • nxboolean

      only set the key if it does not already exist

    • pxnumber

      REDIS backend only. Set the specified expire time, in milliseconds (a positive integer); 0 - not expired

    • ignoreTenantsboolean

      for multi-tenancy environment. If true - do not append key by tenantID

# grantEndpointToRole (endpointNamestring, roleCodestring) → boolean static

Grant endpoint to role

Return:

true if endpoint exists and role not already granted, false otherwise

Arguments:

# isIPInBlackList (IPstring) → boolean | string static

Check IP match any of line from file specified in ubConfig.security.blackListFileName. Returns false if not match or string with reason (comment from file line) if so

Arguments:

# leaveCriticalSection (csIndexnumber) static

Releases ownership of the specified critical section

Arguments:

# logEnter (methodNamestring) static

Enter a log recursion call. ** IMPORTANT** A thread must call App.logLeave once for each time that it entered the log recursion. For method with ctx: ubMethodParam parameter App.wrapEnterLeaveForUbMethod can be used to create an enter/leave log wrapper

Arguments:
  
      function wrapEnterLeave(enterText, originalMethod) {
    return function(ctx) {
      App.logEnter(enterText)
      try {
        originalMethod(ctx)
      } finally {
        App.logLeave()
      }
    }
  }
  

# logLeave () static

Exit a log recursion call

# logout () → boolean static

Logout a current user (kill current session)

# memCacheGet (keystring, optionsoptobject) → string static

Get value from memory cache. Memory cache shared between all threads of current instance. If you are completely sure that the value should be used by only one instance, use this function, otherwise use globalCacheGet.

Return '' (empty string) in case key not present in cache.

For multi-tenancy environments key is automatically appended by tenantID (own cache for each tenant)

Arguments:
  • key: string

    Key to retrieve

  • options: object
    • ignoreTenantsboolean

      for multi-tenancy environment. If true - do not append key by tenantID

# memCachePut (keystring, valuestring | null, optionsoptobject) → boolean static

Put value to memory cache. Memory cache shared between all threads of current instance. If you are completely sure that the value should be used by only one instance, use this function, otherwise use globalCachePut.

For multi-tenancy environments key is automatically appended by tenantID (own cache for each tenant)

Return:

return true if key is set, otherwise - false (for example when nx=true and key exists)

Arguments:
  • key: string

    Key to put into

  • value: string| null

    Value to put into this key. If === null then key will be remover from cache

  • options: object
    • nxboolean

      only set the key if it does not already exist

    • ignoreTenantsboolean

      for multi-tenancy environment. If true - do not append key by tenantID

# preventDefault () static

Accessible inside app-level :before event handler. Call to prevent default method handler. In this case developer are responsible to fill response object, otherwise HTTP 400 is returned

# registerCriticalSection (csNamestring) → number static

Register a named critical section. Can be done only in initialization mode. In case section with the same name already registered in another thread - returns existed CS index

All threads MUST register section in the same way, do not put call into condition what may evaluate to the different values in the different threads.

Arguments:
  • csName: string

    Critical section name

  
      const App = require('@unitybase/ub').App
// critical section must be registered once, at the moment modules are evaluated without any conditions
const MY_CS = App.registerCriticalSection('SHARED_FILE_ACCESS')

function concurrentFileAccess() {
  // prevents mutual access to the same file from the different threads
  App.enterCriticalSection(FSSTORAGE_CS)
  try {
    const data = fs.readfileSync('/tmp/concurrent.txt', 'utf8')
    // do some operation what modify data
    fs.writefileSync('/tmp/concurrent.txt', data)
  } finally {
    // important to leave critical section in finally block to prevent forever lock
    App.leaveCriticalSection(FSSTORAGE_CS)
  }
}
  

# registerEndpoint (endpointNamestring, handlerfunction, authorizationRequiredoptboolean, isDefaultoptboolean, bypassHTTPLoggingoptboolean) static

Register a server endpoint. One of the endpoints can be default endpoint - it will be used as a fallback in case URL do not start with any of known endpoints name.

Exceptions inside endpoint handler are intercepted by UB server. In case exception is occurred server will roll back any active DB transactions and serialize an exception message to response depending on server execution mode:

  • for dev mode - original exception text will be serialized (for debugging purpose)
  • for production mode - in case exception message is wrapped into <<<..>>> then this message will be serialized, if not - text will be always Internal server error (for security reason)

Recommended way to throw a handled error inside endpoint handler is throw new UB.UBAbort('.....')

Arguments:
  • endpointName: string
  • handler: function
  • authorizationRequired = true: boolean

    If true UB will check for valid Authorization header before execute endpoint handler

  • isDefault = false: boolean
  • bypassHTTPLogging = false: boolean

    Do not put HTTP body into log (for example if body contains sensitive information, like password)

  
      // Write a custom request body to file FIXTURES/req and echo file back to client
// @param {THTTPRequest} req
// @param {THTTPResponse} resp
//
function echoToFile(req, resp) {
  var fs = require('fs');
  fs.writeFileSync(FIXTURES + 'req', req.read('bin'));
  resp.statusCode = 200;
  resp.writeEnd(fs.readFileSync(FIXTURES + 'req', {encoding: 'bin'}));
}
App.registerEndpoint('echoToFile', echoToFile);
  

# reloadConfig () → boolean static

Partially reload server config - the same as -HUP signal for process

  • set JS variable App.serverConfig to new config
  • reset generated index page (uiSettings block of config)

Return:

true is config s valid and successfully reloaded

# removeUserSessions (userIDnumber, exceptCurrentoptboolean) → boolean static

Remove all user sessions (logout user).

If exceptCurrent is true - do not remove current session (logout all other sessions except my).

Return:

true if user had had any session

Arguments:
  • userID: number
  • exceptCurrent = false: boolean

    If true - do not remove current session

  
      const UB = require('@unitybase/ub')
const Session = UB.Session
const App = UB.App
Session.on('login', logoutAllMyOldSessions)
// One user - one session mode
function logoutAllMyOldSessions (req) {
  if (App.removeUserSessions(Session.userID, true)) {
    console.log(`All other sessions for user ${Session.userID} are removed`)
  }
}
  

# runInAutonomousTransaction (funcfunction) → * static

Run func in autonomous transaction

All database statements, or direct SQL execution from inside func will be executed in a separate connection intended for executing autonomous transactions and automatically committed (or rolled back in case of an exception).

Can't be nested.

Return:

a func result

Arguments:

# updateFTSIndex (entityNamestring, instanceIDnumber) static

Update FTS index for exemplar with instanceID of entity entityName (mixin fts must be enabled for entity). In case row does not exist in FTS perform insert action automatically.

Arguments:

# wrapEnterLeaveForUbMethod (enterTextstring, originalMethodfunction) → function static

Enter a log recursion call with enterText, call methodImpl and exit from log recursion call.

In case enterText is method(myMixin) my_entity.select, logging will be:

20210314 09224807 " + method(myMixin) my_entity.select 20210314 09224807 " debug some debug (shifted by recursion level automatically) 20210314 09224807 " - 00.005.124

Arguments:
  • enterText: string

    Text what will be logged in the beginning of function call

  • originalMethod: function

    function what accept one parameter - ctx: ubMethodParam

Events

# 'commit:after'  --> (connectionNamestring)

Fires (by native code) for an UB.App just after RDBMS "commit" is successfully called

Arguments:
  • connectionName: string

    Name of the committed connection

# 'commit:before'  --> (connectionNamestring)

Fires (by native code) for an UB.App just before RDBMS "commit" is called

  
      App.on('commit:before', (connectionName) => {
  console.debug('Event commit:before fired for connection', connectionName)
})
  
Arguments:
  • connectionName: string

    Name of the connection being committed

# 'endpointName:after'  --> (reqTHTTPRequest, respTHTTPResponse, endpointNamestring)

Fires after endpoint execution. In example below handler is called before each getDocument execution

Arguments:

# 'endpointName:before'  --> (reqTHTTPRequest, respTHTTPResponse, endpointNamestring)

Fires before endpoint execution. In example below handler is called before each getDocument execution

  
      const UB = require('@unitybase/ub')
   const App = UB.App
   function doSomethingBeforeGetDocumentCall(req, resp){
console.log('User with ID', Session.userID, 'try to get document')
}
   // Adds hook called before each call to getDocument endpoint
   App.on('getDocument:before', doSomethingBeforeGetDocumentCall)
  
Arguments:

# 'launchEndpoint:after'  --> (reqTHTTPRequest, respTHTTPResponse, endpointNamestring)

Fires after any endpoint execution

Arguments:

# 'launchEndpoint:before'  --> (reqTHTTPRequest, respTHTTPResponse, endpointNamestring)

Fires before any endpoint execution

Arguments:

# 'rollback:after'  --> (connectionNamestring)

Fires (by native code) for an UB.App just after RDBMS "rollback" is called

Arguments:
  • connectionName: string

    Name of the rollback connection

# 'rollback:before'  --> (connectionNamestring)

Fires (by native code) for an UB.App just before RDBMS "rollback" is called

  
      App.on('rollback:before', (connectionName) => {
  console.debug('Event rollback:before fired for connection', connectionName)
})
  
Arguments:
  • connectionName: string

    Name of the connection being rollback

# applicationReady  --> ()

Fires inside each working thread for an UB.App after all initialization step is done (domain, endpoints and BLOB stores are initialized).

On this stage application can use both domain and blobStores.

# domainIsLoaded  --> ()

Fires inside each working thread for an UB.App just after all domain entities (all *.meta) are loaded into server memory and all server-side js are evaluated (for each working thread) but before BLOB stores initialization and endpoints registration.

On this stage you can subscribe on a cross-model handles.

  
      const UB = require('@unitybase/ub')
const App = UB.App
App.once('domainIsLoaded', function () {
   const entities = App.domainInfo.entities
   for (const eName in entities) {
      // if entity have attribute mi_fedUnit
      if (entities[eName].attributes.mi_fedUnit) {
        let entityObj = global[eName]
        entityObj.on('insert:before', fedBeforeInsert) // add before insert handler
      }
   }
 })
  

# enterConnectionContext  --> ()

Fires (by native code) for an UB.App just after HTTP request context tries to get a DB connection for the first time

On this stage DB session properties, specific for a current Session can be sets. For example multi-tenancy mixin subscribes for this event and sets a ub_tenantID DB session variable value to Session.tenantID

# getAppInfo  --> (appInfoobject)

Models can modify getAppInto response by mutate an appInto parameter of getAppInfo App event.

WARNING getAppInto endpoint is called often, so event handler should be as fast as possible. Since getAppInto endpoint is called for non-authorised users, response MUST NOT contain a sensitive data

  
      App.on('getAppInfo', function(appInfo) {
  const serverConfig = App.serverConfig
  const DSTU = serverConfig.security && serverConfig.security.dstu
  appInfo.trafficEncryption = DSTU ? DSTU.trafficEncryption : false
})
  
Arguments: