      1 /*
      2     Copyright 2008-2024
      3         Matthias Ehmann,
      4         Michael Gerhaeuser,
      5         Carsten Miller,
      6         Bianca Valentin,
      7         Alfred Wassermann,
      8         Peter Wilfahrt
      9
     10     This file is part of JSXGraph.
     11
     12     JSXGraph is free software dual licensed under the GNU LGPL or MIT License.
     13
     14     You can redistribute it and/or modify it under the terms of the
     15
     16       * GNU Lesser General Public License as published by
     17         the Free Software Foundation, either version 3 of the License, or
     18         (at your option) any later version
     19       OR
     20       * MIT License: https://github.com/jsxgraph/jsxgraph/blob/master/LICENSE.MIT
     21
     22     JSXGraph is distributed in the hope that it will be useful,
     23     but WITHOUT ANY WARRANTY; without even the implied warranty of
     24     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     25     GNU Lesser General Public License for more details.
     26
     27     You should have received a copy of the GNU Lesser General Public License and
     28     the MIT License along with JSXGraph. If not, see <https://www.gnu.org/licenses/>
     29     and <https://opensource.org/licenses/MIT/>.
     30  */
     31
     32 /*global JXG: true, define: true, escape:true, window:true, ActiveXObject:true, XMLHttpRequest:true*/
     33 /*jslint nomen: true, plusplus: true*/
     34
     35 /**
     36  * @fileoverview The JXG.Server is a wrapper for a smoother integration of server side calculations. on the
     37  * server side a python plugin system is used.
     38  */
     39
     40 import JXG from "../jxg.js";
     41 import Zip from "../utils/zip.js";
     42 import Base64 from "../utils/base64.js";
     43 import Type from "../utils/type.js";
     44
     45 /**
     46  * @namespace
     47  * JXG.Server namespace holding functions to load JXG server modules.
     48  */
     49 JXG.Server = {
     50     /**
     51      * This is where all of a module's handlers are accessed from. If you're loading a module named JXGModule which
     52      * provides a handler called ImaHandler, then this handler can be called by invoking JXG.Server.modules.JXGModule.ImaHandler().
     53      * @namespace
     54      */
     55     modules: {},
     56
     57     /**
     58      * Stores all asynchronous calls to server which aren't finished yet.
     59      * @private
     60      */
     61     runningCalls: {},
     62
     63     /**
     64      * Handles errors, just a default implementation, can be overwritten by you, if you want to handle errors by yourself.
     65      * @param {object} data An object holding a field of type string named message handling the error described in the message string.
     66      */
     67     handleError: function (data) {
     68         JXG.debug("error occured, server says: " + data.message);
     69     },
     70
     71     /**
     72      * The main method of JXG.Server. Actually makes the calls to the server and parses the feedback.
     73      * @param {String} action Can be 'load' or 'exec'.
     74      * @param {function} callback Function pointer or anonymous function which takes as it's only argument an
     75      * object containing the data from the server. The fields of this object depend on the reply of the server
     76      * module. See the correspondings server module readme.
     77      * @param {Object} data What is to be sent to the server.
     78      * @param {Boolean} sync If the call should be synchronous or not.
     79      */
     80     callServer: function (action, callback, data, sync) {
     81         var fileurl, passdata, AJAX,// params, k,
     82             id, dataJSONStr;
     83
     84         sync = sync || false;
     85
     86         // params = "";
     87         // for (k in data) {
     88         //     if (data.hasOwnProperty(k)) {
     89         //         params += "&" + escape(k) + "=" + escape(data[k]);
     90         //     }
     91         // }
     92
     93         dataJSONStr = Type.toJSON(data);
     94
     95         // generate id
     96         do {
     97             id = action + Math.floor(Math.random() * 4096);
     98         } while (Type.exists(this.runningCalls[id]));
     99
    100         // store information about the calls
    101         this.runningCalls[id] = { action: action };
    102         if (Type.exists(data.module)) {
    103             this.runningCalls[id].module = data.module;
    104         }
    105
    106         fileurl = JXG.serverBase + "JXGServer.py";
    107         passdata =
    108             "action=" +
    109             escape(action) +
    110             "&id=" +
    111             id +
    112             "&dataJSON=" +
    113             escape(Base64.encode(dataJSONStr));
    114
    115         this.cbp = function (d) {
    116             /*jslint evil:true*/
    117             var str, data, tmp, inject, paramlist, id, i, j;
    118
    119             str = new Zip.Unzip(Base64.decodeAsArray(d)).unzip();
    120             if (Type.isArray(str) && str.length > 0) {
    121                 str = str[0][0];
    122             }
    123
    124             if (!Type.exists(str)) {
    125                 return;
    126             }
    127
    128             data =
    129                 window.JSON && window.JSON.parse
    130                     ? window.JSON.parse(str)
    131                     : new Function("return " + str)();
    132
    133             if (data.type === "error") {
    134                 this.handleError(data);
    135             } else if (data.type === "response") {
    136                 id = data.id;
    137
    138                 // inject fields
    139                 for (i = 0; i < data.fields.length; i++) {
    140                     tmp = data.fields[i];
    141                     inject =
    142                         tmp.namespace +
    143                         (typeof new Function("return " + tmp.namespace)() === "object"
    144                             ? "."
    145                             : ".prototype.") +
    146                         tmp.name +
    147                         " = " +
    148                         tmp.value;
    149                     new Function(inject)();
    150                 }
    151
    152                 // inject handlers
    153                 for (i = 0; i < data.handler.length; i++) {
    154                     tmp = data.handler[i];
    155                     paramlist = [];
    156
    157                     for (j = 0; j < tmp.parameters.length; j++) {
    158                         paramlist[j] = '"' + tmp.parameters[j] + '": ' + tmp.parameters[j];
    159                     }
    160                     // insert subnamespace named after module.
    161                     inject =
    162                         "if(typeof JXG.Server.modules." +
    163                         this.runningCalls[id].module +
    164                         ' == "undefined")' +
    165                         "JXG.Server.modules." +
    166                         this.runningCalls[id].module +
    167                         " = {};";
    168
    169                     // insert callback method which fetches and uses the server's data for calculation in JavaScript
    170                     inject +=
    171                         "JXG.Server.modules." +
    172                         this.runningCalls[id].module +
    173                         "." +
    174                         tmp.name +
    175                         "_cb = " +
    176                         tmp.callback +
    177                         ";";
    178
    179                     // insert handler as JXG.Server.modules.<module name>.<handler name>
    180                     inject +=
    181                         "JXG.Server.modules." +
    182                         this.runningCalls[id].module +
    183                         "." +
    184                         tmp.name +
    185                         " = function (" +
    186                         tmp.parameters.join(",") +
    187                         ", __JXGSERVER_CB__, __JXGSERVER_SYNC) {" +
    188                         'if(typeof __JXGSERVER_CB__ == "undefined") __JXGSERVER_CB__ = JXG.Server.modules.' +
    189                         this.runningCalls[id].module +
    190                         "." +
    191                         tmp.name +
    192                         "_cb;" +
    193                         "var __JXGSERVER_PAR__ = {" +
    194                         paramlist.join(",") +
    195                         ', "module": "' +
    196                         this.runningCalls[id].module +
    197                         '", "handler": "' +
    198                         tmp.name +
    199                         '" };' +
    200                         'JXG.Server.callServer("exec", __JXGSERVER_CB__, __JXGSERVER_PAR__, __JXGSERVER_SYNC);' +
    201                         "};";
    202                     new Function(inject)();
    203                 }
    204
    205                 delete this.runningCalls[id];
    206
    207                 // handle data
    208                 callback(data.data);
    209             }
    210         };
    211
    212         // bind cbp callback method to JXG.Server to get access to JXG.Server fields from within cpb
    213         this.cb = JXG.bind(this.cbp, this);
    214
    215         // We are using our own XMLHttpRequest object in here because of a/sync and POST
    216         if (window.XMLHttpRequest) {
    217             AJAX = new XMLHttpRequest();
    218             AJAX.overrideMimeType("text/plain; charset=iso-8859-1");
    219         } else {
    220             AJAX = new ActiveXObject("Microsoft.XMLHTTP");
    221         }
    222         if (AJAX) {
    223             // POST is required if data sent to server is too long for a url.
    224             // some browsers/http servers don't accept long urls.
    225             AJAX.open("POST", fileurl, !sync);
    226             AJAX.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
    227
    228             if (!sync) {
    229                 // Define function to fetch data received from server
    230                 // that function returning a function is required to make this.cb known to the function.
    231                 AJAX.onreadystatechange = (function (cb) {
    232                     return function () {
    233                         if (AJAX.readyState === 4 && AJAX.status === 200) {
    234                             cb(AJAX.responseText);
    235                             return true;
    236                         }
    237                         return false;
    238                     };
    239                 })(this.cb);
    240             }
    241
    242             // send the data
    243             AJAX.send(passdata);
    244             if (sync) {
    245                 this.cb(AJAX.responseText);
    246                 return true;
    247             }
    248         }
    249
    250         return false;
    251     },
    252
    253     /**
    254      * Callback for the default action 'load'.
    255      */
    256     loadModule_cb: function (data) {
    257         var i;
    258         for (i = 0; i < data.length; i++) {
    259             JXG.debug(data[i].name + ": " + data[i].value);
    260         }
    261     },
    262
    263     /**
    264      * Loads a module from the server.
    265      * @param {string} module A string containing the module. Has to match the filename of the Python module on the server exactly including
    266      * lower and upper case letters without the file ending .py.
    267      */
    268     loadModule: function (module) {
    269         return JXG.Server.callServer(
    270             "load",
    271             JXG.Server.loadModule_cb,
    272             { module: module },
    273             true
    274         );
    275     }
    276 };
    277
    278 JXG.Server.load = JXG.Server.loadModule;
    279
    280 export default JXG.Server;
    281