Publishing Proactive Events

Proactive events give a skill the ability to notify users of significant information out-of-session, even though the user isn’t actively using the skill. When the notification is received, all of the user’s Alexa devices will chime and display a visual indication of pending notifications (a pulsing yellow ring on Echo and Echo Dot devices, for instance). The user can then ask Alexa to read the notifications by saying, “Alexa, read notifications.”

A common example of proactive events is when an Amazon order has been delivered. If the user has notifications enabled, their Alexa devices will notify them of the delivery. But proactive events aren’t limited to Amazon orders. Any skill can take advantage of them to communicate updates and events to its users.

Proactive events come in eight predefined schemas:[38]

  • Media Content Alerts—Alert users about upcoming book, television episode, album, music single, movie, or game release

  • Message Alerts—Alert users about messages (e.g, text or voice message) they have received

  • Occasions—Notifies user about upcoming reservations or appointments

  • Order Status—Notifies user about order status changes

  • Social Game Invitations—Invites a user to join another user in playing some game

  • Sports Events—Updates users regarding a sporting event, such as scores from a soccer match

  • Trash Collection Alerts—Alerts users regarding trash collection dates

  • Weather Alerts—Alerts users regarding significant weather events

As you can see, each of these schemas targets a specific domain. Sending a user scores of a sporting event is very different than alerting a user when their trash will be collected. When using proactive events, it’s important to select the schema that is most suitable for your skill’s needs.

For the purposes of Star Port 75 Travel, the Occasions schema is most relevant and we’ll use it to notify users regarding their trip’s reservation. To get started, we’ll enable proactive events in the skill’s manifest.

Enabling Proactive Events

In order for a skill to send events to its users, the events must be declared in the skill’s manifest, under the events property:

 "events"​: {
 "publications"​: [
  { ​"eventName"​: ​"AMAZON.Occasion.Updated"​}
  ],
 "endpoint"​: {
 "uri"​: ​"arn:aws:lambda:us-east-1:4944..."
  }
 }

Here, under the publications sub-property, we have declared that this skill will be able to send occasion events. Notice that publications is an array and can declare one or more entries where eventName is one of the following:

  • AMAZON.MediaContent.Available
  • AMAZON.MessageAlert.Activated
  • AMAZON.Occasion.Updated
  • AMAZON.OrderStatus.Updated
  • AMAZON.SocialGameInvite.Available
  • AMAZON.SportsEvent.Updated
  • AMAZON.TrashCollectionAlert.Activated
  • AMAZON.WeatherAlert.Activated

