samples-ts/functions/smsPhoneVerification.ts (64 lines of code) (raw):
import { InvocationContext, output } from "@azure/functions";
import * as df from "durable-functions";
import {
ActivityHandler,
OrchestrationContext,
OrchestrationHandler,
Task,
TimerTask,
} from "durable-functions";
import { DateTime } from "luxon";
const sendSmsChallengeActivityName = "sendSmsChallenge";
const smsPhoneVerification: OrchestrationHandler = function* (context: OrchestrationContext) {
const phoneNumber: string = context.df.getInput();
if (!phoneNumber) {
throw new Error("A phone number input is required.");
}
const challengeCode: number = yield context.df.callActivity(
sendSmsChallengeActivityName,
phoneNumber
);
// The user has 90 seconds to respond with the code they received in the SMS message.
const expiration: DateTime = DateTime.fromJSDate(context.df.currentUtcDateTime).plus({
seconds: 90,
});
const timeoutTask: TimerTask = context.df.createTimer(expiration.toJSDate());
let authorized = false;
for (let i = 0; i <= 3; i++) {
const challengeResponseTask: Task = context.df.waitForExternalEvent("SmsChallengeResponse");
const winner: Task = yield context.df.Task.any([challengeResponseTask, timeoutTask]);
if (winner === timeoutTask) {
// Timeout expired
break;
}
// We got back a response! Compare it to the challenge code.
if (challengeResponseTask.result === challengeCode) {
authorized = true;
break;
}
}
if (!timeoutTask.isCompleted) {
// All pending timers must be complete or canceled before the function exits.
timeoutTask.cancel();
}
return authorized;
};
df.app.orchestration("smsPhoneVerification", smsPhoneVerification);
const twilioOutput = output.generic({
type: "twilioSms",
from: "%TwilioPhoneNumber%",
accountSidSetting: "TwilioAccountSid",
authTokenSetting: "TwilioAuthToken",
});
const sendSmsChallenge: ActivityHandler = function (
phoneNumber: string,
context: InvocationContext
): number {
// Get a random challenge code
const challengeCode: number = Math.floor(Math.random() * 10000);
context.log(`Sending verification code ${challengeCode} to ${phoneNumber}.`);
context.extraOutputs.set(twilioOutput, {
body: `Your verification code is ${challengeCode.toPrecision(4)}`,
to: phoneNumber,
});
return challengeCode;
};
df.app.activity(sendSmsChallengeActivityName, {
extraOutputs: [twilioOutput],
handler: sendSmsChallenge,
});