turtlestitch/cloud.js

490 wiersze
14 KiB
JavaScript
Czysty Zwykły widok Historia

2017-09-28 16:30:13 +00:00
/*
cloud.js
a backend API for SNAP!
written by Bernat Romagosa
inspired in the old cloud API by Jens Mönig
2017-09-28 16:30:13 +00:00
Copyright (C) 2017 by Bernat Romagosa
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, nop, localize*/
modules.cloud = '2015-December-15';
// Global stuff
var Cloud;
// Cloud /////////////////////////////////////////////////////////////
function Cloud(url) {
this.init(url);
};
Cloud.prototype.init = function (url) {
this.url = url;
this.username = null;
};
// Dictionary handling
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;
};
2017-10-04 09:46:50 +00:00
// Error handling
Cloud.genericErrorMessage =
'There was an error while trying to access\n' +
'a Snap!Cloud service. Please try again later.';
Cloud.prototype.genericError = function () {
throw new Error(Cloud.genericErrorMessage);
};
2017-09-28 16:30:13 +00:00
// Low level functionality
2017-10-04 09:46:50 +00:00
Cloud.prototype.request = function (
method,
path,
onSuccess,
onError,
errorMsg,
wantsRawResponse,
body) {
2017-09-28 16:30:13 +00:00
var request = new XMLHttpRequest(),
myself = this;
2017-10-04 09:46:50 +00:00
2017-09-28 16:30:13 +00:00
try {
request.open(
2017-10-04 09:46:50 +00:00
method,
2017-09-28 16:30:13 +00:00
this.url + path,
true
);
request.setRequestHeader(
'Content-Type',
'application/json; charset=utf-8'
);
request.withCredentials = true;
request.onreadystatechange = function () {
if (request.readyState === 4) {
if (request.responseText) {
var response =
(!wantsRawResponse ||
(request.responseText.indexOf('{"errors"') === 0)) ?
JSON.parse(request.responseText) :
request.responseText;
2017-09-28 16:30:13 +00:00
if (response.errors) {
onError.call(
null,
response.errors[0],
errorMsg
);
} else {
2017-10-04 09:46:50 +00:00
if (onSuccess) {
onSuccess.call(null, response.message || response);
}
2017-09-28 16:30:13 +00:00
}
} else {
2017-10-04 09:46:50 +00:00
if (onError) {
2017-09-28 16:30:13 +00:00
onError.call(
2017-10-04 09:46:50 +00:00
null,
2017-10-25 14:59:58 +00:00
errorMsg || Cloud.genericErrorMessage,
myself.url
2017-09-28 16:30:13 +00:00
);
} else {
2017-10-04 09:46:50 +00:00
myself.genericError();
2017-09-28 16:30:13 +00:00
}
}
}
};
request.send(body);
} catch (err) {
onError.call(this, err.toString(), 'Cloud Error');
}
};
2017-10-04 09:46:50 +00:00
Cloud.prototype.withCredentialsRequest = function (
method,
path,
onSuccess,
onError,
errorMsg,
wantsRawResponse,
body) {
2017-10-04 09:46:50 +00:00
var myself = this;
this.checkCredentials(
function (username) {
if (username) {
myself.request(
method,
// %username is replaced by the actual username
path.replace('%username', username),
onSuccess,
onError,
errorMsg,
wantsRawResponse,
body);
2017-10-04 09:46:50 +00:00
} else {
onError.call(this, 'You are not logged in', 'Snap!Cloud');
}
2017-10-04 09:46:50 +00:00
}
);
};
2017-09-28 16:30:13 +00:00
// Credentials management
2017-10-04 17:53:25 +00:00
Cloud.prototype.initSession = function (onSuccess) {
var myself = this;
this.request(
'POST',
'/init',
function () { myself.checkCredentials(onSuccess); },
nop,
null,
true
);
};
2017-09-28 16:30:13 +00:00
Cloud.prototype.checkCredentials = function (onSuccess, onError) {
var myself = this;
this.getCurrentUser(
function (user) {
if (user.username) {
myself.username = user.username;
}
if (onSuccess) { onSuccess.call(null, user.username); }
},
onError
);
2017-09-28 16:30:13 +00:00
};
Cloud.prototype.getCurrentUser = function (onSuccess, onError) {
2017-10-04 09:46:50 +00:00
this.request('GET', '/users/c', onSuccess, onError, 'Could not retrieve current user');
2017-09-28 16:30:13 +00:00
};
Cloud.prototype.logout = function (onSuccess, onError) {
2017-10-04 17:53:25 +00:00
this.username = null;
2017-10-04 09:46:50 +00:00
this.request(
'POST',
2017-10-04 17:53:25 +00:00
'/logout',
2017-09-28 16:30:13 +00:00
onSuccess,
onError,
'logout failed'
);
2017-09-28 16:30:13 +00:00
};
2017-10-04 17:53:25 +00:00
Cloud.prototype.login = function (username, password, persist, onSuccess, onError) {
2017-09-28 16:30:13 +00:00
var myself = this;
2017-10-04 09:46:50 +00:00
this.request(
'POST',
2017-10-04 17:53:25 +00:00
'/users/' + username + '/login?' +
this.encodeDict({
password: password,
persist: persist
}),
2017-09-28 16:30:13 +00:00
function () {
myself.checkCredentials(onSuccess, onError);
},
onError,
'login failed');
};
Cloud.prototype.signup = function (username, password, password_repeat, email, onSuccess, onError){
2017-10-04 09:46:50 +00:00
this.request(
'POST',
2017-09-28 16:30:13 +00:00
'/users/' + username + '?' + this.encodeDict({
email: email,
password: password,
password_repeat: password_repeat
}),
onSuccess,
onError,
'signup failed');
};
// Projects
Cloud.prototype.saveProject = function (ide, onSuccess, onError) {
var myself = this;
2017-09-28 16:30:13 +00:00
this.checkCredentials(
function (username) {
if (username) {
var xml = ide.serializer.serialize(ide.stage),
thumbnail = ide.stage.thumbnail(
SnapSerializer.prototype.thumbnailSize).toDataURL(),
body, mediaSize, size;
ide.serializer.isCollectingMedia = true;
body = {
notes: ide.projectNotes,
xml: xml,
media: ide.hasChangedMedia ?
ide.serializer.mediaXML(ide.projectName) : null,
thumbnail: thumbnail
};
ide.serializer.isCollectingMedia = false;
ide.serializer.flushMedia();
mediaSize = body.media ? body.media.length : 0;
size = body.xml.length + mediaSize;
if (mediaSize > 10485760) {
new DialogBoxMorph().inform(
'Snap!Cloud - Cannot Save Project',
'The media inside this project exceeds 10 MB.\n' +
'Please reduce the size of costumes or sounds.\n',
ide.world(),
ide.cloudIcon(null, new Color(180, 0, 0))
);
throw new Error('Project media exceeds 10 MB size limit');
}
2017-09-28 16:30:13 +00:00
// check if serialized data can be parsed back again
try {
ide.serializer.parse(body.xml);
2017-09-28 16:30:13 +00:00
} catch (err) {
ide.showMessage('Serialization of program data failed:\n' + err);
throw new Error('Serialization of program data failed:\n' + err);
}
if (body.media !== null) {
try {
ide.serializer.parse(body.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();
2017-09-28 16:30:13 +00:00
ide.showMessage('Uploading ' + Math.round(size / 1024) + ' KB...');
2017-09-28 16:30:13 +00:00
2017-10-04 09:46:50 +00:00
myself.request(
'POST',
'/projects/' + username + '/' + ide.projectName,
onSuccess,
onError,
2017-10-04 09:46:50 +00:00
'Project could not be saved',
false,
JSON.stringify(body), // POST body
);
2017-09-28 16:30:13 +00:00
} else {
onError.call(this, 'You are not logged in', 'Snap!Cloud');
}
}
);
};
Cloud.prototype.getProjectList = function (onSuccess, onError, withThumbnail) {
var path = '/projects/%username';
if (withThumbnail) {
path += '?withthumbnail=true';
}
2017-10-04 09:46:50 +00:00
this.withCredentialsRequest(
'GET',
path,
2017-10-04 09:46:50 +00:00
onSuccess,
onError,
'Could not fetch projects'
);
};
Cloud.prototype.getPublishedProjectList = function (username, page, pageSize, searchTerm, onSuccess, onError, withThumbnail) {
2017-10-13 16:29:47 +00:00
var path = '/projects' + (username ? '/' + username : '') + '?ispublished=true';
if (withThumbnail) {
path += '&withthumbnail=true';
}
2017-10-13 16:29:47 +00:00
if (page) {
path += '&page=' + page + '&pagesize=' + (pageSize || 16);
}
if (searchTerm) {
path += '&matchtext=' + searchTerm;
}
2017-10-10 11:00:49 +00:00
this.request(
'GET',
2017-10-13 16:29:47 +00:00
path,
2017-10-10 11:00:49 +00:00
onSuccess,
onError,
'Could not fetch projects'
);
};
2017-10-13 16:29:47 +00:00
Cloud.prototype.getThumbnail = function (username, projectName, onSuccess, onError) {
if (username) {
this.request(
'GET',
'/projects/' + username + '/' + projectName + '/thumbnail',
onSuccess,
onError,
'Could not fetch thumbnail',
true
);
} else {
this.withCredentialsRequest(
'GET',
'/projects/%username/' + projectName + '/thumbnail',
onSuccess,
onError,
'Could not fetch thumbnail',
true
);
}
};
Cloud.prototype.getRawProject = function (projectName, onSuccess, onError) {
2017-10-04 09:46:50 +00:00
this.withCredentialsRequest(
'GET',
'/projects/%username/' + projectName,
onSuccess,
onError,
'Could not fetch project ' + projectName,
true
);
};
Cloud.prototype.getPublicProject = function (projectName, username, onSuccess, onError) {
this.request(
'GET',
'/projects/' + username + '/' + projectName,
onSuccess,
onError,
'Could not fetch project ' + projectName,
2017-10-04 09:46:50 +00:00
true
);
};
2017-10-09 10:37:34 +00:00
Cloud.prototype.getProjectMetadata = function (projectName, username, onSuccess, onError) {
this.request(
'GET',
'/projects/' + username + '/' + projectName + '/metadata',
onSuccess,
onError,
'Could not fetch metadata for ' + projectName
);
};
Cloud.prototype.deleteProject = function (projectName, onSuccess, onError) {
2017-10-04 09:46:50 +00:00
this.withCredentialsRequest(
'DELETE',
'/projects/%username/' + projectName,
onSuccess,
onError,
'Could not delete project'
);
2017-09-28 16:30:13 +00:00
};
Cloud.prototype.shareProject = function (projectName, onSuccess, onError) {
this.withCredentialsRequest(
'POST',
'/projects/%username/' + projectName + '/metadata?ispublic=true',
onSuccess,
onError,
'Could not share project'
);
};
Cloud.prototype.unshareProject = function (projectName, onSuccess, onError) {
this.withCredentialsRequest(
'POST',
'/projects/%username/' + projectName + '/metadata?ispublic=false&ispublished=false',
onSuccess,
onError,
'Could not unshare project'
);
};
Cloud.prototype.publishProject = function (projectName, onSuccess, onError) {
this.withCredentialsRequest(
'POST',
'/projects/%username/' + projectName + '/metadata?ispublished=true',
onSuccess,
onError,
'Could not publish project'
);
};
Cloud.prototype.unpublishProject = function (projectName, onSuccess, onError) {
this.withCredentialsRequest(
'POST',
'/projects/%username/' + projectName + '/metadata?ispublished=false',
onSuccess,
onError,
'Could not unpublish project'
);
};
2017-10-25 14:59:58 +00:00
Cloud.prototype.updateNotes = function (projectName, notes, onSuccess, onError) {
this.withCredentialsRequest(
'POST',
'/projects/%username/' + projectName + '/metadata',
onSuccess,
onError,
'Could not update project notes',
false, // wants raw response
JSON.stringify({ notes: notes })
);
};
2017-09-28 16:30:13 +00:00
var SnapCloud = new Cloud('http://localhost:8080');