Alexa-skills-kit-sdk-for-nodejs: Session is not cleared by setting this.handler.state = ''

Created on 2 Mar 2017  路  29Comments  路  Source: alexa/alexa-skills-kit-sdk-for-nodejs

if you set this.handler.state = '' and persist the state into DynamoDB the state will never get cleared, this is due to the following code:

':saveState': function(forceSave) {
if (this.isOverridden()) {
return;
}

        **if(forceSave && this.handler.state){
            this.attributes['STATE'] = this.handler.state;
        }**

notice that if you assign this.handler.state = '', then the if will not succeed. I am not sure exactly why this code is here. It may be that the && needs to be an ||. I believe the author is trying to prevent this.handler.state from being null (because Dynamo can't persist an empty string) or if state's aren't being used, then there isn't a reason to save the state. Maybe there needs to be a function to clear state or resetState, or maybe a parameter to saveState that is boolean for clearState. so something like this.

':saveState': function(forceSave, clearState) {
if (this.isOverridden()) {
return;
}

if (clearState) {
this.attributes['STATE'] = undefined;
} else {
if (forceSave && this.handler.state) {
this.attributes['STATE'] = this.handler.state;
}
}

I believe the above would be the least intrusive as many developers may already be working around this issue and may rely on this to function this way.

Most helpful comment

I also ran into this issue. I made the following change to response.js:

   ':saveState': function(forceSave) {
        if (this.isOverridden()) {
            return;
        }

        if(forceSave && this.handler.state){
            this.attributes['STATE'] = this.handler.state;
        }

        // Added this to enable clearing a state
        if ( this.handler.state == '' ) {
            this.attributes['STATE'] = undefined;
        }

All 29 comments

I'm also observing this issue that the state can't be switched back to ''

I also know that it's not currently possible to persist an empty string value into DynamoDB - so this may be why the code is structured like that.

I think it's probably best just to use the '' state for the first ever use for a user and not try and go back to it.

So your states could be something like:

var states = {
  FIRST_EVER_USE: ''
  MAIN_STATE: '_MAIN_STATE',
  SOME_OTHER_STATE: '_SOME_OTHER_STATE'
};

And in the FIRST_EVER_USE state you could just have a NewSession handler that gave some kind of welcome message and then switched to your MAIN_STATE:

var firstEverUseHandlers = Alexa.CreateStateHandler(states.FIRST_EVER_USE, {

  'NewSession': function () {
    this.handler.state = states.MAIN_STATE;
    this.emit(':ask', 'First ever use welcome message.', 'First ever use re-prompt.');
  }

});

As an UPDATE: In my code i just set this.attributes['STATE'] = undefined and that seemed to resolve the issue, but feel this is a work around. Also, MerryOscar posted above will also work, but again, it seems like working around the issue. There should be a way to clear state and start over. Maybe just need a ':clearState' event that just sets this.attributes['STATE'] = undefined.

I'm also experiencing this issue: this.handler.state = '' doesn't reset the state like it should, whereas putting anything but an empty string does... This is annoying given that if you want to use the 'LaunchRequest' model rather than the 'NewSession' model you need to have quite a few intents in your "blank" state anyway; not being able to go back to that "blank" state is annoying...

I also ran into this issue. I made the following change to response.js:

   ':saveState': function(forceSave) {
        if (this.isOverridden()) {
            return;
        }

        if(forceSave && this.handler.state){
            this.attributes['STATE'] = this.handler.state;
        }

        // Added this to enable clearing a state
        if ( this.handler.state == '' ) {
            this.attributes['STATE'] = undefined;
        }

I have also noticed the STATE: value cannot be reset, but also seems to be being ignored totally. I have the states...

var states = {
NOTINGAME: '',
INGAME: '_INGAME'
}

... and am finding...

'AMAZON.StartOverIntent': function ( ) {
this.emit('NewGameRequest');

...is actually invoking the NewGameHandler not NewGameHandler_INGAME as it should. Dumping the Session object clearly shows STATE: is set to '_INGAME" ...

attributes:
{ questionsAsked: [ 19, 35, [length]: 2 ],
qsWrong: 1,
questionNum: 2,
STATE: '_INGAME',
qsCorrect: 0,
correctAnswer: 3 },

...the only way I can invoke the right version of NewGameHandler is to code...

AMAZON.StartOverIntent': function ( ) {
this.emit('NewGameRequest_INGAME');

...which works OK, but is a workaround.

Fwiw I got this working with:

delete this.handler.state;
delete this.attributes['STATE'];
this.emit(':saveState', true);

This is with some intents in one state handler, and lots of others stateless. However, even if no state is set, I'm still seeing 'stateful' intents getting triggered, even though that should not happen I thought. Easy enough to duplicate the intent name, but not ideal. Also seeing weird behaviour in error cases: e.g. stateless error triggering stateful error handler. Seems that mixing of statefulness and statelessness is not really supported.

Hi everyone,

Thanks for all the discussion and interest in Alexa Node.js SDK.

The reported "reset state" or "clear state" feature is a known issue to the SDK.
The current design to ignore empty string is due to the known issue in AWS SDK. Deleting the "STATE" from this.attributes when user set this.handler.state to '' is also not very optimal as it intrinsically changed user behavior and might cause confusion.

For now, if user wants to explicitly reset state, the following code should be added:
JavaScript this.handler.state = '' \\ delete this.handler.state might cause reference errors delete this.attributes['STATE']; this.emit(':saveState', true);

I've labelled this as "feature request" and create tasks internally to track this issue.

Regards

Doesn't work for me.

However the following does:

this.handler.state = '';
delete this.attributes.STATE;

@Amazon-tianrenz , your post above seems to make sense and that setting the state to empty string is not supported. Should this be updated in the documentation, or at least removed from the readme and other examples to avoid confusion. I only tried this because I saw it in the readme examples.

var guessModeHandlers = Alexa.CreateStateHandler(states.GUESSMODE, {

    'NewSession': function () {
        this.handler.state = '';
        this.emitWithState('NewSession'); // Equivalent to the Start Mode NewSession handler
    },

@blueshirts
Yes, we are working on a new draft of ReadMe document that should address all existing problems.

@Amazon-tianrenz That would be great - I've found a few doc problems myself. Will the work-in-progress draft be on its own branch for now? In other words, is there a way we can see and comment?

Hi @talkingnews,
We are currently working on a private repository. All the published documents have to go through a review process so we can't make it public until the final revision is approved.
At the meantime, you can post ideas/suggestions for the documents here. We are actively tracking GitHub issues now and will take into consideration feedback from our developer community.

Regards

Had the same issue. Finally resolved by adding a new LAUNCH state handler similar to the guessmode described above. This took care of taking the user to a default/launch mode. Not ideal, but seems to do the job.

@Amazon-tianrenz
What's is the state on this?
I tried all the suggestions I found here but non of them worked. The state stays in the response and on the next request the intent handler won't be found because it does not exist on the state the skill was in before.

In the docs I read about the 'NewSession' intent, but that does not solve my issues - again, the state stays at the response.

Any help on this?

@tschuege,

Have you tried the method mentioned at the end of this section in readme.md?

Regards,

@Amazon-tianrenz
Yes, I did, but I could not get rid of a state once set.

@tschuege ,

Could you attach the your code for clearing state please?

Regards

@Amazon-tianrenz
I'm using ES6 classes for the handlers that have a method called to handle the intent. Since the alexa-sdk binds the call to the handler function replacing this I have to wrap it in a closure to save the handler (that has emit etc.). I store the alexa handler in the member alexaHandler - I think that should work because tests emitting speech works just fine.
So the method to reset the state simpli looks like this (in Typescript but that should not matter):

protected resetState() {
        this.alexaHandler.handler.state = '';
        delete this.alexaHandler.handler['STATE'];
 }

When debugging I can see, that alexaHandler.handler.state has the correct state but when I reset it it won't be reflected in the response. Therefore the next call will still contain the state and fail because there is no matching handler for that state.

Any Idea?

Thanks for your help!

@Amazon-tianrenz
it seems to work if i set it on session.attributes. In my case like that:

this.event.session.attributes.state = '';
delete this.event.session.attributes['STATE'];

I hope that won't have any other unexpected side effect...

Hi @tschuege,

State is stored in the attributes of handler. So if you change your code to the following then it should work:
JavaScript protected resetState() { this.alexaHandler.handler.state = ''; delete this.alexaHandler.attributes['STATE']; }
And the workaround you have should work just fine.

Cheers!

I am also facing a strange issue that session is not persisting in DynamoDB on Stop Intent. But when user tries to launch the skill again, old session is saving to dynamoDB. This is happening only in the skill where I use state events this.handler.state

"AMAZON.StopIntent": function() {
        console.log(this.attributes);
         delete this.attributes["quizitem"];
        delete this.attributes["quizproperty"];
        delete this.attributes["previousIntent"];
        delete this.attributes["counter"];
        this.handler.state = '';
        delete this.event.session.attributes['STATE'];
        delete this.attributes['STATE'];
        delete this.attributes['response'];
        delete this.attributes['quizscore'];
        delete this.attributes['quizCode'];
        //this.response.shouldEndSession(true);
        console.log("Stop Intent 2");
        console.log(this.attributes);
        this.response.speak(EXIT_SKILL_MESSAGE);
        this.emit(":responseReady");
    }

I got around this using the steps mentioned above and providing a NewSession handler in each of my lists of state handlers. My common NewSession is below.

https://github.com/blueshirts/alexa-who-said-meow/blob/master/index.js#L43

const commonNewSession = function () {
  this.handler.state = ''
  this.emit('NewSession')
}

I have a list of common handlers that should be implemented in all of my state handlers. This is both for this issue and to ensure I pass certification.

https://github.com/blueshirts/alexa-who-said-meow/blob/master/index.js#L43

const commonHandlers = {
  'NewSession': commonNewSession,
  'Unhandled': Unhandled,
  'SessionEndedRequest': SessionEndedRequest,
  'AMAZON.HelpIntent': HelpIntent,
  'AMAZON.CancelIntent': CancelIntent,
  'AMAZON.StopIntent': CancelIntent
}

I create my state handlers using a utility that extends the common list of handlers. Any duplicates are overridden.

https://github.com/blueshirts/alexa-who-said-meow/blob/master/index.js#L43

const continueHandlers = createStateHandler(states.CONTINUE, {
  'AMAZON.YesIntent': continueYes,
  'AMAZON.NoIntent': continueNo
})

Here is the utility code. It may not be optimized though it works fine for my usage. If a state is left in the attributes when a new session is created the commonNewSession is fired and sends the user back to the default NewSession.

https://github.com/blueshirts/alexa-who-said-meow/blob/master/index.js#L43

function extend(o = {}) {
  for (let k of Object.keys(commonHandlers)) {
    if (!o[k]) {
      o[k] = commonHandlers[k]
    }
  }
  return o
}

function createStateHandler(state, handlers) {
  return Alexa.CreateStateHandler(state, extend(handlers))
}

That's pretty cool :+1:

Hi all,

Thanks for your support for the SDK. We've released ASK SDK v2 for Node.js yesterday, which aims to address many of the issues here. We encourage you to check out our wiki here.

Hi,

Could you please show how v2 solves this issue? Would it be possible to post a link showing the migration path for state handling from v1 to v2 please?

I ended up here because I have the same issue, and hoped to see some fixes in v2, but instead have been presented with a complete rewrite of the sdk which makes my current code null and void because the adapter doesn't work correctly and I don't have enough in-depth knowledge to know what the issue is.

Thanks,
Jarrod

@tianrenz Hello I am not able to clear the state . I have written the entire skill in SDK v1 , I do not want to migrate to V2 SDK only for this .
I am using this.handler.state = `` and in my response.js , this is the change i made but it does not work . Any help is appreciated. thanks in advance !!

var _this = this;
if (this.isOverridden()) {
return;
}
if (forceSave && this.handler.state) {
this.attributes.STATE = this.handler.state;
}
if ( this.handler.state == '' ) {
this.attributes['STATE'] = undefined;
}
var response = this.handler.response.response;
if (response.shouldEndSession || forceSave || this.handler.saveBeforeResponse) {
if (!dynamoDbPersistenceAdapter) {
dynamoDbPersistenceAdapter = new ask_sdk_1.DynamoDbPersistenceAdapter({
createTable: true,
dynamoDBClient: this.handler.dynamoDBClient,
partitionKeyName: 'userId',
attributesName: 'mapAttr',
tableName: this.handler.dynamoDBTableName,
});
}
dynamoDbPersistenceAdapter.saveAttributes(this.event, this.attributes).then(function () {
_this.handler.promiseResolve(response);
}).catch(function (error) {
if (error) {
return _this.emit(':saveStateError', error);
}
});
}
else {
this.handler.promiseResolve(response);
}

Hi @Aashrith1 ,

Could you try replacing this.attributes['STATE'] = undefined with delete this.attributes['STATE']?

Regards

This works for me.

this.handler.state = '';
delete this.attributes.STATE;

Thanks @tianrenz :)

Was this page helpful?
0 / 5 - 0 ratings

Related issues

mehtanilay10 picture mehtanilay10  路  6Comments

sohanmaheshwar picture sohanmaheshwar  路  3Comments

ancashoria picture ancashoria  路  6Comments

lostvicking picture lostvicking  路  4Comments

slesem picture slesem  路  4Comments