Skip to content
28 changes: 9 additions & 19 deletions src/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const path = require("path");
const routeUtils = require("./procedures/utils/router-utils");
const NetsBloxCloud = require("./cloud-client");
const { UserError } = require("./error");
const RpcCaller = require("./rpc-caller");

class ServicesAPI {
constructor() {
Expand Down Expand Up @@ -85,7 +86,7 @@ class ServicesAPI {

this.addServiceRoutes(router);

router.route("/").get((req, res) => {
router.route("/").get((_req, res) => {
const metadata = Object.entries(this.services.metadata)
.filter((nameAndMetadata) => this.isServiceLoaded(nameAndMetadata[0]))
.map((pair) => {
Expand Down Expand Up @@ -169,35 +170,24 @@ class ServicesAPI {
return false;
}

async invokeRPC(serviceName, rpcName, req, res) {
async invokeRPC(serviceName, rpcName, request, response) {
const { clientId } = req.query;
const caller = new RpcCaller(NetsBloxCloud, clientId);
this.logger.info(
`Received request to ${serviceName} for ${rpcName} (from ${clientId})`,
`Received request to ${serviceName} for ${rpcName} (from ${caller.clientId})`,
);

const ctx = {};
ctx.response = res;
ctx.request = req;
const { username, state } = await NetsBloxCloud.getClientInfo(clientId);
// TODO: add support for external states, too?
const projectId = state?.browser?.projectId;
const roleId = state?.browser?.roleId;

ctx.caller = {
username,
projectId,
roleId,
clientId,
};
const ctx = { caller, request, response };

const apiKey = this.services.getApiKey(serviceName);
const isLoggedIn = !!username;
if (apiKey && isLoggedIn) {
if (apiKey && await caller.isLoggedIn()) {
// TODO: handle invalid settings (parse error)
const apiKeyValue = await this.keys.get(username, apiKey); // TODO: double check this
if (apiKeyValue) {
ctx.apiKey = apiKeyValue;
}
}
// TODO: move the socket over?
ctx.socket = new RemoteClient(projectId, roleId, clientId);

const args = this.getArguments(serviceName, rpcName, req);
Expand Down
2 changes: 1 addition & 1 deletion src/cloud-client.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ class NetsBloxCloud {
}

// Service Settings
async getServiceSettings(username) {
async getServiceSettings(username) { // TODO: memoize this
const url = `/services/settings/user/${username}/${this.id}/all`;
const settings = await this.get(url);
// settings fields are strings which we happen to use to store JSON
Expand Down
20 changes: 17 additions & 3 deletions src/input-types.js
Original file line number Diff line number Diff line change
Expand Up @@ -433,20 +433,34 @@ defineType({
parser: async (blockXml, _params, ctx) => {
let roleName = "";
let roleNames = [""];
let username = null;
let projectId = null;
let roleId = null;

if (ctx) {
const room = await Cloud.getRoomState(ctx.caller.projectId);
const room = await ctx.caller.getRoomState();
roleId = await ctx.caller.getRoleId();
projectId = await ctx.caller.getProjectId();
username = await ctx.caller.getUsername();
if (room) {
roleNames = Object.values(room.roles)
.map((role) => role.name);
roleName = room.roles[ctx.caller.roleId].name;
roleName = room.roles[roleId].name;
}
}

let factory = blocks2js.compile(blockXml);
let env = blocks2js.newContext();
env.__start = function (project) {
project.ctx = ctx;
const defaultCtx = { caller: {} };
project.ctx = ctx || defaultCtx;

// ctx.caller is expected to provide sync access to things like projectId, roleId, username
// polyfill it for now
project.ctx.caller.roleId = roleId;
project.ctx.caller.projectId = projectId;
project.ctx.caller.username = username;

project.roleName = roleName;
project.roleNames = roleNames;
};
Expand Down
8 changes: 1 addition & 7 deletions src/messages.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,7 @@ class SendMessage extends Message {
}

class SendMessageToClient extends Message {
constructor(projectId, roleId, clientId, type, contents) {
const state = {
browser: {
projectId,
roleId,
},
};
constructor(state, clientId, type, contents) {
const target = { client: { state, clientId } };
super(target, type, contents);
}
Expand Down
17 changes: 10 additions & 7 deletions src/procedures/alexa/alexa.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ Alexa.createSkill = async function (configuration) {

const vendorId = await h.getVendorID(smapiClient);

const manifest = schemas.manifest(this.caller.username, configuration);
const username = await this.caller.getUsername();
const manifest = schemas.manifest(username, configuration);
const interactionModel = schemas.interactionModel(configuration);
const accountLinkingRequest = schemas.accountLinking();

Expand Down Expand Up @@ -77,8 +78,8 @@ Alexa.createSkill = async function (configuration) {
await skills.updateOne({ _id: skillId }, {
$set: {
config: configuration,
context: this.caller,
author: this.caller.username,
context: await this.caller.toSnapshot(),
author: await this.caller.getUsername(),
createdAt: new Date(),
},
}, { upsert: true });
Expand Down Expand Up @@ -128,7 +129,7 @@ Alexa.invokeSkill = async function (id, utterance) {
Alexa.deleteSkill = async function (id) {
const { skills } = GetStorage();
const skillData = await h.getSkillData(id);
if (skillData.author !== this.caller.username) {
if (skillData.author !== await this.caller.getUsername()) {
throw new Error("Unauthorized: Skills can only be deleted by the author.");
}

Expand All @@ -152,7 +153,9 @@ Alexa.deleteSkill = async function (id) {
*/
Alexa.listSkills = async function () {
const { skills } = GetStorage();
const skillConfigs = await skills.find({ author: this.caller.username })
const skillConfigs = await skills.find({
author: await this.caller.getUsername(),
})
.toArray();
return skillConfigs.map((skill) => skill._id);
};
Expand Down Expand Up @@ -202,8 +205,8 @@ Alexa.updateSkill = async function (id, configuration) {
await skills.updateOne({ _id: id }, {
$set: {
config: configuration,
context: this.caller,
author: this.caller.username,
context: await this.caller.toSnapshot(),
author: await this.caller.getUsername(),
updatedAt: new Date(),
},
}, { upsert: true });
Expand Down
10 changes: 2 additions & 8 deletions src/procedures/alexa/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,16 +31,10 @@ function clarifyError(error) {
return error;
}

const ensureLoggedIn = function (caller) {
if (!caller.username) {
throw new Error("Login required.");
}
};

const getAPIClient = async function (caller) {
ensureLoggedIn(caller);
const username = await caller.getUsername();
const collection = GetStorage().tokens;
const tokens = await collection.findOne({ username: caller.username });
const tokens = await collection.findOne({ username });
if (!tokens) {
throw new Error("Amazon Login required. Please login.");
}
Expand Down
10 changes: 4 additions & 6 deletions src/procedures/alexa/skill.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,13 @@ class AlexaSkill {
.find((intentConfig) => intentConfig.name === name);

const handlerXML = intentConfig.handler;
const context = this.skillData.context;
const context = CallerSnapshot.from(this.skillData.context);
if (username) {
context.username = username;
context.setUsername(username);
}

const socket = new RemoteClient(
context.projectId,
context.roleId,
null,
username,
context,
);

const handler = await InputTypes.parse.Function(handlerXML, null, {
Expand Down
23 changes: 6 additions & 17 deletions src/procedures/autograders/autograders.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,11 +93,6 @@ const validateTest = (testConfig) => {
}
};

const ensureLoggedIn = function (caller) {
if (!caller.username) {
throw new Error("Login required.");
}
};
const Autograders = {};

/**
Expand All @@ -106,10 +101,9 @@ const Autograders = {};
* @param {Object} configuration
*/
Autograders.createAutograder = async function (config) {
ensureLoggedIn(this.caller);
const author = await this.caller.getUsername();
config = preprocessConfig(config);
const { autograders } = getDatabase();
const author = this.caller.username;
const extension = {
type: "Autograder",
name: config.name,
Expand Down Expand Up @@ -143,9 +137,8 @@ Autograders.createAutograder = async function (config) {
* @param {String} consumer - name of the consumer to add (eg, Coursera)
*/
Autograders.addLTIConsumer = async function (autograder, consumer) {
ensureLoggedIn(this.caller);
const { autograders } = getDatabase();
const author = this.caller.username;
const author = await this.caller.getUsername();
try {
const secret = uuid.v4();
const consumerData = {
Expand Down Expand Up @@ -209,9 +202,8 @@ Autograders.addLTIConsumer = async function (autograder, consumer) {
* @param {String} autograder - name of the autograder to update
*/
Autograders.getLTIConsumers = async function (autograder) {
ensureLoggedIn(this.caller);
const { autograders } = getDatabase();
const author = this.caller.username;
const author = await this.caller.getUsername();
const grader = await autograders.findOne(
{ author, name: autograder },
);
Expand All @@ -232,9 +224,8 @@ Autograders.getLTIConsumers = async function (autograder) {
* @param {String} consumer - name of the consumer to add (eg, Coursera)
*/
Autograders.removeLTIConsumer = async function (autograder, consumer) {
ensureLoggedIn(this.caller);
const { autograders } = getDatabase();
const author = this.caller.username;
const author = await this.caller.getUsername();
const grader = await autograders.findOne(
{ author, name: autograder },
);
Expand All @@ -259,8 +250,7 @@ Autograders.removeLTIConsumer = async function (autograder, consumer) {
* List the autograders for the given user.
*/
Autograders.getAutograders = async function () {
ensureLoggedIn(this.caller);
const author = this.caller.username;
const author = await this.caller.getUsername();
const { autograders } = getDatabase();
const options = {
projection: { name: 1 },
Expand All @@ -275,8 +265,7 @@ Autograders.getAutograders = async function () {
* @param {String} name
*/
Autograders.getAutograderConfig = async function (name) {
ensureLoggedIn(this.caller);
const author = this.caller.username;
const author = await this.caller.getUsername();
const { autograders } = getDatabase();
const autograder = await autograders.findOne({ author, name });
if (!autograder) {
Expand Down
58 changes: 27 additions & 31 deletions src/procedures/battleship/battleship.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,8 @@ Battleship.prototype.start = function () {
* @param {String} facing Direction to face
* @returns {Boolean} If piece was placed
*/
Battleship.prototype.placeShip = function (ship, row, column, facing) {
var role = this.caller.roleId,
Battleship.prototype.placeShip = async function (ship, row, column, facing) {
var role = await this.caller.getRoleId(),
len = SHIPS[ship];

row--;
Expand Down Expand Up @@ -158,8 +158,8 @@ Battleship.prototype.placeShip = function (ship, row, column, facing) {
* @param {BoundedNumber<1,100>} column Column to fire at
* @returns {Boolean} If ship was hit
*/
Battleship.prototype.fire = function (row, column) {
const role = this.caller.roleId;
Battleship.prototype.fire = async function (row, column) {
const role = await this.caller.getRoleId();

row = row - 1;
column = column - 1;
Expand Down Expand Up @@ -199,22 +199,23 @@ Battleship.prototype.fire = function (row, column) {
const result = this._state._boards[target].fire(row, column);

if (result) {
return Utils.getRoleName(this.caller.projectId, target)
.then((targetName) => {
const msgType = result.HIT
? BattleshipConstants.HIT
: BattleshipConstants.MISS;
const data = {
role: targetName,
row: row + 1,
column: column + 1,
ship: result.SHIP,
sunk: result.SUNK,
};
this.socket.sendMessageToRoom(msgType, data);
this.response.send(!!result);
return !!result;
});
const targetName = await Utils.getRoleName(
await this.caller.getRoomState(),
target,
);

const msgType = result.HIT
? BattleshipConstants.HIT
: BattleshipConstants.MISS;

const data = {
role: targetName,
row: row + 1,
column: column + 1,
ship: result.SHIP,
sunk: result.SUNK,
};
this.socket.sendMessageToRoom(msgType, data);
}

this.response.send(!!result);
Expand All @@ -227,22 +228,17 @@ Battleship.prototype.fire = function (row, column) {
* @returns {Integer} Number of remaining ships
*/
Battleship.prototype.remainingShips = async function (roleName) {
let role;

if (roleName) { // resolve the provided role name to a role ID
const metadata = await NetsBloxCloud.getRoomState(this.caller.projectId);
const role = Object.keys(metadata.roles).find((id) => {
const metadata = await this.caller.getRoomState();
role = Object.keys(metadata.roles).find((id) => {
return metadata.roles[id].name === roleName;
});

if (!this._state._boards[role]) {
logger.error(`board doesn't exist for "${role}"`);
this._state._boards[role] = new Board(BOARD_SIZE);
}

return this._state._boards[role].remaining();
} else {
role = await this.caller.getRoleId();
}

const role = this.caller.roleId;

if (!this._state._boards[role]) {
logger.error(`board doesn't exist for "${role}"`);
this._state._boards[role] = new Board(BOARD_SIZE);
Expand Down
Loading