Cake Walk

Build an Engaging Alexa Skill

Using the Alexa Settings API

In this section, you will enable the Cake Walk skill to calculate the number of days until the user's next birthday.

Use the Alexa developer console for this module. Log in to the console and open the Cake Walk skill.

Step 1: Get and pass device ID to API

To calculate the number of days until the user’s next birthday, we need additional information. Luckily, you can use the Alexa Settings API to get this information. To query the API, you need to provide the device ID for the Alexa-enabled device that prompted the Cake Walk skill to open.

The device ID is provided in every request that comes to the skill code. Get the device ID using the requestEnvelope with the following piece of code:

handlerInput.requestEnvelope.context.System.device.deviceId

The SDK provides a utility function that simplifies getting the device ID. Feel free to use it instead: const deviceId = Alexa.getDeviceId(handlerInput.requestEnvelope). 

For additional information, refer to ASK SDK Utilities.

a. In the developer console, click the Code tab.

b. Find the HasBirthdayLaunchRequestHandler and then the handle() function within the handler. Create a new line just above the line that begins const attributesManager. Copy and paste in the following code:

const deviceId = handlerInput.requestEnvelope.context.System.device.deviceId;

Now the Cake Walk skill needs to pass the captured device ID to the Alexa Settings API. How do you do that?

You need four things:

  • URL for the API
  • Authorization token
  • Device ID
  • Library to make the call

Thankfully, the SDK has the built-in ServiceClient to get these things. You only need to configure and use the ServiceClient.

c. Scroll down in the code until you find the line that begins exports.handler. Create a new line just below it, and copy and paste in the following code:

.withApiClient(new Alexa.DefaultApiClient())

Now that the ServiceClient is built, your code has the four pieces needed to pass the device ID to the API.

d. Click Save.

Step 2: Build the ServiceClient factory

To use the ServiceClient, you will create what is referred to as a factory. The purpose of a factory is to build things. You will use it to build the ServiceClient.

a. Find the HasBirthdayLaunchRequestHandler and then the handle() function within the handler. Create a new line just above the line that begins const deviceId. Copy and paste in the following code:

const serviceClientFactory = handlerInput.serviceClientFactory;

That section of code should now look like the following:

b. Click Save.

Step 3: Retrieve the time zone

There’s a chance that an error can happen when the code makes a call to the Alexa Settings API. For example, if the API takes too long to respond, the code could time out. Therefore, you need to wrap the code in a try/catch block. A try/catch block is a way to ensure the skill code doesn't crash if it encounters an error. You will wrap the code that could crash in a try block. If the code within that block crashes, the catch block will run to handle errors.

You want to know the time zone for the user's Alexa-enabled device. In the try block, use serviceClientFactory to get the settings service client—upsServiceClient—and pass the device ID to the getSystemTimeZone function to get the time zone. The catch block will log an error message using console.log and return an error message response that Alexa will say to the user.

a. Find the HasBirthdayLaunchRequestHandler and then the handle() function within the handler. Create a new line just below the line that begins const day = sessionAttributes. Copy and paste in the following code:

let userTimeZone;
try {
const upsServiceClient = serviceClientFactory.getUpsServiceClient();
userTimeZone = await upsServiceClient.getSystemTimeZone(deviceId);
} catch (error) {
if (error.name !== 'ServiceError') {
return handlerInput.responseBuilder.speak("There was a problem connecting to the service.").getResponse();
}
console.log('error', error.message);
}

Because we're using the API to get the time zone, it could take a while to get a response. Update the code to fetch that information asynchronously.

Previously, you have used the async and await keywords. You will use keywords here to get the time zone from the API asynchronously. The await keyword is included in the try/catch block you just added. Now, add the async keyword to the handle() function.

b. Within the HasBirthdayLaunchRequestHandler, find the handle() function. In front of the function, type async followed by a space.

That section of code should now look like the following:

