diff options
| author | jmoenig <jens@moenig.org> | 2015-01-12 10:13:49 +0100 |
|---|---|---|
| committer | jmoenig <jens@moenig.org> | 2015-01-12 10:13:49 +0100 |
| commit | 947906aab9a8bb684ae6d1ce1ddd0c3ed2505119 (patch) | |
| tree | b3213ec3ff876aeb07bb0ce5925ec045f9f6af99 | |
| parent | 27370676f2bf925c3a8498fc2dfef959e1aa45bd (diff) | |
| download | snap-947906aab9a8bb684ae6d1ce1ddd0c3ed2505119.tar.gz snap-947906aab9a8bb684ae6d1ce1ddd0c3ed2505119.zip | |
Backend load balancing support
also:
* eliminate now obsolete authentication roundtrip,
* Cloud error message tweaks
| -rw-r--r-- | cloud.js | 1281 |
1 files changed, 642 insertions, 639 deletions
@@ -1,639 +1,642 @@ -/* - - cloud.js - - a backend API for SNAP! - - written by Jens Mönig - - Copyright (C) 2014 by Jens Mönig - - This file is part of Snap!. - - Snap! is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as - published by the Free Software Foundation, either version 3 of - the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see <http://www.gnu.org/licenses/>. - -*/ - -// Global settings ///////////////////////////////////////////////////// - -/*global modules, IDE_Morph, SnapSerializer, hex_sha512, alert, nop, -localize*/ - -modules.cloud = '2014-May-26'; - -// Global stuff - -var Cloud; - -var SnapCloud = new Cloud( - 'https://snapcloud.miosoft.com/miocon/app/login?_app=SnapCloud' -); - -// Cloud ///////////////////////////////////////////////////////////// - -function Cloud(url) { - this.username = null; - this.password = null; // hex_sha512 hashed - this.url = url; - this.session = null; - this.api = {}; -} - -Cloud.prototype.clear = function () { - this.username = null; - this.password = null; - this.session = null; - this.api = {}; -}; - -Cloud.prototype.hasProtocol = function () { - return this.url.toLowerCase().indexOf('http') === 0; -}; - -// Cloud: Snap! API - -Cloud.prototype.signup = function ( - username, - email, - callBack, - errorCall -) { - // both callBack and errorCall are two-argument functions - var request = new XMLHttpRequest(), - myself = this; - try { - request.open( - "GET", - (this.hasProtocol() ? '' : 'http://') - + this.url + 'SignUp' - + '&Username=' - + encodeURIComponent(username) - + '&Email=' - + encodeURIComponent(email), - true - ); - request.setRequestHeader( - "Content-Type", - "application/x-www-form-urlencoded" - ); - request.withCredentials = true; - request.onreadystatechange = function () { - if (request.readyState === 4) { - if (request.responseText) { - if (request.responseText.indexOf('ERROR') === 0) { - errorCall.call( - this, - request.responseText, - 'Signup' - ); - } else { - callBack.call( - null, - request.responseText, - 'Signup' - ); - } - } else { - errorCall.call( - null, - myself.url + 'SignUp', - localize('could not connect to:') - ); - } - } - }; - request.send(null); - } catch (err) { - errorCall.call(this, err.toString(), 'Snap!Cloud'); - } -}; - -Cloud.prototype.getPublicProject = function ( - id, - callBack, - errorCall -) { - // id is Username=username&projectName=projectname, - // where the values are url-component encoded - // callBack is a single argument function, errorCall take two args - var request = new XMLHttpRequest(), - responseList, - myself = this; - try { - request.open( - "GET", - (this.hasProtocol() ? '' : 'http://') - + this.url + 'Public' - + '&' - + id, - true - ); - request.setRequestHeader( - "Content-Type", - "application/x-www-form-urlencoded" - ); - request.withCredentials = true; - request.onreadystatechange = function () { - if (request.readyState === 4) { - if (request.responseText) { - if (request.responseText.indexOf('ERROR') === 0) { - errorCall.call( - this, - request.responseText - ); - } else { - responseList = myself.parseResponse( - request.responseText - ); - callBack.call( - null, - responseList[0].SourceCode - ); - } - } else { - errorCall.call( - null, - myself.url + 'Public', - localize('could not connect to:') - ); - } - } - }; - request.send(null); - } catch (err) { - errorCall.call(this, err.toString(), 'Snap!Cloud'); - } -}; - -Cloud.prototype.resetPassword = function ( - username, - callBack, - errorCall -) { - // both callBack and errorCall are two-argument functions - var request = new XMLHttpRequest(), - myself = this; - try { - request.open( - "GET", - (this.hasProtocol() ? '' : 'http://') - + this.url + 'ResetPW' - + '&Username=' - + encodeURIComponent(username), - true - ); - request.setRequestHeader( - "Content-Type", - "application/x-www-form-urlencoded" - ); - request.withCredentials = true; - request.onreadystatechange = function () { - if (request.readyState === 4) { - if (request.responseText) { - if (request.responseText.indexOf('ERROR') === 0) { - errorCall.call( - this, - request.responseText, - 'Reset Password' - ); - } else { - callBack.call( - null, - request.responseText, - 'Reset Password' - ); - } - } else { - errorCall.call( - null, - myself.url + 'ResetPW', - localize('could not connect to:') - ); - } - } - }; - request.send(null); - } catch (err) { - errorCall.call(this, err.toString(), 'Snap!Cloud'); - } -}; - -Cloud.prototype.connect = function ( - callBack, - errorCall -) { - // both callBack and errorCall are two-argument functions - var request = new XMLHttpRequest(), - myself = this; - try { - request.open( - "GET", - (this.hasProtocol() ? '' : 'http://') + this.url, - true - ); - request.setRequestHeader( - "Content-Type", - "application/x-www-form-urlencoded" - ); - request.withCredentials = true; - request.onreadystatechange = function () { - if (request.readyState === 4) { - if (request.responseText) { - myself.api = myself.parseAPI(request.responseText); - myself.session = request.getResponseHeader('MioCracker') - .split(';')[0]; - if (myself.api.login) { - callBack.call(null, myself.api, 'Snap!Cloud'); - } else { - errorCall.call( - null, - 'connection failed' - ); - } - } else { - errorCall.call( - null, - myself.url, - localize('could not connect to:') - ); - } - } - }; - request.send(null); - } catch (err) { - errorCall.call(this, err.toString(), 'Snap!Cloud'); - } -}; - - -Cloud.prototype.login = function ( - username, - password, - callBack, - errorCall -) { - var myself = this; - this.connect( - function () { - myself.rawLogin(username, password, callBack, errorCall); - myself.disconnect(); - }, - errorCall - ); -}; - -Cloud.prototype.rawLogin = function ( - username, - password, - callBack, - errorCall -) { - // both callBack and errorCall are two-argument functions - var myself = this, - pwHash = hex_sha512("miosoft%20miocon," - + this.session.split('=')[1] + "," - + encodeURIComponent(username.toLowerCase()) + "," - + password // alreadey hex_sha512 hashed - ); - this.callService( - 'login', - function (response, url) { - if (myself.api.logout) { - myself.username = username; - myself.password = password; - callBack.call(null, response, url); - } else { - errorCall.call( - null, - 'Service catalog is not available,\nplease retry', - 'Connection Error:' - ); - } - }, - errorCall, - [username, pwHash] - ); -}; - -Cloud.prototype.reconnect = function ( - callBack, - errorCall -) { - if (!(this.username && this.password)) { - this.message('You are not logged in'); - return; - } - this.login( - this.username, - this.password, - callBack, - errorCall - ); -}; - -Cloud.prototype.saveProject = function (ide, callBack, errorCall) { - var myself = this, - pdata, - media; - - ide.serializer.isCollectingMedia = true; - pdata = ide.serializer.serialize(ide.stage); - media = ide.hasChangedMedia ? - ide.serializer.mediaXML(ide.projectName) : null; - ide.serializer.isCollectingMedia = false; - ide.serializer.flushMedia(); - - // check if serialized data can be parsed back again - try { - ide.serializer.parse(pdata); - } catch (err) { - ide.showMessage('Serialization of program data failed:\n' + err); - throw new Error('Serialization of program data failed:\n' + err); - } - if (media !== null) { - try { - ide.serializer.parse(media); - } catch (err) { - ide.showMessage('Serialization of media failed:\n' + err); - throw new Error('Serialization of media failed:\n' + err); - } - } - ide.serializer.isCollectingMedia = false; - ide.serializer.flushMedia(); - - myself.reconnect( - function () { - myself.callService( - 'saveProject', - function (response, url) { - callBack.call(null, response, url); - myself.disconnect(); - ide.hasChangedMedia = false; - }, - errorCall, - [ - ide.projectName, - pdata, - media, - pdata.length, - media ? media.length : 0 - ] - ); - }, - errorCall - ); -}; - -Cloud.prototype.getProjectList = function (callBack, errorCall) { - var myself = this; - this.reconnect( - function () { - myself.callService( - 'getProjectList', - function (response, url) { - callBack.call(null, response, url); - myself.disconnect(); - }, - errorCall - ); - }, - errorCall - ); -}; - -Cloud.prototype.changePassword = function ( - oldPW, - newPW, - callBack, - errorCall -) { - var myself = this; - this.reconnect( - function () { - myself.callService( - 'changePassword', - function (response, url) { - callBack.call(null, response, url); - myself.disconnect(); - }, - errorCall, - [oldPW, newPW] - ); - }, - errorCall - ); -}; - -Cloud.prototype.logout = function (callBack, errorCall) { - this.clear(); - this.callService( - 'logout', - callBack, - errorCall - ); -}; - -Cloud.prototype.disconnect = function () { - this.callService( - 'logout', - nop, - nop - ); -}; - -// Cloud: backend communication - -Cloud.prototype.callURL = function (url, callBack, errorCall) { - // both callBack and errorCall are optional two-argument functions - var request = new XMLHttpRequest(), - myself = this; - try { - request.open('GET', url, true); - request.withCredentials = true; - request.setRequestHeader( - "Content-Type", - "application/x-www-form-urlencoded" - ); - request.setRequestHeader('MioCracker', this.session); - request.onreadystatechange = function () { - if (request.readyState === 4) { - if (request.responseText) { - var responseList = myself.parseResponse( - request.responseText - ); - callBack.call(null, responseList, url); - } else { - errorCall.call( - null, - url, - 'no response from:' - ); - } - } - }; - request.send(null); - } catch (err) { - errorCall.call(this, err.toString(), url); - } -}; - -Cloud.prototype.callService = function ( - serviceName, - callBack, - errorCall, - args -) { - // both callBack and errorCall are optional two-argument functions - var request = new XMLHttpRequest(), - service = this.api[serviceName], - myself = this, - postDict; - - if (!this.session) { - errorCall.call(null, 'You are not connected', 'Cloud'); - return; - } - if (!service) { - errorCall.call( - null, - 'service ' + serviceName + ' is not available', - 'API' - ); - return; - } - if (args && args.length > 0) { - postDict = {}; - service.parameters.forEach(function (parm, idx) { - postDict[parm] = args[idx]; - }); - } - try { - request.open(service.method, service.url, true); - request.withCredentials = true; - request.setRequestHeader( - "Content-Type", - "application/x-www-form-urlencoded" - ); - request.setRequestHeader('MioCracker', this.session); - request.onreadystatechange = function () { - if (request.readyState === 4) { - var responseList = []; - if (request.responseText && - request.responseText.indexOf('ERROR') === 0) { - errorCall.call( - this, - request.responseText, - localize('Service:') + ' ' + localize(serviceName) - ); - return; - } - if (serviceName === 'login') { - myself.api = myself.parseAPI(request.responseText); - } - responseList = myself.parseResponse( - request.responseText - ); - callBack.call(null, responseList, service.url); - } - }; - request.send(this.encodeDict(postDict)); - } catch (err) { - errorCall.call(this, err.toString(), service.url); - } -}; - -// Cloud: payload transformation - -Cloud.prototype.parseAPI = function (src) { - var api = {}, - services; - services = src.split(" "); - services.forEach(function (service) { - var entries = service.split("&"), - serviceDescription = {}, - parms; - entries.forEach(function (entry) { - var pair = entry.split("="), - key = decodeURIComponent(pair[0]).toLowerCase(), - val = decodeURIComponent(pair[1]); - if (key === "service") { - api[val] = serviceDescription; - } else if (key === "parameters") { - parms = val.split(","); - if (!(parms.length === 1 && !parms[0])) { - serviceDescription.parameters = parms; - } - } else { - serviceDescription[key] = val; - } - }); - }); - return api; -}; - -Cloud.prototype.parseResponse = function (src) { - var ans = [], - lines; - if (!src) {return ans; } - lines = src.split(" "); - lines.forEach(function (service) { - var entries = service.split("&"), - dict = {}; - entries.forEach(function (entry) { - var pair = entry.split("="), - key = decodeURIComponent(pair[0]), - val = decodeURIComponent(pair[1]); - dict[key] = val; - }); - ans.push(dict); - }); - return ans; -}; - -Cloud.prototype.parseDict = function (src) { - var dict = {}; - if (!src) {return dict; } - src.split("&").forEach(function (entry) { - var pair = entry.split("="), - key = decodeURIComponent(pair[0]), - val = decodeURIComponent(pair[1]); - dict[key] = val; - }); - return dict; -}; - -Cloud.prototype.encodeDict = function (dict) { - var str = '', - pair, - key; - if (!dict) {return null; } - for (key in dict) { - if (dict.hasOwnProperty(key)) { - pair = encodeURIComponent(key) - + '=' - + encodeURIComponent(dict[key]); - if (str.length > 0) { - str += '&'; - } - str += pair; - } - } - return str; -}; - -// Cloud: user messages (to be overridden) - -Cloud.prototype.message = function (string) { - alert(string); -}; +/*
+
+ cloud.js
+
+ a backend API for SNAP!
+
+ written by Jens Mönig
+
+ Copyright (C) 2015 by Jens Mönig
+
+ This file is part of Snap!.
+
+ Snap! is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as
+ published by the Free Software Foundation, either version 3 of
+ the License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+// Global settings /////////////////////////////////////////////////////
+
+/*global modules, IDE_Morph, SnapSerializer, hex_sha512, alert, nop,
+localize*/
+
+modules.cloud = '2015-January-12';
+
+// Global stuff
+
+var Cloud;
+var SnapCloud = new Cloud(
+ 'https://snap.apps.miosoft.com/SnapCloud'
+);
+
+// Cloud /////////////////////////////////////////////////////////////
+
+function Cloud(url) {
+ this.username = null;
+ this.password = null; // hex_sha512 hashed
+ this.url = url;
+ this.session = null;
+ this.limo = null;
+ this.route = null;
+ this.api = {};
+}
+
+Cloud.prototype.clear = function () {
+ this.username = null;
+ this.password = null;
+ this.session = null;
+ this.limo = null;
+ this.route = null;
+ this.api = {};
+};
+
+Cloud.prototype.hasProtocol = function () {
+ return this.url.toLowerCase().indexOf('http') === 0;
+};
+
+Cloud.prototype.setRoute = function (username) {
+ var routes = 10,
+ userNum = 0,
+ i;
+
+ for (i = 0; i < username.length; i += 1) {
+ userNum += username.charCodeAt(i);
+ }
+ userNum = userNum % routes + 1;
+ this.route = '.sc1m' +
+ (userNum < 10 ? '0' : '') +
+ userNum;
+};
+
+// Cloud: Snap! API
+
+Cloud.prototype.signup = function (
+ username,
+ email,
+ callBack,
+ errorCall
+) {
+ // both callBack and errorCall are two-argument functions
+ var request = new XMLHttpRequest(),
+ myself = this;
+ try {
+ request.open(
+ "GET",
+ (this.hasProtocol() ? '' : 'http://')
+ + this.url + 'SignUp'
+ + '?Username='
+ + encodeURIComponent(username)
+ + '&Email='
+ + encodeURIComponent(email),
+ true
+ );
+ request.setRequestHeader(
+ "Content-Type",
+ "application/x-www-form-urlencoded"
+ );
+ request.withCredentials = true;
+ request.onreadystatechange = function () {
+ if (request.readyState === 4) {
+ if (request.responseText) {
+ if (request.responseText.indexOf('ERROR') === 0) {
+ errorCall.call(
+ this,
+ request.responseText,
+ 'Signup'
+ );
+ } else {
+ callBack.call(
+ null,
+ request.responseText,
+ 'Signup'
+ );
+ }
+ } else {
+ errorCall.call(
+ null,
+ myself.url + 'SignUp',
+ localize('could not connect to:')
+ );
+ }
+ }
+ };
+ request.send(null);
+ } catch (err) {
+ errorCall.call(this, err.toString(), 'Snap!Cloud');
+ }
+};
+
+Cloud.prototype.getPublicProject = function (
+ id,
+ callBack,
+ errorCall
+) {
+ // id is Username=username&projectName=projectname,
+ // where the values are url-component encoded
+ // callBack is a single argument function, errorCall take two args
+ var request = new XMLHttpRequest(),
+ responseList,
+ myself = this;
+ try {
+ request.open(
+ "GET",
+ (this.hasProtocol() ? '' : 'http://')
+ + this.url + 'Public'
+ + '?'
+ + id,
+ true
+ );
+ request.setRequestHeader(
+ "Content-Type",
+ "application/x-www-form-urlencoded"
+ );
+ request.withCredentials = true;
+ request.onreadystatechange = function () {
+ if (request.readyState === 4) {
+ if (request.responseText) {
+ if (request.responseText.indexOf('ERROR') === 0) {
+ errorCall.call(
+ this,
+ request.responseText
+ );
+ } else {
+ responseList = myself.parseResponse(
+ request.responseText
+ );
+ callBack.call(
+ null,
+ responseList[0].SourceCode
+ );
+ }
+ } else {
+ errorCall.call(
+ null,
+ myself.url + 'Public',
+ localize('could not connect to:')
+ );
+ }
+ }
+ };
+ request.send(null);
+ } catch (err) {
+ errorCall.call(this, err.toString(), 'Snap!Cloud');
+ }
+};
+
+Cloud.prototype.resetPassword = function (
+ username,
+ callBack,
+ errorCall
+) {
+ // both callBack and errorCall are two-argument functions
+ var request = new XMLHttpRequest(),
+ myself = this;
+ try {
+ request.open(
+ "GET",
+ (this.hasProtocol() ? '' : 'http://')
+ + this.url + 'ResetPW'
+ + '?Username='
+ + encodeURIComponent(username),
+ true
+ );
+ request.setRequestHeader(
+ "Content-Type",
+ "application/x-www-form-urlencoded"
+ );
+ request.withCredentials = true;
+ request.onreadystatechange = function () {
+ if (request.readyState === 4) {
+ if (request.responseText) {
+ if (request.responseText.indexOf('ERROR') === 0) {
+ errorCall.call(
+ this,
+ request.responseText,
+ 'Reset Password'
+ );
+ } else {
+ callBack.call(
+ null,
+ request.responseText,
+ 'Reset Password'
+ );
+ }
+ } else {
+ errorCall.call(
+ null,
+ myself.url + 'ResetPW',
+ localize('could not connect to:')
+ );
+ }
+ }
+ };
+ request.send(null);
+ } catch (err) {
+ errorCall.call(this, err.toString(), 'Snap!Cloud');
+ }
+};
+
+Cloud.prototype.login = function (
+ username,
+ password,
+ callBack,
+ errorCall
+) {
+ // both callBack and errorCall are two-argument functions
+ var request = new XMLHttpRequest(),
+ usr = JSON.stringify({'__h': password, '__u': username}),
+ myself = this;
+ this.setRoute(username);
+ try {
+ request.open(
+ "POST",
+ (this.hasProtocol() ? '' : 'http://') +
+ this.url +
+ '?SESSIONGLUE=' +
+ this.route,
+ true
+ );
+ request.setRequestHeader(
+ "Content-Type",
+ "application/json; charset=utf-8"
+ );
+ // glue this session to a route:
+ request.setRequestHeader('SESSIONGLUE', this.route);
+ request.withCredentials = true;
+ request.onreadystatechange = function () {
+ if (request.readyState === 4) {
+ if (request.responseText) {
+ myself.api = myself.parseAPI(request.responseText);
+ myself.session = request.getResponseHeader('MioCracker')
+ .split(';')[0];
+ // set the cookie identifier:
+ myself.limo = this.getResponseHeader("miocracker")
+ .substring(
+ 9,
+ this.getResponseHeader("miocracker").indexOf("=")
+ );
+ if (myself.api.logout) {
+ myself.username = username;
+ myself.password = password;
+ callBack.call(null, myself.api, 'Snap!Cloud');
+ } else {
+ errorCall.call(
+ null,
+ request.responseText,
+ 'connection failed'
+ );
+ }
+ } else {
+ errorCall.call(
+ null,
+ myself.url,
+ localize('could not connect to:')
+ );
+ }
+ }
+ };
+ request.send(usr);
+ } catch (err) {
+ errorCall.call(this, err.toString(), 'Snap!Cloud');
+ }
+};
+
+Cloud.prototype.reconnect = function (
+ callBack,
+ errorCall
+) {
+ if (!(this.username && this.password)) {
+ this.message('You are not logged in');
+ return;
+ }
+ this.login(
+ this.username,
+ this.password,
+ callBack,
+ errorCall
+ );
+};
+
+Cloud.prototype.saveProject = function (ide, callBack, errorCall) {
+ var myself = this,
+ pdata,
+ media;
+
+ ide.serializer.isCollectingMedia = true;
+ pdata = ide.serializer.serialize(ide.stage);
+ media = ide.hasChangedMedia ?
+ ide.serializer.mediaXML(ide.projectName) : null;
+ ide.serializer.isCollectingMedia = false;
+ ide.serializer.flushMedia();
+
+ // check if serialized data can be parsed back again
+ try {
+ ide.serializer.parse(pdata);
+ } catch (err) {
+ ide.showMessage('Serialization of program data failed:\n' + err);
+ throw new Error('Serialization of program data failed:\n' + err);
+ }
+ if (media !== null) {
+ try {
+ ide.serializer.parse(media);
+ } catch (err) {
+ ide.showMessage('Serialization of media failed:\n' + err);
+ throw new Error('Serialization of media failed:\n' + err);
+ }
+ }
+ ide.serializer.isCollectingMedia = false;
+ ide.serializer.flushMedia();
+
+ myself.reconnect(
+ function () {
+ myself.callService(
+ 'saveProject',
+ function (response, url) {
+ callBack.call(null, response, url);
+ myself.disconnect();
+ ide.hasChangedMedia = false;
+ },
+ errorCall,
+ [
+ ide.projectName,
+ pdata,
+ media,
+ pdata.length,
+ media ? media.length : 0
+ ]
+ );
+ },
+ errorCall
+ );
+};
+
+Cloud.prototype.getProjectList = function (callBack, errorCall) {
+ var myself = this;
+ this.reconnect(
+ function () {
+ myself.callService(
+ 'getProjectList',
+ function (response, url) {
+ callBack.call(null, response, url);
+ myself.disconnect();
+ },
+ errorCall
+ );
+ },
+ errorCall
+ );
+};
+
+Cloud.prototype.changePassword = function (
+ oldPW,
+ newPW,
+ callBack,
+ errorCall
+) {
+ var myself = this;
+ this.reconnect(
+ function () {
+ myself.callService(
+ 'changePassword',
+ function (response, url) {
+ callBack.call(null, response, url);
+ myself.disconnect();
+ },
+ errorCall,
+ [hex_sha512(oldPW), hex_sha512(newPW)]
+ );
+ },
+ errorCall
+ );
+};
+
+Cloud.prototype.logout = function (callBack, errorCall) {
+ this.clear();
+ this.callService(
+ 'logout',
+ callBack,
+ errorCall
+ );
+};
+
+Cloud.prototype.disconnect = function () {
+ this.callService(
+ 'logout',
+ nop,
+ nop
+ );
+};
+
+// Cloud: backend communication
+
+Cloud.prototype.callURL = function (url, callBack, errorCall) {
+ // both callBack and errorCall are optional two-argument functions
+ var request = new XMLHttpRequest(),
+ stickyUrl,
+ myself = this;
+ try {
+ // set the Limo. Also set the glue as a query paramter for backup.
+ stickyUrl = url +
+ '&SESSIONGLUE=' +
+ this.route +
+ '&_Limo=' +
+ this.limo;
+ request.open('GET', stickyUrl, true);
+ request.withCredentials = true;
+ request.setRequestHeader(
+ "Content-Type",
+ "application/x-www-form-urlencoded"
+ );
+ request.setRequestHeader('MioCracker', this.session);
+ // Set the glue as a request header.
+ request.setRequestHeader('SESSIONGLUE', this.route);
+ request.onreadystatechange = function () {
+ if (request.readyState === 4) {
+ if (request.responseText) {
+ var responseList = myself.parseResponse(
+ request.responseText
+ );
+ callBack.call(null, responseList, url);
+ } else {
+ errorCall.call(
+ null,
+ url,
+ 'no response from:'
+ );
+ }
+ }
+ };
+ request.send(null);
+ } catch (err) {
+ errorCall.call(this, err.toString(), url);
+ }
+};
+
+Cloud.prototype.callService = function (
+ serviceName,
+ callBack,
+ errorCall,
+ args
+) {
+ // both callBack and errorCall are optional two-argument functions
+ var request = new XMLHttpRequest(),
+ service = this.api[serviceName],
+ myself = this,
+ stickyUrl,
+ postDict;
+
+ if (!this.session) {
+ errorCall.call(null, 'You are not connected', 'Cloud');
+ return;
+ }
+ if (!service) {
+ errorCall.call(
+ null,
+ 'service ' + serviceName + ' is not available',
+ 'API'
+ );
+ return;
+ }
+ if (args && args.length > 0) {
+ postDict = {};
+ service.parameters.forEach(function (parm, idx) {
+ postDict[parm] = args[idx];
+ });
+ }
+ try {
+ stickyUrl = this.url +
+ '/' +
+ service.url +
+ '&SESSIONGLUE=' +
+ this.route +
+ '&_Limo=' +
+ this.limo;
+ request.open(service.method, stickyUrl, true);
+ request.withCredentials = true;
+ request.setRequestHeader(
+ "Content-Type",
+ "application/x-www-form-urlencoded"
+ );
+ request.setRequestHeader('MioCracker', this.session);
+ request.setRequestHeader('SESSIONGLUE', this.route);
+ request.onreadystatechange = function () {
+ if (request.readyState === 4) {
+ var responseList = [];
+ if (request.responseText &&
+ request.responseText.indexOf('ERROR') === 0) {
+ errorCall.call(
+ this,
+ request.responseText,
+ localize('Service:') + ' ' + localize(serviceName)
+ );
+ return;
+ }
+ if (serviceName === 'login') {
+ myself.api = myself.parseAPI(request.responseText);
+ }
+ responseList = myself.parseResponse(
+ request.responseText
+ );
+ callBack.call(null, responseList, service.url);
+ }
+ };
+ request.send(this.encodeDict(postDict));
+ } catch (err) {
+ errorCall.call(this, err.toString(), service.url);
+ }
+};
+
+// Cloud: payload transformation
+
+Cloud.prototype.parseAPI = function (src) {
+ var api = {},
+ services;
+ services = src.split(" ");
+ services.forEach(function (service) {
+ var entries = service.split("&"),
+ serviceDescription = {},
+ parms;
+ entries.forEach(function (entry) {
+ var pair = entry.split("="),
+ key = decodeURIComponent(pair[0]).toLowerCase(),
+ val = decodeURIComponent(pair[1]);
+ if (key === "service") {
+ api[val] = serviceDescription;
+ } else if (key === "parameters") {
+ parms = val.split(",");
+ if (!(parms.length === 1 && !parms[0])) {
+ serviceDescription.parameters = parms;
+ }
+ } else {
+ serviceDescription[key] = val;
+ }
+ });
+ });
+ return api;
+};
+
+Cloud.prototype.parseResponse = function (src) {
+ var ans = [],
+ lines;
+ if (!src) {return ans; }
+ lines = src.split(" ");
+ lines.forEach(function (service) {
+ var entries = service.split("&"),
+ dict = {};
+ entries.forEach(function (entry) {
+ var pair = entry.split("="),
+ key = decodeURIComponent(pair[0]),
+ val = decodeURIComponent(pair[1]);
+ dict[key] = val;
+ });
+ ans.push(dict);
+ });
+ return ans;
+};
+
+Cloud.prototype.parseDict = function (src) {
+ var dict = {};
+ if (!src) {return dict; }
+ src.split("&").forEach(function (entry) {
+ var pair = entry.split("="),
+ key = decodeURIComponent(pair[0]),
+ val = decodeURIComponent(pair[1]);
+ dict[key] = val;
+ });
+ return dict;
+};
+
+Cloud.prototype.encodeDict = function (dict) {
+ var str = '',
+ pair,
+ key;
+ if (!dict) {return null; }
+ for (key in dict) {
+ if (dict.hasOwnProperty(key)) {
+ pair = encodeURIComponent(key)
+ + '='
+ + encodeURIComponent(dict[key]);
+ if (str.length > 0) {
+ str += '&';
+ }
+ str += pair;
+ }
+ }
+ return str;
+};
+
+// Cloud: user messages (to be overridden)
+
+Cloud.prototype.message = function (string) {
+ alert(string);
+};
|
