alexa / alexa-skills-kit-sdk-for-nodejs

The Alexa Skills Kit SDK for Node.js helps you get a skill up and running quickly, letting you focus on skill logic instead of boilerplate code.
Apache License 2.0
3.12k stars 737 forks source link

DefaultApiClient creating empty object #599

Closed michael-sanderson closed 4 years ago

michael-sanderson commented 4 years ago

Hi,

using ask-sdk-core I have the following code for a custom skill:

const skillBuilder = Alexa.SkillBuilders.custom()

exports.handler = skillBuilder
  .addRequestHandlers(
    LaunchRequestHandler,
    CreateReminderIntentHandler,
    WhichBinIntentHandler,
    HelpIntentHandler,
    CancelAndStopIntentHandler,
    SessionEndedRequestHandler,
  )
  .addErrorHandlers(ErrorHandler)
  .withApiClient(new Alexa.DefaultApiClient())
  .lambda()

But I am getting the following error:

Cannot read property 'apiEndpoint' of undefined and it turns out this is because serviceClientFactory is empty in the handlerInput as a result of DefaultApiClient making an empty object?

At a complete loss here.

hideokamoto commented 4 years ago

Could you share your code of the package.json?

michael-sanderson commented 4 years ago

I am using Lambda layers - the ask-sdk-core package.json is:

  "name": "ask-sdk-core-layer",
  "version": "1.0.0",
  "description": "A layer for aws alexa core",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "Michael Sanderson",
  "license": "ISC",
  "dependencies": {
    "ask-sdk-core": "^2.7.0",
    "ask-sdk-model": "^1.25.1"
  }
}
michael-sanderson commented 4 years ago

I also have a layer for csv-parser

ShenChen93 commented 4 years ago

Hi @michael-sanderson

I used the same version of ask-sdk-core and tried to reproduce the issue. I write some simple code to get my isp info by using monetizationServiceClient, which is retrieved by using handlerInput.serviceClientFactory.

const LaunchRequestHandler = {
  canHandle(handlerInput) {
    return handlerInput.requestEnvelope.request.type === 'LaunchRequest';
  },
  async handle(handlerInput) {
    const ms = handlerInput.serviceClientFactory.getMonetizationServiceClient()
    const locale = 'en-US'
    const speechText = 'Welcome to the Alexa Skills Kit, you can say hello!';
    const result = await ms.getInSkillProducts(locale)
    console.log(result);

    return handlerInput.responseBuilder
      .speak(speechText)
      .reprompt(speechText)
      .getResponse();
  },
};

exports.handler = skillBuilder
  .addRequestHandlers(
    LaunchRequestHandler,
    ByeIntentHandler,
  )
  .addErrorHandlers(ErrorHandler)
  .withApiClient(new Alexa.DefaultApiClient())
  .lambda();

And I can successfully get the Alexa service response

{ inSkillProducts: [], nextToken: null, truncated: false }

Your case seems weird. As I cannot reproduce with the current info, could you please paste some code here show how you use the serviceClientFactory ?

michael-sanderson commented 4 years ago

So the below code attempts to make a reminders client by invoking: handlerInput.serviceClientFactory.getReminderManagementServiceClient()

const CreateReminderIntentHandler = {
      canHandle(handlerInput) {
        return handlerInput.requestEnvelope.request.type === C.intentRequest
          && handlerInput.requestEnvelope.request.intent.name === C.yesIntent
      },
      async handle(handlerInput) {
        const remindersApiClient = handlerInput.serviceClientFactory.getReminderManagementServiceClient(),
          { permissions } = handlerInput.requestEnvelope.context.System.user

        if(!permissions) {
          return handlerInput.responseBuilder
            .speak("Please enable reminders permissions in the Amazon Alexa app")
            .withAskForPermissionsConsentCard(["alexa::alerts:reminders:skill:readwrite"])
            .getResponse()
        }
        try {
          const now = new Date()
          const nextMonday = new Date(now.setDate(now.getDate() + (1 + 7 - now.getDay()) % 7))
          const reminderParams = {
            requestTime : now,
            trigger: {
            type : 'SCHEDULED_ABSOLUTE',
            scheduledTime : new Date(nextMonday.setHours(20,00,00,00)),
            timeZoneId : 'Europe/Belfast',
            recurrence : {                     
              freq : 'WEEKLY',               
              byDay: ['Mo']                 
              }
            },
            alertInfo: {
              spokenInfo: {
                content: [{
                  locale: 'en-US', 
                  text: await getBinResponse()
                }]
              }
            },
            pushNotification : {                            
              status : 'ENABLED'
            }
          }
          await remindersApiClient.createReminder(reminderParams)
          return handlerInput.responseBuilder
            .speak(C.reminderSetMessage)
            .getResponse()
      } catch (error) {
          return handlerInput.responseBuilder
            .speak(C.reminderError)
            .getResponse()
        }
      }
    }

The skillbuilder passes the api client via the following from index.js:

const Alexa = require('ask-sdk-core')

