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, 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