ubjs/packages/stubs/UB.js

/*
 UnityBase startup script
 this script initializes working thread JavaScript context and is called by server fo each thread.
 In case UB runs in batch mode, script is called once - for main thread only
 */

/**
 * The UB namespace (global object) encapsulates some classes, singletons, and utility methods provided by UnityBase server.
 * @namespace UB
 */
UB = {};
/**
 * If we are in UnityBase server scripting (both -f or server thread) this property is true, if in browser - undefined or false.
 * Use it for check execution context in scripts, shared between client & server.
 * To check we are in server thread use process.isServer. 
 * @readonly
 */
UB.isServer = true; //Object.defineProperty(UB, 'isServer', {enumerable: true, value: true} );
(function (nativeProcess) {
    this.global = this;

    process = nativeProcess;

    // process & module function is documented inside _UBCommonGlobals.js
    function startup() {
        let require = global.require
	/**
         * Put something to log with log levels depending on method
         * @global
         * @type {Console}
         */
        console = require('console');

        let module = require('module')
        let path = require('path')

        
        process.emitWarning = console.warn;


        var EventEmitter = require('events').EventEmitter;
        // add EventEmitter to process object
        EventEmitter.call(process);
        var util = require('util');
        util._extend(process, EventEmitter.prototype);

       /**
        * Server-side Abort exception. To be used in server-side logic in case of HANDLED
        * exception. This errors logged using "Error" log level to prevent unnecessary
        * EXC log entries.
        *
        *       // UB client will show message inside <<<>>> to user (and translate it using UB.i18n)
        *       throw new UB.UBAbort('<<<textToDisplayForClient>>>');
        *       //for API methods we do not need <<<>>>
        *       throw new UB.UBAbort('wrongParameters');
        *
        * @param {String} [message] Message
        * @extends {Error}
        */
        //UB.UBAbort = Error
	
	//For SM<=45 we use a "exception class" inherit pattern below, but it stop working in SM52, so fallback to Error
	UB.UBAbort = function UBAbort(message) {
            this.name = 'UBAbort';
            this.code = 'UBAbort';
            this.message = message || 'UBAbortError';
            if (Error.captureStackTrace) {
    		// Chrome and NodeJS
    		Error.captureStackTrace(this, stackStartFunction);
  	    } else {
     		// FF, IE 10+ and Safari 6+. Fallback for others
     		let tmp_stack = (new Error).stack.split("\n").slice(1),
         	re = /^(.*?)@(.*?):(.*?)$/.exec(tmp_stack[1]); //[undef, undef, this.fileName, this.lineNumber] = re
     		this.fileName = re[2];
     		this.lineNumber = re[3];
     		this.stack = tmp_stack.join("\n");
 	    }
            // originat FF version:
	    // this.stack = (new Error()).stack;
        };
        UB.UBAbort.prototype = Object.create(Error.prototype) // new Error();
        //UB.UBAbort.prototype = new Error();
        UB.UBAbort.prototype.constructor = UB.UBAbort;
        
        if (process.isServer || process.isWebSocketServer){
            if (process.isServer) {

                // add EventEmitter to Session object
                EventEmitter.call(Session);
                util._extend(Session, EventEmitter.prototype);

                /**
                 * Called by UB.exe in server thread during Domain initialization just after all scopes for entity is created but before entities modules (entityName.js) is evaluated
                 * @private
                 */
                UB.initEventEmitter = function(){
                    var
                        ettCnt = App.domain.count, i, n, obj;
                    var __preventDefault = false;

                    //add eventEmitter to application object
                    obj = App;
                    EventEmitter.call(obj);
                    util._extend(obj, EventEmitter.prototype);
                    App.emitWithPrevent = function(type, req, resp){
                        __preventDefault = false;
                        this.emit(type, req, resp);
                        return __preventDefault;
                    };
                    /**
                     * 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.
                     * @memberOf App
                     */
                    App.preventDefault = function(){
                        __preventDefault = true;
                    };

                    App.launchEndpoint = function(endpointName, req, resp){
                        __preventDefault = false;
                        this.emit(endpointName + ':before', req, resp);
                        if (!__preventDefault) {
                            appBinding.endpoints[endpointName](req, resp)
                            this.emit(endpointName + ':after', req, resp);
                        }
                    }

                    //add eventEmitter to all entities
                    for(i=0; i<ettCnt; i++){
                        //console.log(App.domain.items[i].name);
                        n = App.domain.items[i].name;
                        if (obj = global[n]){
                            // add EventEmitter to entity scope object
                            EventEmitter.call(obj);
                            util._extend(obj, EventEmitter.prototype);
                        }
                    }
                };

                var appBinding = process.binding('ub_app');
                /**
                 * Register a server endpoint. By default access to endpoint require authentication
                 * @example
                 *
                 * // 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);
                 *
                 * @param {String} endpointName
                 * @param {Function} handler
                 * @param {boolean} [requireAuthentication=true]
                 * @memberOf App
                 */
                App.registerEndpoint = function(endpointName, handler, requireAuthentication) {
                    if (!appBinding.endpoints[endpointName]) {
                        appBinding.endpoints[endpointName] = handler;
                        return appBinding.registerEndpoint(endpointName, requireAuthentication === undefined ? true : requireAuthentication);
                    }
                };

                /**
                 * @param {String} methodName
                 * @method addAppLevelMethod
                 * @deprecated Use {@link App.registerEndpoint} instead
                 * @memberOf App
                 */
                App.addAppLevelMethod = function(methodName) {
                    if (!appBinding.endpoints[methodName]) {
                        appBinding.endpoints[methodName] = global[methodName];
                        return appBinding.registerEndpoint(methodName, true);
                    }
                };
                /**
                 * @param {String} methodName
                 * @method serviceMethodByPassAuthentication
                 * @deprecated Use {@link App.registerEndpoint} instead
                 * @memberOf App
                 */
                App.serviceMethodByPassAuthentication  =  function(methodName){
                    return appBinding.setEndpointByPassAuthentication(methodName);
                };

                var sessionBinding = process.binding('ub_session');
                /**
                 * Create new session for userID
                 * @deprecated use runAsUser instead this
                 * @method
                 * @param {Number} userID ID of  user
                 * @param {String} [secret] secret word. If defined then session secretWord is `JSON.parse(returns).result+secret`
                 * @returns {String} JSON string like answer on auth request
                 */
                Session.setUser = sessionBinding.switchUser;
                /**
                 * Call function as admin.
                 * Built-in "always alive"(newer expired) `admin` session is always created when the application starts,
                 * so this is very cheap method - it will not trigger Session.login event every time context is switched (Session.setUser and Session.runAsUser does)
                 * Can be used in scheduled tasks, not-authorized methods, etc. to obtain a `admin` Session context
                 * @param {Function} call Function to be called in admin context
                 * @returns {*}
                 */
                Session.runAsAdmin = function(call){
                    var result;
                    sessionBinding.switchToAdmin();
                    try {
                        result = call();
                    } finally {
                        sessionBinding.switchToOriginal();
                    }
                    return result;
                };
                /**
                 * Call function as custom user.
                 * New session will be created. Will fire `login` event.
                 * @param userID ID of  user
                 * @param call Function to be called in user's session.
                 * @returns {*}
                 */
                Session.runAsUser = function(userID, call){
                    var result;
                    sessionBinding.switchUser(userID);
                    try {
                        result = call();
                    } finally {
                        sessionBinding.switchToOriginal();
                    }
                    return result;
                };
            }
        }
    }

    /**
     * Creates namespaces to be used for scoping variables and classes so that they are not global.
     *
     *     UB.ns('DOC.Report');
     *     DOC.Report.myReport = function() { ... };
     *
     * @param {String} namespacePath
     * @return {Object} The namespace object.
     */
    UB.ns = function (namespacePath) {
        var root = global, parts, part, j, subLn;

        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;
    };

    var FORMAT_RE = /\{(\d+)}/g;
    /**
     * Allows you to define a tokenized string and pass an arbitrary number of arguments to replace the tokens.  Each
     * token must be unique, and must increment in the format {0}, {1}, etc.
     * @example
     *
     *     var s = UB.format('{1}/lang-{0}.js', 'en', 'locale');
     *     // s now contains the string: ''locale/lang-en.js''
     *
     * @param {String} stringToFormat The string to be formatted.
     * @param {...*} values The values to replace tokens `{0}`, `{1}`, etc in order.
     * @return {String} The formatted string.
     */
    UB.format = function(stringToFormat, ...values) {
        return stringToFormat.replace(FORMAT_RE, function(m, i) {
            return values[i];
        });
    };



    function evalScript(name) {
        var Module = require('module');
        var path = require('path');
        var cwd = process.cwd();

        var module = new Module(name);
        module.filename = path.join(cwd, name);
        module.paths = Module._nodeModulePaths(cwd);
        var script = process._eval;
        if (!Module._contextLoad) {
            var body = script;
            script = 'global.__filename = ' + JSON.stringify(name) + ';\n' + 'global.exports = exports;\n' + 'global.module = module;\n' + 'global.__dirname = __dirname;\n' + 'global.require = require;\n' + 'return require("vm").runInThisContext(' + JSON.stringify(body) + ', ' + JSON.stringify(name) + ', true);\n';
        }
        var result = module._compile(script, name + '-wrapper');
        if (process._print_eval) console.log(result);
    }

    startup();
    /*    if (!process.isServer){
     toLog('!!!!not server - run startup')
     var Module = NativeModule.require('module');
     Module._load(process.argv[1], null, true);
     }*/


})(_process);