|
| 1 | +const axios = require('axios'); |
| 2 | +const qs = require('qs'); |
| 3 | + |
| 4 | +// The root URL for an API which is known to fail on occasion |
| 5 | +const unstableSpaceUri = 'https://unstable-5604.twil.io'; |
| 6 | +// We'll declare these functions outside of the handler since they have no |
| 7 | +// dependencies on other values, and this will tidy up our pRetry calls. |
| 8 | +const astronautRequest = () => axios.get(`${unstableSpaceUri}/astros`); |
| 9 | +const issRequest = () => axios.get(`${unstableSpaceUri}/iss`); |
| 10 | +// Use a common object for retry configuration to DRY up our code :) |
| 11 | +const retryConfig = (reqName) => ({ |
| 12 | + retries: 3, |
| 13 | + onFailedAttempt: () => console.log(`Retrying ${reqName}...`), |
| 14 | +}); |
| 15 | + |
| 16 | +exports.handler = async (context, event, callback) => { |
| 17 | + // We need to asynchronously import p-retry since it is an ESM module |
| 18 | + const { default: pRetry } = await import('p-retry'); |
| 19 | + // Create a new voice response object |
| 20 | + const twiml = new Twilio.twiml.VoiceResponse(); |
| 21 | + |
| 22 | + try { |
| 23 | + // Create a promise with retry for each API call that can be made |
| 24 | + // independently of each other |
| 25 | + const getAstronauts = pRetry(astronautRequest, retryConfig('astros')); |
| 26 | + const getIss = pRetry(issRequest, retryConfig('iss')); |
| 27 | + // pRetry returns a promise, so we can still use Promise.all to await |
| 28 | + // the result of both requests in parallel with retry and backoff enabled! |
| 29 | + const [astronauts, iss] = await Promise.all([getAstronauts, getIss]); |
| 30 | + |
| 31 | + const { number, people } = astronauts.data; |
| 32 | + const { latitude, longitude } = iss.data.iss_position; |
| 33 | + |
| 34 | + const names = people.map((astronaut) => astronaut.name).sort(); |
| 35 | + |
| 36 | + // We can use reverse geocoding to convert the latitude and longitude |
| 37 | + // of the ISS to a human-readable location. We'll use positionstack.com |
| 38 | + // since they provide a free API. |
| 39 | + // Be sure to set your positionstack API key as an environment variable! |
| 40 | + const { data: issLocation } = await pRetry( |
| 41 | + () => |
| 42 | + axios.get( |
| 43 | + `http://api.positionstack.com/v1/reverse?${qs.stringify({ |
| 44 | + access_key: context.POSITIONSTACK_API_KEY, |
| 45 | + query: `${latitude},${longitude}`, |
| 46 | + })}` |
| 47 | + ), |
| 48 | + retryConfig('iss location') |
| 49 | + ); |
| 50 | + |
| 51 | + const { label } = issLocation.data[0] || 'an unknown location'; |
| 52 | + |
| 53 | + // Create a list formatter to join the names with commas and 'and' |
| 54 | + // so that the played speech sounds more natural |
| 55 | + const listFormatter = new Intl.ListFormat('en'); |
| 56 | + |
| 57 | + twiml.say(`There are ${number} people in space.`); |
| 58 | + twiml.pause({ length: 1 }); |
| 59 | + twiml.say(`Their names are: ${listFormatter.format(names)}`); |
| 60 | + twiml.pause({ length: 1 }); |
| 61 | + twiml.say( |
| 62 | + `Also, the International Space Station is currently above ${label}` |
| 63 | + ); |
| 64 | + // Return the final TwiML as the second argument to `callback` |
| 65 | + // This will render the response as XML in reply to the webhook request |
| 66 | + // and result in the message being played back to the user |
| 67 | + return callback(null, twiml); |
| 68 | + } catch (error) { |
| 69 | + // In the event of an error, return a 500 error and the error message |
| 70 | + console.error(error); |
| 71 | + return callback(error); |
| 72 | + } |
| 73 | +}; |
0 commit comments