diff --git a/CHANGELOG.md b/CHANGELOG.md index eb03ddb..0f0aa07 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ### 2.4.1 (Next) * Your contribution here. +* [#118](https://github.com/matt-kruse/alexa-app/pull/118): [#117](https://github.com/matt-kruse/alexa-app/issues/117) Prevent updating session attributes directly - [@ajcrites](https://github.com/ajcrites). ### 2.4.0 (January 5, 2017) diff --git a/README.md b/README.md index 559a31f..19376f0 100644 --- a/README.md +++ b/README.md @@ -140,6 +140,9 @@ var session = request.getSession() // set a session variable // by defailt, Alexa only persists session variables to the next request // the alexa-app module makes session variables persist across multiple requests +// Note that you *must* use `.set` or `.clear` to update +// session properties. Updating properties of `attributeValue` +// that are objects will not persist until `.set` is called session.set(String attributeName, String attributeValue) // return the value of a session variable diff --git a/index.js b/index.js index d4aec66..f476cd6 100644 --- a/index.js +++ b/index.js @@ -227,7 +227,9 @@ alexa.session = function(session) { return (true === session.new); }; this.get = function(key) { - return this.attributes[key]; + // getAttributes deep clones the attributes object, so updates to objects + // will not affect the session until `set` is called explicitly + return this.getAttributes()[key]; }; this.set = function(key, value) { this.attributes[key] = value; @@ -262,7 +264,9 @@ alexa.session = function(session) { } this.getAttributes = function() { // do some stuff with session data - return this.attributes; + // Deep clone attributes so direct updates to objects are not set in the + // session unless `.set` is called explicitly + return JSON.parse(JSON.stringify(this.attributes)); }; }; diff --git a/test/test_alexa_app_session.js b/test/test_alexa_app_session.js index a6f9111..fb14991 100644 --- a/test/test_alexa_app_session.js +++ b/test/test_alexa_app_session.js @@ -77,6 +77,66 @@ describe("Alexa", function() { ) ]); }); + + it("does not update session properties without explicit set", function() { + testApp.pre = function(req, res, type) { + var session = req.getSession(); + session.set("foo", true); + session.set("bar", {qaz: "woah"}); + }; + + testApp.intent("airportInfoIntent", {}, function(req, res) { + res.say("message").shouldEndSession(false); + var session = req.getSession(); + var bar = session.get("bar"); + bar.qaz = "not woah"; + return true; + }); + + var subject = testApp.request(mockRequest).then(function(response) { + return response.sessionAttributes; + }); + + return Promise.all([ + expect(subject).to.eventually.become({ + foo: true, + bar: { + qaz: "woah" + } + }) + ]); + }); + + it("updates session properties with explicit set", function() { + testApp.pre = function(req, res, type) { + var session = req.getSession(); + session.set("foo", true); + session.set("bar", {qaz: "woah"}); + }; + + testApp.intent("airportInfoIntent", {}, function(req, res) { + res.say("message").shouldEndSession(false); + var session = req.getSession(); + var bar = session.get("bar"); + bar.qaz = "not woah"; + session.set("bar", bar); + session.set("foo", false); + return true; + }); + + var subject = testApp.request(mockRequest).then(function(response) { + return response.sessionAttributes; + }); + + return Promise.all([ + expect(subject).to.eventually.become({ + foo: false, + bar: { + qaz: "not woah" + } + }) + ]); + }); }); });