Retry loading map on variable error

If the back is getting an error (because the user has no right to set a variable),
instead of failing directly, let's try to reload the map (maybe we have cached a wrong version of the map).
This commit is contained in:
David Négrier 2021-11-22 18:37:44 +01:00
parent 3487fa90e0
commit 82a1a5fc1e
3 changed files with 67 additions and 21 deletions

View File

@ -26,6 +26,7 @@ import { VariablesManager } from "../Services/VariablesManager";
import { ADMIN_API_URL } from "../Enum/EnvironmentVariable"; import { ADMIN_API_URL } from "../Enum/EnvironmentVariable";
import { LocalUrlError } from "../Services/LocalUrlError"; import { LocalUrlError } from "../Services/LocalUrlError";
import { emitErrorOnRoomSocket } from "../Services/MessageHelpers"; import { emitErrorOnRoomSocket } from "../Services/MessageHelpers";
import { VariableError } from "../Services/VariableError";
export type ConnectCallback = (user: User, group: Group) => void; export type ConnectCallback = (user: User, group: Group) => void;
export type DisconnectCallback = (user: User, group: Group) => void; export type DisconnectCallback = (user: User, group: Group) => void;
@ -336,30 +337,61 @@ export class GameRoom {
// First, let's check if "user" is allowed to modify the variable. // First, let's check if "user" is allowed to modify the variable.
const variableManager = await this.getVariableManager(); const variableManager = await this.getVariableManager();
const readableBy = variableManager.setVariable(name, value, user); try {
const readableBy = variableManager.setVariable(name, value, user);
// If the variable was not changed, let's not dispatch anything. // If the variable was not changed, let's not dispatch anything.
if (readableBy === false) { if (readableBy === false) {
return; return;
} }
// TODO: should we batch those every 100ms? // TODO: should we batch those every 100ms?
const variableMessage = new VariableWithTagMessage(); const variableMessage = new VariableWithTagMessage();
variableMessage.setName(name); variableMessage.setName(name);
variableMessage.setValue(value); variableMessage.setValue(value);
if (readableBy) { if (readableBy) {
variableMessage.setReadableby(readableBy); variableMessage.setReadableby(readableBy);
} }
const subMessage = new SubToPusherRoomMessage(); const subMessage = new SubToPusherRoomMessage();
subMessage.setVariablemessage(variableMessage); subMessage.setVariablemessage(variableMessage);
const batchMessage = new BatchToPusherRoomMessage(); const batchMessage = new BatchToPusherRoomMessage();
batchMessage.addPayload(subMessage); batchMessage.addPayload(subMessage);
// Dispatch the message on the room listeners // Dispatch the message on the room listeners
for (const socket of this.roomListeners) { for (const socket of this.roomListeners) {
socket.write(batchMessage); socket.write(batchMessage);
}
} catch (e) {
if (e instanceof VariableError) {
// Ok, we have an error setting a variable. Either the user is trying to hack the map... or the map
// is not up to date. So let's try to reload the map from scratch.
if (this.variableManagerLastLoad === undefined) {
throw e;
}
const lastLoaded = new Date().getTime() - this.variableManagerLastLoad.getTime();
if (lastLoaded < 10000) {
console.log(
'An error occurred while setting the "' +
name +
"\" variable. But we tried to reload the map less than 10 seconds ago, so let's fail."
);
// Do not try to reload if we tried to reload less than 10 seconds ago.
throw e;
}
// Reset the variable manager
this.variableManagerPromise = undefined;
console.log(
'An error occurred while setting the "' + name + "\" variable. Let's reload the map and try again"
);
// Try to set the variable again!
await this.setVariable(name, value, user);
} else {
throw e;
}
} }
} }
@ -449,11 +481,13 @@ export class GameRoom {
} }
private variableManagerPromise: Promise<VariablesManager> | undefined; private variableManagerPromise: Promise<VariablesManager> | undefined;
private variableManagerLastLoad: Date | undefined;
private getVariableManager(): Promise<VariablesManager> { private getVariableManager(): Promise<VariablesManager> {
if (!this.variableManagerPromise) { if (!this.variableManagerPromise) {
this.variableManagerPromise = this.getMap() this.variableManagerPromise = this.getMap()
.then((map) => { .then((map) => {
this.variableManagerLastLoad = new Date();
const variablesManager = new VariablesManager(this.roomUrl, map); const variablesManager = new VariablesManager(this.roomUrl, map);
return variablesManager.init(); return variablesManager.init();
}) })

View File

@ -0,0 +1,9 @@
/**
* Errors related to variable handling.
*/
export class VariableError extends Error {
constructor(message: string) {
super(message);
Object.setPrototypeOf(this, VariableError.prototype);
}
}

View File

@ -10,6 +10,7 @@ import {
import { User } from "_Model/User"; import { User } from "_Model/User";
import { variablesRepository } from "./Repository/VariablesRepository"; import { variablesRepository } from "./Repository/VariablesRepository";
import { redisClient } from "./RedisClient"; import { redisClient } from "./RedisClient";
import { VariableError } from "./VariableError";
interface Variable { interface Variable {
defaultValue?: string; defaultValue?: string;
@ -174,11 +175,13 @@ export class VariablesManager {
if (this.variableObjects) { if (this.variableObjects) {
variableObject = this.variableObjects.get(name); variableObject = this.variableObjects.get(name);
if (variableObject === undefined) { if (variableObject === undefined) {
throw new Error('Trying to set a variable "' + name + '" that is not defined as an object in the map.'); throw new VariableError(
'Trying to set a variable "' + name + '" that is not defined as an object in the map.'
);
} }
if (variableObject.writableBy && !user.tags.includes(variableObject.writableBy)) { if (variableObject.writableBy && !user.tags.includes(variableObject.writableBy)) {
throw new Error( throw new VariableError(
'Trying to set a variable "' + 'Trying to set a variable "' +
name + name +
'". User "' + '". User "' +