release/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;

    //noinspection JSUndeclaredVariable
    /**
     * Information about current executable. Accessible via global variable `process`
     * @type {Process}
     * @global
     */
    process = nativeProcess;
    /**
     * Current working directory
     * @return {string|String}
     */
    process.cwd = function () {
        return nativeProcess.startupPath;
    };
    /**
     * List of loaded via `require` modules
     * @private
     * @type {Array<string>}
     */
    process.moduleLoadList = [];

    /**
     * This function is just to be compatible with node.js
     * @param {Function} cb Callback (called immediately in UB)
     */
    process.nextTick = function(cb){
        cb();
    };
    /**
     * The main executable full path (excluding .exe file name)
     * @type {String}
     * @readonly
     */
    process.execPath = process.binPath;

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



        var Module = NativeModule.require('module');
        Module.call(global, ['.']);
        process.mainModule = global;

        //noinspection JSUndeclaredVariable
        /**
         * Load a module. Acts like a <a href="http://nodejs.org/api/modules.html">Node JS</a> require, with 4 difference:
         *
         *   - **Core modules** list is: `["fs", "util", "path", "assert", "module", "console" and "events"]` - this modules always loaded from `bin\modules` folder
         *   - In case of `moduleName` is **RELATIVE** (not start from `./` `../` or `X:\` where X is drive letter) lookup order is:
         *      - `currentModulePath + \modules`
         *      - `currentModulePath + \node_modules`
         *      - `bin\modules`
         *      - `process.cwd()`
         *   - in case UnityBase run in production (`!process.isDebug`) and package.json contain `main_min` entry it value will be used as a module entry point<br>
         *       Placing minimized js into `main_min` help to prevent memory overflow;
         *   - `require` know about UnityBase **models**. In **server thread** context, in case `moduleName` start from `models/ModelName` require search for module inside `ModelName.path` folder:
         *
         *          require('models/UBS/public/UBReport');
         *
         *     will search in domain config (ubConfig.json) path for `UBS` model and perform request relative to this folder, i.e. load `D:\projects\UnityBase\models\UBS\public\UBReport.js` in my case.
         *
         *  *In case you need to debug from there module is loaded set OS Environment variable*
         *  `>SET NODE_DEBUG=modules` *and restart server - require will put to debug log all information about how module are loaded.* Do not do this on production, of course :)
         *
         * @global
         * @method
         * @param {String} moduleName
         * @returns {*}
         */
        require = Module.prototype.require;

        //noinspection JSUndeclaredVariable
        /**
         * Instance of lodash library. Follow <a href="http://lodash.com/docs">this link</a> to see documentation on the official site.
         * @global
         * @type {lodash}
         */
        _ = require('lodash');

    	// Used in Pdf for binary data in fonts. please do not remove this definition
        var base64 = require('base64.js');
        //noinspection JSUndeclaredVariable
        /**
         * The same as <a href="https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64.atob">window.atob</a> in browser. Warning! slow implementation.
         * @method atob
         */
        atob = base64.atob;
        //noinspection JSUndeclaredVariable
        /**
         * The same as <a href="https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64.btoa">window.btoa</a> in browser. Warning! slow implementation.
         * @method btoa
         */
        btoa = base64.btoa;

        var EventEmitter = NativeModule.require('events').EventEmitter;
        // add EventEmitter to process object
        EventEmitter.call(process);
        var util = NativeModule.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 = function UBAbort(message) {
            this.name = 'UBAbort';
            this.code = 'UBAbort';
            this.message = message || 'UBAbortError';
            this.stack = (new Error()).stack;
        };
        UB.UBAbort.prototype = new Error();
        UB.UBAbort.prototype.constructor = UB.UBAbort;

        var repositoryConstructor = require('Repository'); // for backward compatibility with UB 1.7
        /**
         * Create new instance of {@link ServerRepository}
         *
         * @param {String} entityName
         * @param {Object} [cfg]
		 * @param {UBConnection} [connection] Pass in case of remote UnityBase server connection.
         * @returns {ServerRepository}
         */
        UB.Repository = function (entityName, cfg, connection) {
			connection = connection || global.conn;
			if (connection){ 
				return new repositoryConstructor(connection, entityName, cfg);
			} else {
				return new repositoryConstructor(null, entityName, cfg);
			}	
        };

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

                    //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('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);
                    }
                };

                var argv = require('cmd/argv');

                /**
                 * Server configuration (result of argv.getServerConfiguration() execution)
                 * In this case developer are responsible to fill response object, otherwise HTTP 400 is returned.
                 * @type {Object}
                 * @memberOf App
                 */
                App.serverConfig = undefined;
                try {
                    App.serverConfig = argv.getServerConfiguration();
                } catch (e){
		  console.error(e);
                }

                /**
                 * @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('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();
                    result = call();
                    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);
                    result = call();
                    sessionBinding.switchToOriginal();
                    return result;
                };
            }

            var _ubNotifierInstance = process.isWebSocketEnabled ? undefined : null;
            var JsonMessagesProtocol = require('WebSockets').protocols.JsonMessages;

            /**
             * Return a instance of {@link JsonMessagesProtocol} named `ubNotifier` for Server<->Client communication using WebSocket
             *
             * In case `ubNotifier` protocol is not registered during WebSocket thread initialization
             * or not configured in config - will return `null`
             *
             * Returned {@link JsonMessagesProtocol} instance methods is limited
             * by {@link WSProtocol#getUserSessions}, {@link WSProtocol#sendTo} and {@link WSProtocol#broadcast}
             * 
             * See {@tutorial web_sockets.md } for detailed descripiuon
             *
             * @return {JsonMessagesProtocol}
             */
            UB.getWSNotifier = function(){
                if (_ubNotifierInstance || _ubNotifierInstance===null)
                    return _ubNotifierInstance;

                _ubNotifierInstance = new JsonMessagesProtocol('ubNotifier');
                return _ubNotifierInstance;
            }
        } else {
            /*
             * Create connection to server and expose it as a global `conn` variable.. Login operation is performed on first request what need auth.
             * FOR A GUI SERVER ONLY
             * @return {Boolean} success or not
             */
            UB.autologon = function(serverURL, user, pwd){
                var
                    UBConnection = require('UBConnection'), conn;
                function createRequestAuthParams (){
                    return function() { return {login: user, password: pwd} };
                }
                if (!/http:\/\/|https:\/\//.test(serverURL)) serverURL = 'http://'+serverURL; //fix URL parse
                conn = global.conn = new UBConnection({
                    URL: serverURL
                });
                conn.onRequestAuthParams = createRequestAuthParams();
                return true;
            };
        }
    }

    /**
     * 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) {
        var args = _.toArray(arguments).slice(1);
        return stringToFormat.replace(FORMAT_RE, function(m, i) {
            return args[i];
        });
    };



    function evalScript(name) {
        var Module = NativeModule.require('module');
        var path = NativeModule.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);
    }


    function NativeModule(id) {
        this.filename = id + '.js';
        this.id = id;
        this.exports = {};
        this.loaded = false;
    }

    NativeModule._source = {
        fs: relToAbs(process.binPath, '.\\modules\\fs.js'),
        util: relToAbs(process.binPath, '.\\modules\\util.js'),
        path: relToAbs(process.binPath, '.\\modules\\path.js'),
        assert: relToAbs(process.binPath, '.\\modules\\assert.js'),
        module: relToAbs(process.binPath, '.\\modules\\module.js'),
        console: relToAbs(process.binPath, '.\\modules\\console.js'),
        events:  relToAbs(process.binPath, '.\\modules\\events.js')
    };

    NativeModule._cache = {};

    NativeModule.require = function (id) {
        if (id == 'native_module') {
            return NativeModule;
        }

        var cached = NativeModule.getCached(id);
        if (cached) {
            return cached.exports;
        }

        if (!NativeModule.exists(id)) {
            throw new Error('No such native module ' + id);
        }

        process.moduleLoadList.push('NativeModule ' + id);

        var nativeModule = new NativeModule(id);

        nativeModule.cache();
        nativeModule.compile();

        return nativeModule.exports;
    };

    NativeModule.getCached = function (id) {
        if (NativeModule._cache.hasOwnProperty(id)) {
            return NativeModule._cache[id]
        } else {
            return null;
        }
    };

    NativeModule.exists = function (id) {
        return NativeModule._source.hasOwnProperty(id);
    };

    NativeModule.getSource = function (id) {
        return loadFile(NativeModule._source[id]);
    };

    NativeModule.wrap = function (script) {
        return NativeModule.wrapper[0] + script + NativeModule.wrapper[1];
    };

    NativeModule.wrapper = [
        '(function (exports, require, module, __filename, __dirname) { ', '\n});'
    ];

    NativeModule.prototype.compile = function () {
        var source = NativeModule.getSource(this.id);
        source = NativeModule.wrap(source);

        var fn = runInThisContext(source, this.filename, true);
        fn(this.exports, NativeModule.require, this, this.filename);

        this.loaded = true;
    };

    NativeModule.prototype.cache = function () {
        NativeModule._cache[this.id] = this;
    };

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


})(_process);