1

I am trying to make a Google Home app and I am using webhooks with express.js running on a local server as fulfilment. I would like to use the "request.body.originalDetectIntentRequest.payload.user.userStorage" parameter from the request to identify a user when the webhook sends a post request to my server. The userId given with this request is equal to a log-filename that I store locally for each user where I keep track of different things per user.

I got the idea of handling things like this from an earlier post here on stack overflow. In the answer given by Prisoner, he talks about the importance of the response that you have to send back. So far so good, I tried to implement this in my code by sending a response once the new userId is made but I keep getting the same error :

(node:21236) UnhandledPromiseRejectionWarning: Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
    at ServerResponse.setHeader (_http_outgoing.js:518:11)

So it probably has to do with the fact that I have a line agent.handleRequest(intentMap); at the end of my code which will handle the request and send a response back to the user itself.

How do I solve this? I already tried to find a solution for this problem but the after trying different things, there is only one thing I can think of which would be to expand the response before sending it with the payload.google.userStorage variable even though I have tried response.write but that was giving me the same error.

My code:

  app.post('/', express.json(), (request, response) => {
  const agent = new WebhookClient({ request, response });
  console.log('Dialogflow Request headers: ' + JSON.stringify(request.headers));
  console.log('Dialogflow Request body: ' + JSON.stringify(request.body));
  
  let userStorage = request.body.originalDetectIntentRequest.payload.user.userStorage || JSON.stringify({});
  let userId;
  console.log("userStorage", userStorage);
  userStorage = JSON.parse(userStorage);
  console.log("userStorage_after_parsing", userStorage);
  if (userStorage.hasOwnProperty('userId')) {
      userId = userStorage.userId;
      console.log('user was already defined:' + userId);
  } else {
    let uuid = require('uuid/v4');
    checkNewUser = true;
    userId = uuid();
    userStorage.userId = userId
    console.log(userId);
    response.send({
      'payload': {
        'google': {
          'userStorage': JSON.stringify(userStorage)
        }
      }
    }
    );
//rest of the code
agent.handle(intentMap)

UPDATED QUESTION:

After prisoner's answer, I decided to include the code of the handleWelcome intent where I would like to paste the code into to extend the response with the the userStorage variable:

      function handleWelcome(agent) {
    //code to add userStorage to the response?
    if (checkIfUserVerified() === true) {
      agent.add('Hi.');
      //updateLog('User just said hi.');
      let userObj = readJsonSync(userId + '.json');
      userObj.logs.push('User just started talking to this chatbot ' + getCurrentDateTime());
      fs.writeFileSync(userId + '.json', JSON.stringify(userObj));
    }
    else {
      close('Please make sure you are a verified user before using this version of SMART4MD. To do so, read the SMART4MD Voice assistant manual.');
      agent.setFollowupEvent('GOODBYE');
    }
  } 
Edevont
  • 73
  • 6

1 Answers1

1

Note the comment in the answer you cite where it says

// Make sure you include the userStorage as part of the response

"part of" the response - not the response itself. In your code, you're sending back the JSON to sent the user storage before your Intent Handler is called. Once something is sent, the connection is closed, so nothing further can be sent - including the reply you want. Which is why you get the error.

You don't show any of your Intent Handler functions, so it is difficult to give you an exact answer, but the general solution would be to set the userid in userStorage as part of a function that each handler calls.

Prisoner
  • 49,922
  • 7
  • 53
  • 105
  • Thanks for this comment! If I understand you correctly, it is possible to add the userStorage variable to reply INSIDE the function that is handled when I, for example, trigger the welcome intent? The welcome intent is triggered automatically when I talk to my google home speaker and say 'hey google, talk to...' so that would be the perfect function to add this part of the response to if I am not mistaken? I will update my question with the handleWelcome function which is triggered when the welcome intent was recognized. – Edevont Jun 20 '20 at 14:09
  • I added the function to my question. My question kind of remains the same tho. How do I add something to the response before it is send? – Edevont Jun 20 '20 at 18:42
  • after some trail and error I still don't seem to understand how to add the userStorage variable to the response. I tried to do it like 'response.payload.google = JSON.stringify(userStorage) but now I get the error 'cannot read property google of undefined'. Eventho, every webhook response body contains a payload if I am not mistaken? You declare it as an object in the original post, but what should I do with that object in order to really append the response? – Edevont Jun 20 '20 at 18:56
  • do you have any idea how to add this variable to the response? I have been trying agent.add, been looking into also using the actions-on-google library to make app.intent(conv => so I can use the conv object to store it in but this gives me other errors because I am still using the dialogflow library... I am out of ideas please enlighten me. – Edevont Jun 21 '20 at 13:21