Files
DiceCloud/app/imports/api/engine/actions/getUserInput.js

91 lines
3.2 KiB
JavaScript

import SimpleSchema from 'simpl-schema';
import { ValidatedMethod } from 'meteor/mdg:validated-method';
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
import { set } from 'lodash';
// Reminder: throwStubExceptions: true is the default, and only
// possible when run() is not async
// For async run() stub exceptions never stop the client from sending
// the call to the server
// Dict of invocationId: {steps: {earlyAnswers, resolve, reject}}
// either resolve functions waiting for the user's input or early answers that were provided
// before the resolves could be set up
let userInputRequests = {};
let provideUserInput;
if (Meteor.isClient) {
provideUserInput = function (invocationId, step, answers, callback) {
Meteor.call('answerUserInputRequest', { invocationId, step, answers }, callback);
// Do the same work on the client without using a stub
answerInputRequestWork({ invocationId, step, answers });
}
}
export { userInputRequests, provideUserInput };
export default async function getUserInput(questions, actionContext) {
// get the invocation details from the action context
const invocationId = actionContext.invocationId;
const step = actionContext.userInputStep;
actionContext.userInputStep += 1; // increment userInput step every time
// If the answers are already waiting, just return them
if (userInputRequests[invocationId]?.[step]?.earlyAnswers) {
return userInputRequests[invocationId][step].earlyAnswers;
}
// On the client, store the questions to be answered
if (Meteor.isClient) {
set(userInputRequests, `${invocationId}[${step}]`, { questions });
}
// Create a place for the answers to go when they are provided
return new Promise((resolve, reject) => {
set(userInputRequests, `${invocationId}[${step}]`, { resolve, reject });
});
}
function answerInputRequestWork({ invocationId, step, answers }) {
console.log('running answerUserInputRequest');
const invocation = userInputRequests[invocationId];
if (!invocation) {
// Call order on the server is guaranteed, so the invocation must have been created
// Before we can update it
throw new Meteor.Error('Not found', 'The method this answer is updating does not exist');
}
if (invocation[step]?.resolve) {
// If there is a resolve waiting for this response, provide it
invocation[step].resolve(answers);
} else {
// Otherwise just store the response as early answers
invocation[step] = {
earlyAnswers: answers
};
}
}
if (Meteor.isServer) {
// This function is not defined on the client so that it has no stub function
// This allows it to be called while still simulating an awaited async method
// See https://guide.meteor.com/2.8-migration.html#the-limitations
new ValidatedMethod({
name: 'answerUserInputRequest',
validate: new SimpleSchema({
invocationId: SimpleSchema.RegEx.Id,
step: SimpleSchema.Integer,
answers: {
type: Object,
blackbox: true,
},
}).validator(),
applyOptions: {
throwStubExceptions: false,
},
mixins: [RateLimiterMixin],
rateLimit: {
numRequests: 20,
timeInterval: 5000,
},
run: answerInputRequestWork,
});
}