Source: lib/yexpert-js.js

/*

!----------------------------------------------------------------------------!
!                                                                            !
! Yexpert : (your) Système Expert sous Mumps GT.M et GNU/Linux               !
! Copyright (C) 2001-2015 by Hamid LOUAKED (HL).                             !
!                                                                            !
!----------------------------------------------------------------------------!

*/

var build = require('./build');
var sessions = require('ewd-session');
var runRPC = require('yexpert-rpc/lib/proto/runRPC');
//var httpHandlers = require('yexpert-rpc/lib/proto/httpHandlers');
var fs = require('fs');
var os = require('os');

/**
 * Module yexpert-js.
 * @module lib/yexper-js
 */

/** Fonction yexpert-js. */
module.exports = {

  /** Initialisation. */
  init: function() {
    var types = [
      'qoper8-stats',
      'qoper8-getStats',
      'getMasterProcessDetails',
      'getWorkerDetails',
      'getPoolSize'
    ];
    if (this.dontLog) this.dontLog(types);
  },

  /** Services autorisés. */
  servicesAllowed: {
    'ewd-react-tools': true
  },

  /** Gestionnaire d'évènemments. */
  handlers: {

    loginRpc: function(messageObj, session, send, finished) {

      if (session.authenticated) {
        finished({error: 'Vous êtes déjà connectés à Yexpert-JS'});
        return;
      }

      var accessCode = messageObj.params.ac;
      var verifyCode = messageObj.params.vc;
      //console.log("-----messageObj.params.ac: " + JSON.stringify(messageObj.params.ac));
      //console.log("-----messageObj.params.vc: " + JSON.stringify(messageObj.params.vc));

      if (accessCode === '') {
        finished({error: "Vous devez entrer un code d'accès"});
        return;
      }
      if (verifyCode === '') {
        finished({error: 'Vous devez saisir un code de validation'});
        return;
      }

      if (!this.db.symbolTable) this.db.symbolTable = sessions.symbolTable(this.db);

      var params = {
        rpcName: 'RPCUSR ETABLIR CONNEXION'
      };

      // N'enregistrez pas encore la table des symboles!
      var response = runRPC.call(this, params, session, false);
      //console.log("-----response: " + JSON.stringify(response));
  
      params = {
        rpcName: 'RPCUSR VERIFIER CODE',
        rpcArgs: [{
          type: 'LITERAL',
          value: accessCode + ';' + verifyCode
        }],
      };

      var response = runRPC.call(this, params, session, false);
      console.log('login response: ' + JSON.stringify(response));
      var values = response.value;
      var duz = values[0];
      var err = values[3]
      //console.log("-----err: " + JSON.stringify(err));
      if (duz.toString() === '0' && err !== '') {
        finished({error: err});
      }
      else {
        // Connecté avec succès
        // Enregistrer la table des symboles à la session ...
        ok = this.db.symbolTable.save(session);

        // Nettoyer le processus de back-end Cache / GT.M:
        ok = this.db.symbolTable.clear();

        // ** important ! Signaler l'utilisateur comme authentifié pour empêcher l'accès non autorisé aux RPC par un utilisateur avant de se connecter
        session.authenticated = true;

        // Réponse de retour
        var greeting = values[7];
        var pieces = greeting.split(' ');
        pieces = pieces.splice(2, pieces.length);
        var displayName = pieces.join(' ');

        var results = {
          displayName: displayName,
          greeting: greeting,
          lastSignon: values[8],
          messages: values.splice(8, values.length)
        };
        // Notez que nous ne renvoyons pas le DUZ!
        //finished(results);
        session.timeout = 20 * 60;
        session.authenticated = true;
        finished({ok: true});    
      }
    },

    rpc: function(messageObj, session, send, finished) {

      if (!this.db.symbolTable) this.db.symbolTable = sessions.symbolTable(this.db);

      var rpcName = messageObj.params.rpcName;
      if (rpcName === 'RPCUSR ETABLIR CONNEXION' || rpcName === 'RPCUSR VERIFIER CODE') {
        // Vous devez utiliser le gestionnaire de connexion pour ces RPC
        finished({error: rpcName + ' RPC ne peut pas être invoqué directement'});
        return;
      }
      var manageSymbolTable = false;      
      if (rpcName !== 'RPCUSR MESSAGE INTRO') {
        if (!session.authenticated) {
          finished({error: "Vous n'avez pas été authentifié sur Yexpert-JS"});
          return;
        }
        manageSymbolTable = true;
      }

      var results = runRPC.call(this, messageObj.params, session, manageSymbolTable);

      if (manageSymbolTable) {
         // Nettoyer le processus de back-end Cache / GT.M :
         this.db.symbolTable.clearclear();
      }
      finished(results);
    },

    exemple: function(messageObj, session, send, finished) {

      var node = {
        global: 'irelay',
        subscripts: ['a'],
        data: 'hello world'
      };
      this.db.set(node);

      var func2 = this.db.function({function: 'ZGBLDIR^%GTM', arguments: ['DMO']});

      var dir = this.db.global_directory();
      finished(dir);
      //finished({ok: 'example executed!'});
    },

    /**
     * Se déplacer sur une partition.
     * @constructor
     * @param {object} messageObj - Message.
     * @param {number} session - Session.
     * @param {string} send - Send.
     * @param {object} finished - Message de retour de la fonction.
     */
    cdNameSpace: function(messageObj, session, send, finished) {
      var fn = {};
      var path = '/home/' + process.env['instance'] + '/partitions/' + messageObj.params.namespace.toLowerCase() + '/globals/';
      if (messageObj.params.namespace != '') {
        if (fs.existsSync(path + messageObj.params.namespace.toUpperCase() + '.gld')) {
          var fn = this.db.function({function: 'ZGBLDIR^%GTM', arguments: [messageObj.params.namespace.toUpperCase()]});
        }
      }
      finished(fn);    
    },

    /**
     * Obtenir la liste des noms des partitions installées.
     * @constructor
     * @param {object} messageObj - Message.
     * @param {number} session - Session.
     * @param {string} send - Send.
     * @param {object} finished - Liste des noms des partitions installées sous la forme ["DMO", "YXP"].
     */
    getNameSpace: function(messageObj, session, send, finished) {
      var path = '/home/' + process.env['instance'] + '/partitions/';
      fs.readdir(path, function(err, items) {
        if (err) {
          finished(err);
        }
        finished(items);
      });
    },

    getQui: function(messageObj, session, send, finished) {
      var node = {
        global: 'QUI',
        subscripts: []
      };
      var result = this.db.get(node);
      //console.log("-----getQui: " + JSON.stringify(result.data));
      finished({qui: result.data});
    },

    setQui: function(messageObj, session, send, finished) {
      if (messageObj.params.partition && messageObj.params.partition !== '') {
        var node = {
          global: 'QUI',
          subscripts: [],
          data: messageObj.params.partition
        };
        var result = this.db.set(node);
        //console.log("-----setQui: " + JSON.stringify(result.data));
        finished({ok: true});    
      }
      else {
        finished({error: 'Invalid partition'});
      }
    },

    login: function(messageObj, session, send, finished) {

      if (messageObj.params.password === this.userDefined.config.managementPassword) {
        session.timeout = 20 * 60;
        session.authenticated = true;
        finished({ok: true});    
      }
      else {
        finished({error: 'Invalid login attempt'});
      }
      return;
    },

    getServerName: function(messageObj, session, send, finished) {
      var serverName = '';
      if (this.userDefined.config && this.userDefined.config.serverName) serverName = this.userDefined.config.serverName;
      finished({serverName: serverName});
    },

    getBuildDetails: function(messageObj, session, send, finished) {
      var node = {
        global: 'INCONNE',
        subscripts: ['VERSION']
      };
      var rep = this.db.get(node);
      if (session.authenticated) {
        var buildDetails = {
          nodejsBuild: process.version,
          dbInterface: this.db.version(),
          qoper8Build: this.build,
          docStoreBuild: this.documentStore.build,
          xpressBuild: this.xpress.build,
          yexpertmVersion: rep.data,
          yexpertjsBuild: build
        };
        //console.log("-----buildDetails: " + JSON.stringify(buildDetails));
        if (this.userDefined.config && this.userDefined.config.qxBuild) buildDetails.qxBuild = this.userDefined.config.qxBuild;
        finished(buildDetails);
      }
      else {
        finished({error: 'Unauthenticated'});
      }
    },

    getMasterProcessDetails: function(messageObj, session, send, finished) {
      if (session.authenticated) {
        //var details = {};
        //if (this.userDefined.config && this.userDefined.config.masterProcessPid) details.pid = this.userDefined.config.masterProcessPid;
        //finished(details);
        finished({ok: true});
      }
      else {
        finished({error: 'Unauthenticated'});
      }
    },

    stopMasterProcess: function(messageObj, session, send, finished) {
      if (session.authenticated) {
        send({displayButton: true});
        finished({closeSocket: true});
      }
      else {
        finished({error: 'Unauthenticated'});
      }
    },

    getWorkerDetails: function(messageObj, session, send, finished) {
      if (session.authenticated) {
        finished({ok: true});
      }
      else {
        finished({error: 'Unauthenticated'});
      }
    },

    stopWorkerProcess: function(messageObj, session, send, finished) {
      finished({pid: messageObj.params.pid});
    },

    setPoolSize: function(messageObj, session, send, finished) {
      finished({poolSize: messageObj.params.poolSize});
    },

    getPoolSize: function(messageObj, session, send, finished) {
      finished({ok: true});
    },

    getGlobalDirectory: function(messageObj, session, send, finished) {
      var dir = this.db.global_directory();
      finished(dir);
    },

    getNextSubscripts: function(messageObj, session, send, finished) {
      var subscripts = messageObj.params.path.split('.');
      var global = subscripts.shift();
      var glo = new this.documentStore.DocumentNode(global, subscripts);
      var data = {};
      glo.forEachChild(function(name, node) {
        if (node.hasChildren) {
          data[name] = messageObj.params.expandText
	 }
	 else data[name] = node.value;
      });
      finished(data);
    },

    getSessions: function(messageObj, session, send, finished) {
      var activeSessions = this.sessions.active();
      var sessions = [];
      var disabled;
      activeSessions.forEach(function(ewdSession) {
        disabled = false;
        if (ewdSession.id.toString() === session.id.toString()) disabled = true;
        sessions.push({
          id: ewdSession.id,
          token: ewdSession.token,
          application: ewdSession.application,
          expiry: ewdSession.expiryTime,
          disabled: disabled
        });
      });
      finished(sessions);
    },

    stopSession: function(messageObj, session, send, finished) {
      var ewdSession = this.sessions.byToken(messageObj.params.token);
      if (ewdSession) ewdSession.delete();
      finished({ok: true});
    },

    showSession: function(messageObj, session, send, finished) {
      var token = messageObj.params.token;
      var ewdSession = this.sessions.byToken(token);
      if (ewdSession) {
        var data = {};
        var expandText = ' -->';
        ewdSession.data.forEachChild(function(name, childNode) {
          data[name] = expandText;
          if (childNode.hasValue) data[name] = childNode.value;
          if (name === 'ewd_symbolTable') data[name] = 'Mumps Symbol Table Data';
        });
        finished({
          token: token,
          id: ewdSession.id,
          data: data
        });
      }
      else {
        finished({
          token: token,
          error: 'Session no longer exists'
        });
      }
    },

    getSessionSubscripts: function(messageObj, session, send, finished) {
      var subs = messageObj.params.path.split('.');
      var token = messageObj.params.token;
      var ewdSession = this.sessions.byToken(token);
      if (ewdSession) {
        var documentName = ewdSession.documentName;
        var subscripts = ewdSession.data._node.subscripts.concat(subs);
        var doc = new this.documentStore.DocumentNode(documentName, subscripts);
        var data = {};
        doc.forEachChild(function(name, childNode) {
          data[name] = messageObj.params.expandText;
          if (childNode.hasValue) data[name] = childNode.value;
        });
        finished({
          data: data
        });
      }
      else {
        finished({error: 'Session no longer exists'});
      }
    }
  },



  workerResponseHandlers: {
    // allow interception by master process to augment / process the request after authentication in worker

    getMasterProcessDetails: function(message) {
      var stats = this.getStats();
      return {
        pid: process.pid,
        startTime: new Date(this.startTime).toLocaleString(),
        upTime: stats.uptime,
        memory: stats.memory
      }
    },

    getWorkerDetails: function(message, send) {
      // use special handler function for getting stats for master and workers
      var that = this;
      this.handleStats(function(messageObj) {
        var resultObj = {
          type: 'getWorkerDetails',
          results: messageObj.worker
        };
        send(resultObj);
      });
      return;
    },

    stopMasterProcess: function(message) {

      if (message.displayButton) return message ;

      // delay slightly to allow response to be sent to browser
      var that = this;
      setTimeout(function() {
        that.stop();
      }, 2000);
      //return {disconnect: true, error: 'EWD AppRunner has been shut down'};
      return {ok: true};
    },

    stopWorkerProcess: function(message) {
      this.stopWorker(message.pid);
      return {pid: message.pid};
    },

    getPoolSize: function(message) {
      return {poolSize: this.worker.poolSize};
    },

    setPoolSize: function(message) {
      this.setWorkerPoolSize(message.poolSize);
      return {ok: true};
    }

  }

};