turtlestitch/cloud.js

642 wiersze
18 KiB
JavaScript

/*
cloud.js
a backend API for SNAP!
written by Jens Mönig
Copyright (C) 2013 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*/
modules.cloud = '2013-November-07';
// Global stuff
var Cloud;
var SnapCloud = new Cloud(
'https://snapcloud.miosoft.com/miocon/app/login?_app=SnapCloud'
//'192.168.2.110:8087/miocon/app/login?_app=SnapCloud'
//'192.168.186.146:8087/miocon/app/login?_app=SnapCloud'
// 'localhost/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='
+ 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',
'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',
'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',
'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,
'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,
'Service: ' + 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);
};