Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(verify-retry) Improve code documentation #581

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
167 changes: 83 additions & 84 deletions verify-retry/assets/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@
class="logo"
data-name="Layer 1"
xmlns="http://www.w3.org/2000/svg"
viewbox="0 0 60 60"
viewBox="0 0 60 60"
>
<title>Twilio Logo</title>
<path
Expand Down Expand Up @@ -144,7 +144,7 @@
<input type="submit" value="Verify" />
</div>
</form>
<button id="retry" disabled="true" style="display: none"></button>
<button id="retry" disabled style="display: none"></button>
<button id="call" style="display: none">
📞 Having trouble receiving SMS, call me instead
</button>
Expand All @@ -169,7 +169,7 @@

function showError(error) {
console.error(error);
showStatus(error, (color = "#a94442"));
showStatus(error, color = "#a94442");
}

function showStatus(message, color = "gray") {
Expand All @@ -184,7 +184,21 @@
).innerHTML = `<a href="#" onclick="window.location.reload(false);">${message}</a>`;
}

function showOtpForm() {
document.getElementById("login").style.display = "none";
document.getElementById("otp").style.display = "block";
}

function showCallFallback(attempts) {
let minAttemptForVoice = 3;
if (attempts < minAttemptForVoice) return;

document.getElementById("call").style.display = "block";
}

function getRetryTimeout(attemptNumber) {
// progressively back off the retry timeout
// the more times someone requests a code the longer it will take to retry
const retryTimeouts = {
1: 30,
2: 40,
Expand All @@ -200,18 +214,6 @@
return retryTimeouts[attemptNumber] || defaultTimeout;
}

function showOtpForm() {
document.getElementById("login").style.display = "none";
document.getElementById("otp").style.display = "block";
}

function showCallFallback(attempts) {
let minAttemptForVoice = 3;
if (attempts < minAttemptForVoice) return;

document.getElementById("call").style.display = "block";
}

let timer;
let timeRemaining;
let retryButton = document.getElementById("retry");
Expand Down Expand Up @@ -240,9 +242,10 @@
}
}

// save the phone number to use for both sending and checking verifications
var to;