async handle(handlerInput) {
const serviceClientFactory = handlerInput.serviceClientFactory;
const deviceId = handlerInput.requestEnvelope.context.System.device.deviceId;

c. Click Save.

Step 4: Retrieve the current date

The code will use the time zone to get the current date. You can use the currentDateTime function to return the correct date according to the time zone captured from the user's device. You will add this function below the try/catch block added in a previous step.

a. Within the HasBirthdayLaunchRequestHandler, in the handle() function, find the line that begins with const speechText = `Welcome back. Create a new line just above it. Copy and paste in the following code:

// getting the current date with the time
const currentDateTime = new Date(new Date().toLocaleString("en-US", {timeZone: userTimeZone}));

b. Click Save.

Step 5: Extract the month, day, and year

You would like the Cake Walk skill to wish the user happy birthday at midnight in their time zone. This could be a problem because currentDateTime provides the date and time to the second. The Cake Walk skill does not ask the user to provide their birthday down to the second. Therefore, the code needs to extract just the month, day, and year from currentDateTime, and then re-create the date without the seconds included.

a. Within the HasBirthdayLaunchRequestHandler, in the handle() function, create a new line just below the code you just added (const currentDateTime). Copy and paste in the following code:

// removing the time from the date because it affects our difference calculation
const currentDate = new Date(currentDateTime.getFullYear(), currentDateTime.getMonth(), currentDateTime.getDate());
const currentYear = currentDate.getFullYear();

b. Click Save.

Step 6: Determine the user's next birthday

Now the code needs to determine the user's next birthday. First, the code will combine the year and month of their birthday with the current year. Second, the code will determine if the user's birthday has already passed this calendar year. If it has, the code will add a year to the value of their next birthday.

a. Within the HasBirthdayLaunchRequestHandler, in the handle() function, create a new line just below the lines you just added (const currentDate and const currentYear). Copy and paste in the following code:

// getting the next birthday
let nextBirthday = Date.parse(`${month} ${day}, ${currentYear}`);

// adjust the nextBirthday by one year if the current date is after their birthday
if (currentDate.getTime() > nextBirthday) {
nextBirthday = Date.parse(`${month} ${day}, ${currentYear + 1}`);
}

b. Click Save.

Step 7: Compute difference between current date and user's next birthday

Now that the code has the current date and the date of the user's next birthday, it's time to compute the difference. First, the code needs to convert each date into Unix epoch time (the number of seconds elapsed since 00:00:00 January 1, 1970, Coordinated Universal Time (UTC), minus leap seconds).

Second, the code will calculate the difference in milliseconds between the two dates and take the absolute value of the difference.

Finally, the code will convert the difference in milliseconds back to days. One day in milliseconds = 24 hours X 60 minutes X 60 seconds X 1000 milliseconds.

The following is how this would appear in the code—but don’t add it to the code yet:

const oneDay = 24*60*60*1000;
const diffDays = Math.round(Math.abs((currentDate.getTime() - nextBirthday)/oneDay));

The code only needs to calculate the difference when it's not the users birthday. Therefore, you will wrap this code in an if statement that checks if the current date is the user's birthday.

If it is the user's birthday, you want the skill to wish them happy birthday. You can combine the code for this with the if statement so when it is not the user's birthday, the speechText is set to tell the user how many days until their next birthday.

a. Within the HasBirthdayLaunchRequestHandler, in the handle() function, find the line that begins with const speechText = `Welcome back. Replace that line with the following code:

const oneDay = 24*60*60*1000;

// setting the default speechText to Happy xth Birthday!
// Don't worry about when to use st, th, rd--Alexa will automatically correct the ordinal for you.
let speechText = `Happy ${currentYear - year}th birthday!`;
if (currentDate.getTime() !== nextBirthday) {
const diffDays = Math.round(Math.abs((currentDate.getTime() - nextBirthday)/oneDay));
speechText = `Welcome back. It looks like there are ${diffDays} days until your ${currentYear - year}th birthday.`
}

b. Click Save.

Step 8: Save, deploy, and test

a. Click Deploy to build the skill.

b. Go to the Test tab, open the skill, and see if Alexa responds by telling you how many days until your next birthday. If she does, congratulations!

Code

If your skill isn't working or you're getting some kind of syntax error, download the following working code sample. Go to the Code tab in the Alexa developer console and copy and paste the code into the index.js file. Be sure to save and deploy the code before testing it.