Merge pull request #3539 from kchadha/cloud-connect
Cloud Data Provider
This commit is contained in:
5 files changed
+152
-2
No files matched your search
+1
-1
@@ -105,7 +105,7 @@
|
||||
"scratch-render": "0.1.0-prerelease.20181029125804",
|
||||
"scratch-storage": "1.1.0",
|
||||
"scratch-svg-renderer": "0.2.0-prerelease.20181024192149",
|
||||
"scratch-vm": "0.2.0-prerelease.20181025092837",
|
||||
"scratch-vm": "0.2.0-prerelease.20181030160328",
|
||||
"selenium-webdriver": "3.6.0",
|
||||
"startaudiocontext": "1.2.1",
|
||||
"style-loader": "^0.23.0",
|
||||
|
||||
@@ -73,6 +73,7 @@ class GUI extends React.Component {
|
||||
const {
|
||||
/* eslint-disable no-unused-vars */
|
||||
assetHost,
|
||||
cloudHost,
|
||||
error,
|
||||
hideIntro,
|
||||
isError,
|
||||
@@ -104,6 +105,7 @@ class GUI extends React.Component {
|
||||
GUI.propTypes = {
|
||||
assetHost: PropTypes.string,
|
||||
children: PropTypes.node,
|
||||
cloudHost: PropTypes.string,
|
||||
error: PropTypes.oneOfType([PropTypes.object, PropTypes.string]),
|
||||
fetchingProject: PropTypes.bool,
|
||||
hideIntro: PropTypes.bool,
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
import log from './log.js';
|
||||
|
||||
|
||||
class CloudProvider {
|
||||
/**
|
||||
* A cloud data provider which creates and manages a web socket connection
|
||||
* to the Scratch cloud data server. This provider is responsible for
|
||||
* interfacing with the VM's cloud io device.
|
||||
* @param {string} cloudHost The url for the cloud data server
|
||||
* @param {VirtualMachine} vm The Scratch virtual machine to interface with
|
||||
* @param {string} username The username to associate cloud data updates with
|
||||
* @param {string} projectId The id associated with the project containing
|
||||
* cloud data.
|
||||
*/
|
||||
constructor (cloudHost, vm, username, projectId) {
|
||||
this.vm = vm;
|
||||
this.username = username;
|
||||
this.projectId = projectId;
|
||||
|
||||
// Open a websocket connection to the clouddata server
|
||||
this.openConnection(cloudHost);
|
||||
}
|
||||
|
||||
/**
|
||||
* Open a new websocket connection to the clouddata server.
|
||||
* @param {string} cloudHost The cloud data server to connect to.
|
||||
*/
|
||||
openConnection (cloudHost) {
|
||||
if (window.WebSocket === null) {
|
||||
log.warn('Websocket support is not available in this browser');
|
||||
this.connection = null;
|
||||
return;
|
||||
}
|
||||
|
||||
this.connection = new WebSocket((location.protocol === 'http:' ? 'ws://' : 'wss://') + cloudHost);
|
||||
|
||||
this.connection.onerror = e => {
|
||||
log.error(`Websocket connection error: ${JSON.stringify(e)}`);
|
||||
|
||||
// TODO Add re-connection attempt logic here
|
||||
};
|
||||
|
||||
this.connection.onmessage = event => {
|
||||
const messageString = event.data;
|
||||
log.info(`Received websocket message: ${messageString}`);
|
||||
const message = JSON.parse(messageString);
|
||||
if (message.method === 'set') {
|
||||
const varData = {
|
||||
varUpdate: {
|
||||
name: message.name,
|
||||
value: message.value
|
||||
}
|
||||
};
|
||||
this.vm.postIOData('cloud', varData);
|
||||
}
|
||||
};
|
||||
|
||||
this.connection.onopen = () => {
|
||||
this.writeToServer('handshake');
|
||||
log.info(`Successfully connected to clouddata server.`);
|
||||
};
|
||||
|
||||
this.connection.onclose = () => {
|
||||
log.info(`Closed connection to websocket`);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Format and send a message to the cloud data server.
|
||||
* @param {string} methodName The message method, indicating the action to perform.
|
||||
* @param {string} dataName The name of the cloud variable this message pertains to
|
||||
* @param {string | number} dataValue The value to set the cloud variable to
|
||||
* @param {number} dataIndex The index of the item to update (for cloud lists)
|
||||
* @param {string} dataNewName The new name for the cloud variable (if renaming)
|
||||
*/
|
||||
writeToServer (methodName, dataName, dataValue, dataIndex, dataNewName) {
|
||||
const msg = {};
|
||||
msg.method = methodName;
|
||||
msg.user = this.username;
|
||||
msg.project_id = this.projectId;
|
||||
|
||||
if (dataName) msg.name = dataName;
|
||||
if (dataValue) msg.value = dataValue;
|
||||
if (dataIndex) msg.index = dataIndex;
|
||||
if (dataNewName) msg.new_name = dataNewName;
|
||||
|
||||
const dataToWrite = JSON.stringify(msg);
|
||||
this.sendCloudData(dataToWrite);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a formatted message to the cloud data server.
|
||||
* @param {string} data The formatted message to send.
|
||||
*/
|
||||
sendCloudData (data) {
|
||||
this.connection.send(`${data}\n`);
|
||||
log.info(`Sent message to clouddata server: ${data}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides an API for the VM's cloud IO device to update
|
||||
* a cloud variable on the server.
|
||||
* @param {string} name The name of the variable to update
|
||||
* @param {string | number} value The new value for the variable
|
||||
*/
|
||||
updateVariable (name, value) {
|
||||
this.writeToServer('set', name, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the connection to the web socket and clears the cloud
|
||||
* provider of references related to the cloud data project.
|
||||
*/
|
||||
requestCloseConnection () {
|
||||
this.connection.close();
|
||||
this.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear this provider of references related to the project
|
||||
* and current state.
|
||||
*/
|
||||
clear () {
|
||||
this.connection = null;
|
||||
this.vm = null;
|
||||
this.username = null;
|
||||
this.projectId = null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default CloudProvider;
|
||||
@@ -102,7 +102,6 @@ const vmListenerHOC = function (WrappedComponent) {
|
||||
/* eslint-disable no-unused-vars */
|
||||
attachKeyboardEvents,
|
||||
shouldEmitTargetsUpdate,
|
||||
username,
|
||||
onBlockDragUpdate,
|
||||
onKeyDown,
|
||||
onKeyUp,
|
||||
|
||||
@@ -5,6 +5,7 @@ import {connect} from 'react-redux';
|
||||
|
||||
import VM from 'scratch-vm';
|
||||
import AudioEngine from 'scratch-audio';
|
||||
import CloudProvider from '../lib/cloud-provider';
|
||||
|
||||
import {
|
||||
LoadingStates,
|
||||
@@ -46,6 +47,18 @@ const vmManagerHOC = function (WrappedComponent) {
|
||||
return this.props.vm.loadProject(this.props.projectData)
|
||||
.then(() => {
|
||||
this.props.onLoadedProject(this.props.loadingState, this.props.canSave);
|
||||
// If the cloud host exists, open a cloud connection and
|
||||
// set the vm's cloud provider.
|
||||
if (this.props.cloudHost) {
|
||||
// TODO check if we should actually
|
||||
// connect to cloud data based on info from the loaded project and
|
||||
// info about the user (e.g. scratcher status)
|
||||
this.props.vm.setCloudProvider(new CloudProvider(
|
||||
this.props.cloudHost,
|
||||
this.props.vm,
|
||||
this.props.username,
|
||||
this.props.projectId));
|
||||
}
|
||||
})
|
||||
.catch(e => {
|
||||
this.props.onError(e);
|
||||
@@ -54,12 +67,14 @@ const vmManagerHOC = function (WrappedComponent) {
|
||||
render () {
|
||||
const {
|
||||
/* eslint-disable no-unused-vars */
|
||||
cloudHost,
|
||||
fontsLoaded,
|
||||
loadingState,
|
||||
onError: onErrorProp,
|
||||
onLoadedProject: onLoadedProjectProp,
|
||||
projectData,
|
||||
projectId,
|
||||
username,
|
||||
/* eslint-enable no-unused-vars */
|
||||
isLoadingWithId: isLoadingWithIdProp,
|
||||
vm,
|
||||
@@ -77,6 +92,7 @@ const vmManagerHOC = function (WrappedComponent) {
|
||||
|
||||
VMManager.propTypes = {
|
||||
canSave: PropTypes.bool,
|
||||
cloudHost: PropTypes.string,
|
||||
fontsLoaded: PropTypes.bool,
|
||||
isLoadingWithId: PropTypes.bool,
|
||||
loadingState: PropTypes.oneOf(LoadingStates),
|
||||
@@ -84,6 +100,7 @@ const vmManagerHOC = function (WrappedComponent) {
|
||||
onLoadedProject: PropTypes.func,
|
||||
projectData: PropTypes.oneOfType([PropTypes.object, PropTypes.string]),
|
||||
projectId: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
username: PropTypes.string,
|
||||
vm: PropTypes.instanceOf(VM).isRequired
|
||||
};
|
||||
|
||||
|
||||
Reference in new issue
Block a user