function sendVerificationToken(event, channel = "sms") {
async function sendVerificationToken(event, channel = "sms") {
event.preventDefault();
let statusMessage =
channel == "call" ? "☎️ calling..." : "Sending verification code...";
Expand All @@ -254,43 +257,43 @@
data.append("channel", channel);
data.append("to", to);

fetch("./start-verify", {
method: "POST",
body: data,
})
.then((response) => {
if (response.status == 429) {
clearStatus();
showOtpForm();
showError(
`You have attempted to verify the phone number ${to} too many times. If you received a code, enter it below. Otherwise, please wait 10 minutes and `
);
showReset("try again.");
} else if (response.status >= 400) {
clearStatus();
document.getElementById("otp").style.display = "none";
document.getElementById("login").style.display = "flex";

return response.json().then(({ message }) => {
showError(message);
});
} else {
showOtpForm();
return response.json().then((json) => {
if (json.success) {
showStatus(json.message + `.`);
startCountdown(json.attempts);
} else {
showError(json.message);
}
showReset("Edit phone number.");
});
}
})
.catch(() => {
showError(`Something went wrong while sending code to ${to}.`);
showReset("Edit phone number.");
try {
let response = await fetch("./start-verify", {
method: "POST",
body: data,
});

if (response.status == 429) {
clearStatus();
showOtpForm();
showError(
`You have attempted to verify the phone number ${to} too many times. If you received a code, enter it below. Otherwise, please wait 10 minutes and `
);
showReset("try again.");
} else if (response.status >= 400) {
clearStatus();
document.getElementById("otp").style.display = "none";
document.getElementById("login").style.display = "flex";

return response.json().then(({ message }) => {
showError(message);
});
} else {
showOtpForm();
return response.json().then((json) => {
if (json.success) {
showStatus(json.message + `.`);
startCountdown(json.attempts);
} else {
showError(json.message);
}
showReset("Edit phone number.");
});
}
} catch (error) {
showError(`Something went wrong while sending code to ${to}.`);
showReset("Edit phone number.");
};
}

function clearStatus() {
Expand All @@ -301,17 +304,17 @@
document.getElementById("call").style.display = "none";
}

function retrySend(event) {
async function retrySend(event) {
clearStatus();
sendVerificationToken(event);
}

function sendVoiceToken(event) {
async function sendVoiceToken(event) {
clearStatus();
sendVerificationToken(event, (channel = "call"));
sendVerificationToken(event, channel = "call");
}

function validateToken(event) {
async function validateToken(event) {
event.preventDefault();
clearStatus();
showStatus("Checking code...");
Expand All @@ -323,36 +326,32 @@
var tags = document.querySelectorAll("input[name=tags]:checked");
tags.forEach((tag) => data.append("tags", tag.value));

fetch("./check-verify", {
method: "POST",
body: data,
})
.then((response) => {
return response.json();
})
.then((json) => {
if (json.success) {
clearStatus();
document.getElementById("otp").style.display = "none";
showStatus(json.message, (color = "#3c763d"));
} else {
showError(`${json.message} Check the code sent to ${to}.`);
}
showReset(
"Use a different phone number or start a new verification."
);
document.getElementById("code").value = "";
})
.catch((error) => {
showError(error);
try {
let response = await fetch("./check-verify", {
method: "POST",
body: data,
});

let json = await response.json();

if (json.success) {
clearStatus();
document.getElementById("otp").style.display = "none";
showStatus(json.message, "#3c763d");
} else {
showError(`${json.message} Check the code sent to ${to}.`);
}
showReset("Use a different phone number or start a new verification.");
document.getElementById("code").value = "";
} catch (error) {
showError(error);
}
}

document
.getElementById("login")
.addEventListener("submit", sendVerificationToken);
document.getElementById("retry").addEventListener("click", retrySend);
document.getElementById("call").addEventListener("click", sendVoiceToken);
document.getElementById("otp").addEventListener("submit", validateToken);
document.getElementById("login")
.addEventListener("submit", (event) => sendVerificationToken(event));
document.getElementById("retry").addEventListener("click", (event) => retrySend(event));
document.getElementById("call").addEventListener("click", (event) => sendVoiceToken(event));
document.getElementById("otp").addEventListener("submit", (event) => validateToken(event));
</script>
</html>
39 changes: 22 additions & 17 deletions verify-retry/functions/check-verify.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,33 +20,39 @@ const { detectMissingParams, VerificationException } = require(
assets['/utils.js'].path
);

async function checkVerification(client, service, to, code) {
/**
* Checks the verification status of a given code for a specified service and recipient.
*
* @param {Object} client - The client object used to interact with the verification service.
* @param {string} service - The ID of the verification service.
* @param {string} to - The recipient's phone number or email address.
* @param {string} code - The verification code to check.
* @returns {Promise<string>} - A promise that resolves to a success message if the verification is approved.
* @throws {VerificationException} - Throws an exception if the verification code is incorrect.
*/
async function verifyToken(client, service, to, code) {
const check = await client.verify
.services(service)
.verificationChecks.create({
to,
code,
});
.verificationChecks.create({ to, code });

if (check.status === 'approved') {
return 'Verification success.';
// eslint-disable-next-line no-else-return
} else {
throw new VerificationException(401, 'Incorrect token.');
}

throw new VerificationException(401, 'Incorrect token.');
}

/**
* Twilio Function to handle verification check.
*
* @param {Object} context - The context object containing environment variables.
* @param {Object} event - The event object containing the request parameters.
* @param {Function} callback - The callback function to return the response.
*/
exports.handler = async function (context, event, callback) {
const response = new Twilio.Response();
response.appendHeader('Content-Type', 'application/json');

/*
* uncomment to support CORS
* response.appendHeader('Access-Control-Allow-Origin', '*');
* response.appendHeader('Access-Control-Allow-Methods', 'POST, OPTIONS');
* response.appendHeader('Access-Control-Allow-Headers', 'Content-Type');
*/

try {
const missingParams = detectMissingParams(['to', 'code'], event);
if (missingParams.length > 0) {
Expand All @@ -59,8 +65,7 @@ exports.handler = async function (context, event, callback) {
const client = context.getTwilioClient();
const service = context.VERIFY_SERVICE_SID;
const { to, code } = event;

const message = await checkVerification(client, service, to, code);
const message = await verifyToken(client, service, to, code);

response.setStatusCode(200);
response.setBody({
Expand Down
28 changes: 19 additions & 9 deletions verify-retry/functions/start-verify.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* Start Verification
*
* This Function shows you how to send a verification token for Twilio Verify.
* This Twilio Function shows you how to send a verification token for Twilio Verify.
*
* Pre-requisites
* - Create a Verify Service (https://www.twilio.com/console/verify/services)
Expand All @@ -17,11 +17,20 @@
* }
*/

// Load assets from the Runtime, used to access utility functions
const assets = Runtime.getAssets();
const { detectMissingParams, VerificationException } = require(
assets['/utils.js'].path
);

/**
* Retrieves the line type of a given phone number with the Lookup API.
*
* @param {Object} client - The Twilio client.
* @param {string} to - The phone number to check.
* @returns {Promise<string>} - The line type of the phone number.
* @throws {VerificationException} - If the phone number is invalid.
*/
async function getLineType(client, to) {
try {
const response = await client.lookups.v2
Expand All @@ -37,17 +46,18 @@ async function getLineType(client, to) {
}
}

/**
* This function handles the verification process.
* It sends a verification token to the provided phone number with the Verify API.
*
* @param {Object} context - The context object containing environment variables and Twilio client.
* @param {Object} event - The event object containing the request parameters.
* @param {Function} callback - The callback function to return the response.
*/
exports.handler = async function (context, event, callback) {
const response = new Twilio.Response();
response.appendHeader('Content-Type', 'application/json');

/*
* uncomment to support CORS
* response.appendHeader('Access-Control-Allow-Origin', '*');
* response.appendHeader('Access-Control-Allow-Methods', 'POST, OPTIONS');
* response.appendHeader('Access-Control-Allow-Headers', 'Content-Type');
*/

try {
const missingParams = detectMissingParams(['to'], event);
if (missingParams.length > 0) {
Expand All @@ -63,7 +73,7 @@ exports.handler = async function (context, event, callback) {

const lineType = await getLineType(client, to);

let channel = typeof event.channel === 'undefined' ? 'sms' : event.channel;
let channel = event.channel ?? 'sms';
let message = `Sent ${channel} verification to: ${to}`;

if (lineType === 'landline') {
Expand Down
Loading