const csv = require('csv-parser')

const initGetBinResponse = require('./helpers/getBinResponse')

const buildBinMessage = require('./helpers/buildBinMessage')
const C = require('./constants')
const getHandlers = require('./helpers/getHandlers')
const s3 = require('./aws')

const log = console.log

const getBinResponse = initGetBinResponse(
  buildBinMessage,
  C,
  csv,
  log,
  s3
)

const {
  LaunchRequestHandler,
  CreateReminderIntentHandler,
  HelpIntentHandler,
  CancelAndStopIntentHandler,
  SessionEndedRequestHandler,
  ErrorHandler
} = getHandlers(C, getBinResponse)

const skillBuilder = Alexa.SkillBuilders.custom()

exports.handler = skillBuilder
  .addRequestHandlers(
    LaunchRequestHandler,
    CreateReminderIntentHandler,
    HelpIntentHandler,
    CancelAndStopIntentHandler,
    SessionEndedRequestHandler,
  )
  .addErrorHandlers(ErrorHandler)
  .withApiClient(new Alexa.DefaultApiClient())
  .lambda()

The problem is that invoking DefaultApiClient is building an empty object. I know this because logging it out serviceClientFactor gives me:

ServiceClientFactory {
  apiConfiguration: {
    apiClient: DefaultApiClient {},
    apiEndpoint: undefined,
    authorizationValue: undefined
  }

As a result of this, I get the following error when trying to call the reminders api endpoint Cannot read property 'apiEndpoint' of undefined

I dont know whether it helps but here is the Alexa object in its entirety.

{
  AttributesManagerFactory: [Function: AttributesManagerFactory] { init: [Function] },
  ImageHelper: [Function: ImageHelper],
  PlainTextContentHelper: [Function: PlainTextContentHelper],
  ResponseFactory: [Function: ResponseFactory] { init: [Function] },
  RichTextContentHelper: [Function: RichTextContentHelper],
  TextContentHelper: [Function: TextContentHelper],
  DefaultApiClient: [Function: DefaultApiClient],
  Skill: [Function: CustomSkill],
  BaseSkillFactory: [Function: BaseSkillFactory] { init: [Function] },
  CustomSkillFactory: [Function: CustomSkillFactory] { init: [Function] },
  SkillBuilders: { custom: [Function: custom] },
  getViewportDpiGroup: [Function: getViewportDpiGroup],
  getViewportOrientation: [Function: getViewportOrientation],
  getViewportProfile: [Function: getViewportProfile],
  getViewportSizeGroup: [Function: getViewportSizeGroup],
  ViewportDpiGroupOrder: [ 'XLOW', 'LOW', 'MEDIUM', 'HIGH', 'XHIGH', 'XXHIGH' ],
  ViewportSizeGroupOrder: [ 'XSMALL', 'SMALL', 'MEDIUM', 'LARGE', 'XLARGE' ],
  escapeXmlCharacters: [Function: escapeXmlCharacters],
  getAccountLinkingAccessToken: [Function: getAccountLinkingAccessToken],
  getApiAccessToken: [Function: getApiAccessToken],
  getDeviceId: [Function: getDeviceId],
  getUserId: [Function: getUserId],
  getDialogState: [Function: getDialogState],
  getIntentName: [Function: getIntentName],
  getLocale: [Function: getLocale],
  getRequestType: [Function: getRequestType],
  getSlot: [Function: getSlot],
  getSlotValue: [Function: getSlotValue],
  getSupportedInterfaces: [Function: getSupportedInterfaces],
  isNewSession: [Function: isNewSession],
  createAskSdkError: [Function: createAskSdkError],
  createAskSdkUserAgent: [Function: createAskSdkUserAgent]
}
michael-sanderson commented 4 years ago

I believe the problem lies in the fact I am using lambda layers but not sure why.

ShenChen93 commented 4 years ago

Hi, @michael-sanderson Thanks for replying and providing the necessary info.

From the serviceClient object log you provided, it seems apiEndpoint and authorizationValue is missing. From our source code here, our SDK directly copy the apiEndpoint and apiAccessToken from original Alexa request and paste them into the serviceClientFactory. Hence I guess those info are missing in your requestEnvelope, you could directly do console.log(handlerInput.requestEnvelope) to confirm it.

If it is true that those info are not exist in the requestEnvelope, there could be multiple reasons, I am not sure if lambda layer will influence the original request, could you try to test it without lambda layer and see if problem still exist ? Also I am curious the way how you do the test, are you using the developer console and did the simulation ? If so could you also check if apiEndpoint and accessToken are exist in the original request ? I find one similar issue can be a reference here

Please let me know the result of your investigation :)

Thanks, Shen

michael-sanderson commented 4 years ago

Yeah so this answers my questions. My test object in Lambda, which is how I am testing it, doesn't include this information. So I need to add this to the test object.

The problem I have been describing is unique to testing in the lambda console so my guess is the problem doesnt actually replicate in live. I will check this and confirm