1 /* 2 Copyright 2008-2023 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"; 41 import Zip from "../utils/zip"; 42 import Base64 from "../utils/base64"; 43 import Type from "../utils/type"; 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, id, dataJSONStr, k; 82 83 sync = sync || false; 84 85 params = ""; 86 for (k in data) { 87 if (data.hasOwnProperty(k)) { 88 params += "&" + escape(k) + "=" + escape(data[k]); 89 } 90 } 91 92 dataJSONStr = Type.toJSON(data); 93 94 // generate id 95 do { 96 id = action + Math.floor(Math.random() * 4096); 97 } while (Type.exists(this.runningCalls[id])); 98 99 // store information about the calls 100 this.runningCalls[id] = { action: action }; 101 if (Type.exists(data.module)) { 102 this.runningCalls[id].module = data.module; 103 } 104 105 fileurl = JXG.serverBase + "JXGServer.py"; 106 passdata = 107 "action=" + 108 escape(action) + 109 "&id=" + 110 id + 111 "&dataJSON=" + 112 escape(Base64.encode(dataJSONStr)); 113 114 this.cbp = function (d) { 115 /*jslint evil:true*/ 116 var str, data, tmp, inject, paramlist, id, i, j; 117 118 str = new Zip.Unzip(Base64.decodeAsArray(d)).unzip(); 119 if (Type.isArray(str) && str.length > 0) { 120 str = str[0][0]; 121 } 122 123 if (!Type.exists(str)) { 124 return; 125 } 126 127 data = 128 window.JSON && window.JSON.parse 129 ? window.JSON.parse(str) 130 : new Function("return " + str)(); 131 132 if (data.type === "error") { 133 this.handleError(data); 134 } else if (data.type === "response") { 135 id = data.id; 136 137 // inject fields 138 for (i = 0; i < data.fields.length; i++) { 139 tmp = data.fields[i]; 140 inject = 141 tmp.namespace + 142 (typeof new Function("return " + tmp.namespace)() === "object" 143 ? "." 144 : ".prototype.") + 145 tmp.name + 146 " = " + 147 tmp.value; 148 new Function(inject)(); 149 } 150 151 // inject handlers 152 for (i = 0; i < data.handler.length; i++) { 153 tmp = data.handler[i]; 154 paramlist = []; 155 156 for (j = 0; j < tmp.parameters.length; j++) { 157 paramlist[j] = '"' + tmp.parameters[j] + '": ' + tmp.parameters[j]; 158 } 159 // insert subnamespace named after module. 160 inject = 161 "if(typeof JXG.Server.modules." + 162 this.runningCalls[id].module + 163 ' == "undefined")' + 164 "JXG.Server.modules." + 165 this.runningCalls[id].module + 166 " = {};"; 167 168 // insert callback method which fetches and uses the server's data for calculation in JavaScript 169 inject += 170 "JXG.Server.modules." + 171 this.runningCalls[id].module + 172 "." + 173 tmp.name + 174 "_cb = " + 175 tmp.callback + 176 ";"; 177 178 // insert handler as JXG.Server.modules.<module name>.<handler name> 179 inject += 180 "JXG.Server.modules." + 181 this.runningCalls[id].module + 182 "." + 183 tmp.name + 184 " = function (" + 185 tmp.parameters.join(",") + 186 ", __JXGSERVER_CB__, __JXGSERVER_SYNC) {" + 187 'if(typeof __JXGSERVER_CB__ == "undefined") __JXGSERVER_CB__ = JXG.Server.modules.' + 188 this.runningCalls[id].module + 189 "." + 190 tmp.name + 191 "_cb;" + 192 "var __JXGSERVER_PAR__ = {" + 193 paramlist.join(",") + 194 ', "module": "' + 195 this.runningCalls[id].module + 196 '", "handler": "' + 197 tmp.name + 198 '" };' + 199 'JXG.Server.callServer("exec", __JXGSERVER_CB__, __JXGSERVER_PAR__, __JXGSERVER_SYNC);' + 200 "};"; 201 new Function(inject)(); 202 } 203 204 delete this.runningCalls[id]; 205 206 // handle data 207 callback(data.data); 208 } 209 }; 210 211 // bind cbp callback method to JXG.Server to get access to JXG.Server fields from within cpb 212 this.cb = JXG.bind(this.cbp, this); 213 214 // We are using our own XMLHttpRequest object in here because of a/sync and POST 215 if (window.XMLHttpRequest) { 216 AJAX = new XMLHttpRequest(); 217 AJAX.overrideMimeType("text/plain; charset=iso-8859-1"); 218 } else { 219 AJAX = new ActiveXObject("Microsoft.XMLHTTP"); 220 } 221 if (AJAX) { 222 // POST is required if data sent to server is too long for a url. 223 // some browsers/http servers don't accept long urls. 224 AJAX.open("POST", fileurl, !sync); 225 AJAX.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); 226 227 if (!sync) { 228 // Define function to fetch data received from server 229 // that function returning a function is required to make this.cb known to the function. 230 AJAX.onreadystatechange = (function (cb) { 231 return function () { 232 if (AJAX.readyState === 4 && AJAX.status === 200) { 233 cb(AJAX.responseText); 234 return true; 235 } 236 return false; 237 }; 238 })(this.cb); 239 } 240 241 // send the data 242 AJAX.send(passdata); 243 if (sync) { 244 this.cb(AJAX.responseText); 245 return true; 246 } 247 } 248 249 return false; 250 }, 251 252 /** 253 * Callback for the default action 'load'. 254 */ 255 loadModule_cb: function (data) { 256 var i; 257 for (i = 0; i < data.length; i++) { 258 JXG.debug(data[i].name + ": " + data[i].value); 259 } 260 }, 261 262 /** 263 * Loads a module from the server. 264 * @param {string} module A string containing the module. Has to match the filename of the Python module on the server exactly including 265 * lower and upper case letters without the file ending .py. 266 */ 267 loadModule: function (module) { 268 return JXG.Server.callServer( 269 "load", 270 JXG.Server.loadModule_cb, 271 { module: module }, 272 true 273 ); 274 } 275 }; 276 277 JXG.Server.load = JXG.Server.loadModule; 278 279 export default JXG.Server; 280