1 /*
  2     Copyright 2008-2021
  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 <http://www.gnu.org/licenses/>
 29     and <http://opensource.org/licenses/MIT/>.
 30  */
 31 
 32 
 33 /*global JXG: true, define: true, escape:true, window:true, ActiveXObject:true, XMLHttpRequest:true*/
 34 /*jslint nomen: true, plusplus: true*/
 35 
 36 /* depends:
 37  jxg
 38  utils/zip
 39  utils/base64
 40  utils/type
 41  */
 42 
 43 /**
 44  * @fileoverview The JXG.Server is a wrapper for a smoother integration of server side calculations. on the
 45  * server side a python plugin system is used.
 46  */
 47 
 48 define([
 49     'jxg', 'utils/zip', 'utils/base64', 'utils/type'
 50 ], function (JXG, Zip, Base64, Type) {
 51 
 52     "use strict";
 53 
 54     /**
 55      * @namespace
 56      * JXG.Server namespace holding functions to load JXG server modules.
 57      */
 58     JXG.Server = {
 59         /**
 60          * This is where all of a module's handlers are accessed from. If you're loading a module named JXGModule which
 61          * provides a handler called ImaHandler, then this handler can be called by invoking JXG.Server.modules.JXGModule.ImaHandler().
 62          * @namespace
 63          */
 64         modules: {},
 65 
 66         /**
 67          * Stores all asynchronous calls to server which aren't finished yet.
 68          * @private
 69          */
 70         runningCalls: {},
 71 
 72         /**
 73          * Handles errors, just a default implementation, can be overwritten by you, if you want to handle errors by yourself.
 74          * @param {object} data An object holding a field of type string named message handling the error described in the message string.
 75          */
 76         handleError: function (data) {
 77             JXG.debug('error occured, server says: ' + data.message);
 78         },
 79 
 80         /**
 81          * The main method of JXG.Server. Actually makes the calls to the server and parses the feedback.
 82          * @param {String} action Can be 'load' or 'exec'.
 83          * @param {function} callback Function pointer or anonymous function which takes as it's only argument an
 84          * object containing the data from the server. The fields of this object depend on the reply of the server
 85          * module. See the correspondings server module readme.
 86          * @param {Object} data What is to be sent to the server.
 87          * @param {Boolean} sync If the call should be synchronous or not.
 88          */
 89         callServer: function (action, callback, data, sync) {
 90             var fileurl, passdata, AJAX,
 91                 params, id, dataJSONStr,
 92                 k;
 93 
 94             sync = sync || false;
 95 
 96             params = '';
 97             for (k in data) {
 98                 if (data.hasOwnProperty(k)) {
 99                     params += '&' + escape(k) + '=' + escape(data[k]);
100                 }
101             }
102 
103             dataJSONStr = Type.toJSON(data);
104 
105             // generate id
106             do {
107                 id = action + Math.floor(Math.random() * 4096);
108             } while (Type.exists(this.runningCalls[id]));
109 
110             // store information about the calls
111             this.runningCalls[id] = {action: action};
112             if (Type.exists(data.module)) {
113                 this.runningCalls[id].module = data.module;
114             }
115 
116             fileurl = JXG.serverBase + 'JXGServer.py';
117             passdata = 'action=' + escape(action) + '&id=' + id + '&dataJSON=' + escape(Base64.encode(dataJSONStr));
118 
119             this.cbp = function (d) {
120                 /*jslint evil:true*/
121                 var str, data,
122                     tmp, inject, paramlist, id,
123                     i, j;
124 
125                 str = (new Zip.Unzip(Base64.decodeAsArray(d))).unzip();
126                 if (Type.isArray(str) && str.length > 0) {
127                     str = str[0][0];
128                 }
129 
130                 if (!Type.exists(str)) {
131                     return;
132                 }
133 
134                 data = window.JSON && window.JSON.parse ? window.JSON.parse(str) : (new Function('return ' + str))();
135 
136                 if (data.type === 'error') {
137                     this.handleError(data);
138                 } else if (data.type === 'response') {
139                     id = data.id;
140 
141                     // inject fields
142                     for (i = 0; i < data.fields.length; i++) {
143                         tmp = data.fields[i];
144                         inject = tmp.namespace + (typeof ((new Function('return ' + tmp.namespace))()) === 'object' ? '.' : '.prototype.') + tmp.name + ' = ' + tmp.value;
145                         (new Function(inject))();
146                     }
147 
148                     // inject handlers
149                     for (i = 0; i < data.handler.length; i++) {
150                         tmp = data.handler[i];
151                         paramlist = [];
152 
153                         for (j = 0; j < tmp.parameters.length; j++) {
154                             paramlist[j] = '"' + tmp.parameters[j] + '": ' + tmp.parameters[j];
155                         }
156                         // insert subnamespace named after module.
157                         inject = 'if(typeof JXG.Server.modules.' + this.runningCalls[id].module + ' == "undefined")' + 'JXG.Server.modules.' + this.runningCalls[id].module + ' = {};';
158 
159                         // insert callback method which fetches and uses the server's data for calculation in JavaScript
160                         inject += 'JXG.Server.modules.' + this.runningCalls[id].module + '.' + tmp.name + '_cb = ' + tmp.callback + ';';
161 
162                         // insert handler as JXG.Server.modules.<module name>.<handler name>
163                         inject += 'JXG.Server.modules.' + this.runningCalls[id].module + '.' + tmp.name + ' = function (' + tmp.parameters.join(',') + ', __JXGSERVER_CB__, __JXGSERVER_SYNC) {' +
164                             'if(typeof __JXGSERVER_CB__ == "undefined") __JXGSERVER_CB__ = JXG.Server.modules.' + this.runningCalls[id].module + '.' + tmp.name + '_cb;' +
165                             'var __JXGSERVER_PAR__ = {' + paramlist.join(',') + ', "module": "' + this.runningCalls[id].module + '", "handler": "' + tmp.name + '" };' +
166                             'JXG.Server.callServer("exec", __JXGSERVER_CB__, __JXGSERVER_PAR__, __JXGSERVER_SYNC);' +
167                             '};';
168                         (new Function(inject))();
169                     }
170 
171                     delete this.runningCalls[id];
172 
173                     // handle data
174                     callback(data.data);
175                 }
176             };
177 
178             // bind cbp callback method to JXG.Server to get access to JXG.Server fields from within cpb
179             this.cb = JXG.bind(this.cbp, this);
180 
181             // we're using our own XMLHttpRequest object in here because of a/sync and POST
182             if (window.XMLHttpRequest) {
183                 AJAX = new XMLHttpRequest();
184                 AJAX.overrideMimeType('text/plain; charset=iso-8859-1');
185             } else {
186                 AJAX = new ActiveXObject("Microsoft.XMLHTTP");
187             }
188             if (AJAX) {
189                 // POST is required if data sent to server is too long for a url.
190                 // some browsers/http servers don't accept long urls.
191                 AJAX.open("POST", fileurl, !sync);
192                 AJAX.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
193 
194                 if (!sync) {
195                     // Define function to fetch data received from server
196                     // that function returning a function is required to make this.cb known to the function.
197                     AJAX.onreadystatechange = (function (cb) {
198                         return function () {
199                             if (AJAX.readyState === 4 && AJAX.status === 200) {
200                                 cb(AJAX.responseText);
201                                 return true;
202                             }
203                             return false;
204                         };
205                     }(this.cb));
206                 }
207 
208                 // send the data
209                 AJAX.send(passdata);
210                 if (sync) {
211                     this.cb(AJAX.responseText);
212                     return true;
213                 }
214             }
215 
216             return false;
217         },
218 
219         /**
220          * Callback for the default action 'load'.
221          */
222         loadModule_cb: function (data) {
223             var i;
224             for (i = 0; i < data.length; i++) {
225                 JXG.debug(data[i].name + ': ' + data[i].value);
226             }
227         },
228 
229         /**
230          * Loads a module from the server.
231          * @param {string} module A string containing the module. Has to match the filename of the Python module on the server exactly including
232          * lower and upper case letters without the file ending .py.
233          */
234         loadModule: function (module) {
235             return JXG.Server.callServer('load', JXG.Server.loadModule_cb, {'module': module}, true);
236         }
237     };
238 
239     JXG.Server.load = JXG.Server.loadModule;
240 
241     return JXG.Server;
242 });