91 lines
3.2 KiB
JavaScript
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,
|
|
});
|
|
}
|