We also must declare the skill’s endpoint URI. If you’re not sure what the skill’s endpoint URI is, you can easily look it up using the ASK CLI like this:

 $ ask smapi ​get​-skill-manifest
  --skill-id amzn1.ask.skill.28e3f37-2b4-4b5-849-bcf4f2e081
 {
 "manifest"​: {
 "apis"​: {
 "custom"​: {
 "endpoint"​: {
»"uri"​: ​"arn:aws:lambda:us-east-1:4944..."
  }
  }
  },
  ...
 }

The ask smapi get-skill-manifest command requires that the skill ID be passed in via the --skill-id parameter. The skill’s endpoint URI is found near the top of the response, under the manifest.apis.custom.endpoint.uri property. Just copy the value of the property into the events.endpoint.uri property in skill.json when declaring events.

Now the skill declares that it can send proactive events. But it must also be given permission to do so. Let’s see how to configure the skill with permission to send events and how to ask the user for permission.

Asking for Permission to Send Events

We’ve had a little experience asking our skill users for permission. In Chapter 5, Integrating User Data, we configured the skill’s manifest with permissions for fetching the user’s given name and email, then returned a permissions consent card for the user to grant those permissions.

Permissions for sending proactive events are established in exactly the same way. First, we need to add a new entry to the permissions section of skill.json:

 "permissions": [ { "name": "alexa::profile:given_name:read" }, { "name": "alexa::profile:email:read" },
» {
» "name": "alexa::devices:all:notifications:write"
» }
 ]

The newly created permission, alexa::devices:all:notifications:write, declares that our skill may ask the user for permission to write notifications to the user’s Alexa devices.

Now all we must do is send a permissions consent card in the response of a request handler. Any of the skill’s request handlers could work, but it makes the most sense to ask for permission to send a reservation notification in response to a successfully scheduled trip. The following change to how the response is built in ScheduleTripIntentHandler will do the trick:

 return​ handlerInput.responseBuilder
  .speak(speakOutput)
» .withAskForPermissionsConsentCard(
» [​'alexa::devices:all:notifications:write'​])
  .withShouldEndSession(​true​)
  .getResponse();

The call to withAskForPermissionsConsentCard() replaces a call to withStandardCard() we had originally added in Chapter 9, Complementing Responses with Cards. Unfortunately, a response can only include one card and the permissions consent card is more important here than the informational standard card we were returning before.

The permissions consent card won’t do any good if the user doesn’t know to go to the Alexa companion application to grant permission. Therefore, we should also change the spoken text to tell the user to give notifications permission:

 SCHEDULED_MSG: ​"You're all set. Enjoy your trip to {{destination}}!"​ +
 "If you want, I can send you a notification when the reservation "​ +
 "is confirmed. I've sent a card to the Alexa application for you "​ +
 "to give permission for notifications."​,

With these changes in place, deploy the skill and try it out. Plan a trip to any place you’d like and confirm the trip plans. It shouldn’t behave much differently than before, although it should send a card to the Alexa companion application prompting you to grant permission for notifications:

images/events/NotificationPermissions.png

 

After clicking on the “Manage” button on the card, be sure that the “Alexa Notifications” checkbox is checked and click “Save Permissions”. Then the skill can send you proactive events any time it needs. Let’s see how to fire off an event to our skill’s users.

Dispatching Events

Although proactive events are sent from a skill to a user’s Alexa device(s), the events actually originate in an external system. This is typically some backend system for which the skill is a user interface.

We’ve not implemented an actual backend trip booking system for the Star Port 75 Travel skill. We used Google Calendar as a stand-in for a booking system in Chapter 5, Integrating User Data. In this chapter, we will create a NodeJS script to act as a stand-in for the backend system to send events through the skill. Although it will be a standalone NodeJS script, the approach taken can apply to any real-world application, regardless of the language.

Sending events through a skill to the user involves two steps:

  • Obtaining an access token
  • Sending an HTTP POST request to the Alexa API

Not just any application should be allowed to post notifications through the skill. Therefore, the Alexa API is secured as an OAuth2 resource server. Our script will need to obtain an access token that will allow it to submit notifications. The following Javascript code sends a POST request to Amazon’s authorization server’s token endpoint [39] to obtain an access token:

 const​ request = require(​'request-promise-native'​);
 const​ clientId = ​'amzn1.application-oa2-client.9160db0...'​;
 const​ clientSecret = ​'69fcab9...'​;
 const​ eventPublisher = {
 async​ getAccessToken(clientId, clientSecret) {
 const​ requestPayload = require(​'./tokenRequest.json'​);
  requestPayload.client_id = clientId;
  requestPayload.client_secret = clientSecret;
 
 const​ token = ​await​ request({
  method: ​'POST'​,
  uri: ​'https://api.amazon.com/auth/o2/token'​,
  headers: {
 'Content-type'​: ​'application/json'
  },
  body: JSON.stringify(requestPayload)
  });
 return​ JSON.parse(token).access_token;
  },
 };

The payload of the request is pulled in with a require() call from an external file named tokenRequest.json, which looks like this:

 {
 "grant_type"​: ​"client_credentials"​,
 "scope"​: ​"alexa::proactive_events"​,
 "client_id"​: ​""​,
 "client_secret"​: ​""
 }

Once the token request payload is loaded, the script replaces the values of the client_id and client_secret properties with actual client ID and secret values for our skill. You’ll need to replace the clientId and clientSecret properties in sendEvent.js with the actual client ID and secret from the skill. You can look them up using the ask smapi get-skill-credentials command:

 $ ​​ask​​ ​​smapi​​ ​​get-skill-credentials​​ ​​--skill-id=amzn1.ask.skill.28e36f37-...
 {
  "skillMessagingCredentials": {
  "clientId": "amzn1.application-oa2-client.916...",
  "clientSecret": "69fcab9..."
  }
 }

Once the POST request completes, the getAccessToken() function extracts the access token from the response and returns it to the caller. Speaking of the caller, the first thing that the sendOccasionEvent() function does is make a call to getAccessToken():

 const​ request = require(​'request-promise-native'​);
 const​ crypto = require(​"crypto"​);
 
 const​ clientId = ​'amzn1.application-oa2-client.9160db0...'​;
 const​ clientSecret = ​'69fcab9...'​;
 const​ userId = ​'amzn1.ask.account.AF3DEHA...'​;
 const​ reservationTime = ​"2020-03-10T00:00:00Z"​;
 const​ destination = ​"Jupiter"​;
 
 Date.prototype.addHours = ​function​(h) {
 this​.setTime(​this​.getTime() + (h*60*60*1000));
 return​ ​this​;
 }
 
 const​ eventPublisher = {
  ...
 
 async​ sendOccasionEvent(clientId, clientSecret, userId,
  reservationTime, destination) {
 const​ accessToken =
 await​ eventPublisher.getAccessToken(clientId, clientSecret);
 
 const​ requestPayload = require(​'./eventRequest.json'​);
  requestPayload.referenceId = crypto.randomBytes(16).toString(​'hex'​);
  requestPayload.timestamp = ​new​ Date().toISOString();
  requestPayload.expiryTime = ​new​ Date().addHours(1).toISOString();
  requestPayload.event.payload.occasion.bookingTime = reservationTime;
  requestPayload.relevantAudience.payload.user = userId;
  requestPayload.localizedAttributes.push({
  locale: ​'en-US'​,
  subject: ​`Trip to ​${destination}​`​,
  providerName: ​"Star Port 75 Travel"
  });
  requestPayload.localizedAttributes.push({
  locale: ​'es-ES'​,
  subject: ​`Viaje a ​${destination}​`​,
  providerName: ​"Star Port 75 Travel"
  });
 
 const​ eventsUri = ​'https://api.amazonalexa.com/v1/proactiveEvents'​;
 await​ request({
  method: ​'POST'​,
  uri: eventsUri + ​'/stages/development'​,
  headers: {
 'Content-type'​: ​'application/json'​,
 'Authorization'​: ​`Bearer ​${accessToken}​`
  },
  body: JSON.stringify(requestPayload)
  });
 
  console.log(​'Notification sent'​);
  }
 };
 
 eventPublisher.sendOccasionEvent(
  clientId, clientSecret, userId, reservationTime, destination);

With the access token in hand, sendOccasionEvent() uses require() to load the event request payload template into a constant. The template itself looks like this:

 {
 "referenceId"​: ​"TBD"​,
 "timestamp"​: ​"TBD"​,
 "expiryTime"​: ​"TBD"​,
 "event"​: {
 "name"​: ​"AMAZON.Occasion.Updated"​,
 "payload"​: {
 "state"​: {
 "confirmationStatus"​: ​"CONFIRMED"
  },
 "occasion"​: {
 "occasionType"​: ​"RESERVATION"​,
 "subject"​: ​"localizedattribute:subject"​,
 "provider"​: {
 "name"​: ​"localizedattribute:providerName"
  },
 "bookingTime"​: ​"TBD"
  }
  }
  },
 "localizedAttributes"​: [],
 "relevantAudience"​: {
 "type"​: ​"Unicast"​,
 "payload"​: {
 "user"​: ​"TBD"
  }
  }
 }

As you can see, there are several properties that are set to a placeholder value of “TBD”. Much of the request payload template is boilerplate, but a handful of the template’s properties will need to be replaced with actual values. Those properties are:

  • referenceId—A value that uniquely identifies the notification. This can contain any value and could be used to reference some entity in the backend system (such as an order ID or booking ID).

  • timestamp—The timestamp of the notification, typically set to the current time

  • expiryTime—When the notification will expire. Can be any time between 5 minutes from the current time and 24 hours from the current time. After the expiry time passes, the notification will be automatically deleted.

  • bookingTime—The time of the reservation or appointment

  • user—The skill user ID whose Alexa devices will receive the notification

Replacing the placeholder values on these properties is the very next thing that the sendOccasionEvent() function does once it has loaded the template. The referenceId property is generated randomly using the crypto module. The timestamp property is set to the current time. As for the expiryTime, it is set to the current time plus one hour (with help from the addHours() function added to the Date class).

The user and bookingTime properties are set from constants defined earlier in the script. You’ll need to set the reservationTime property to some date in the future. If this were an actual backend booking system, this would be the time of departure date of the schedule trip.

The userId property will need to be set to the skill user’s ID. Each skill user is assigned an ID that is unique to that user and to the skill. If this script were an actual backend system, this ID would need to be extracted from the requests’s session.user.userId property and sent to the backend when the trip is scheduled. When sending the notification, the user ID will be sent in the request. For demonstration purposes, the easiest way to get the user ID is to find it in the request JSON in the developer console’s JSON input box.

Looking back at the request template, notice that there are two properties whose values start with “localizedattribute:”: subject and provider.name:

 "occasion"​: {
 "occasionType"​: ​"RESERVATION"​,
»"subject"​: ​"localizedattribute:subject"​,
 "provider"​: {
»"name"​: ​"localizedattribute:providerName"
  },
 "bookingTime"​: ​"TBD"
 }

The subject property describes the subject of the notification, while the provider.name property describes who is responsible for creating the reservation. We could hard-code those values with “Trip to Jupiter” and “Star Port 75 Travel”, but then those values would be language specific. Instead, the values localizedattribute:subject and localizedattribute:providerName reference localized values in the template under the localizedAttributes property.

For example, suppose that the localizedAttributes property were set like this:

 "localizedAttributes"​: [
  {
 "locale"​: ​"en-US"​,
 "subject"​: ​"Trip to Jupiter"​,
 "providerName"​: ​"Star Port 75 Travel"
  },
  {
 "locale"​: ​"es-ES"​,
 "subject"​: ​"Viaje a Jupiter"​,
 "providerName"​: ​"Star Port 75 Travel"
  }
 ],

As shown here, if the notification were sent to a user whose device is configured for English, the subject would be “Trip to Jupiter”. But if the device is configured with Spanish as the chosen language, it would be “Viaje a Jupiter”. Even though the providerName won’t be different regardless of language, it is a required property in localized attribute entries.

But rather than hard-code either of those in the request template, the sendOccasionEvent() function adds them to the template so that the subject can contain the destination from the reservation. For our stand-in booking system, the sendOccasionEvent() function pushes English and Spanish entries into the localizedAttributes property like this:

 requestPayload.localizedAttributes.push({
  locale: ​'en-US'​,
  subject: ​`Trip to ​${destination}​`​,
  providerName: ​"Star Port 75 Travel"
 });
 requestPayload.localizedAttributes.push({
  locale: ​'es-ES'​,
  subject: ​`Viaje a ​${destination}​`​,
  providerName: ​"Star Port 75 Travel"
 });

Finally, the sendOccasionEvent() sends the payload in a POST request with the access token in the Authorization header. The request is sent to https://api.amazonalexa.com/v1/proactiveEvents/stages/development, which is the URI of the proactive events staging API for North America. There are two more staging URLs for Europe and the Far East:

Staging URLs ensure that no actual users will receive notifications while we test our skill. But once the skill is published to production, we’ll need to post notification requests to production URLs instead:

Once you’ve filled in values for the clientId, clientSecret, userId, reservationTime, and destination properties, you can run the script like this:

 $ ​​node​​ ​​sendEvent.js
 Notification sent

If all goes well, then all of the Alexa devices connected to your developer account will chime to indicate that you have a new notification. Say, “Alexa, what are my notifications?” and Alexa will read the notification to you. Otherwise, after one hour the notification will automatically be deleted.

Sending Multicast Events

In the relevantAudience.payload.user property, our script sends a reservation notification that targets a specific user by their skill user ID. That’s because the relevantAudience.type property is sent to “Unicast”.

But not all notifications are user-specific. For instance, if your skill is sending updates regarding the score of a basketball game, it would be very tedious to send the same notification to all of your skill’s users. Instead, you can broadcast the same event to all users of your skill by setting relevantAudience.type to “Multicast”:

 "relevantAudience": {
  "type": "Multicast",
  "payload": {}
 }

Since a multicast event isn’t user-specific, there’s no need to set the relevantAudience.payload.userId property. The relevantAudience.payload property, however, is still required and may be left empty.

Proactive events are great ways to inform a skill’s users of an event as it happens, such as the change of an order status or the confirmation of a reservation. On the other hand, sometimes it’s helpful for Alexa to remind users of an upcoming event. Let’s see how to use reminders to give users a heads-up on something that is about to happen.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset