diff --git a/cloud.js b/cloud.js index 54189d85..ed7233f9 100644 --- a/cloud.js +++ b/cloud.js @@ -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 . - -*/ - -// 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 . + +*/ + +// 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); +};