diff --git a/backend/config.js b/backend/config.js new file mode 100644 index 0000000..ad1b06d --- /dev/null +++ b/backend/config.js @@ -0,0 +1,15 @@ +var config = {}; + +config.dbURL = 'mongodb://localhost:27017/tellall'; +config.PORT = 5000; + +config.TOKEN = 'Atza|IwEBIBe6gDqrrowEEav6N-_6s4NztYeP3oG8PGWmu8ZiZw6lbOh3wNla3TK6pY-VEpT1d8an-dVf_n3kXJzVFsNo_4xBfZyFHGoCTDTFjs3yBRul4PVdBOhwwiH3-sgRLcUofZbe2oE06GmTcbfYtaStfXpQI5dfpldfnsJg_CvhSA6AHb_snJT3F6lyXzbV076d_3cYUMJxFldJGnYcviNHHxjjmuQTD06hhGzCbAxxe9eBmkuopRsNfyedLT2UlKP_ublah9CUGA3AdIX_3Iuke82jMwGnNl9gv7pbaDNEjAbj7IQSl3B08uuREtJq-oTBOjALNXRvFxTJmQjZwXNf9eHC7fSHJDdEPdZQU0AcffRQObAyAkUuL6Jv39OHzhb3Q64-zzoyODqnJyLP5SQZ2JVF53Kc_cTBqjIc9pXljqe7yEVk6JDs7q1zKbBibx_AQm57TO79IzWyLBzBMlYL5HdTsqEfRzLeDw2tws-hGMgkx2HWfdbYnmf5Qb4SyIhzvmmdfPLg3MVKTxjIBu1rx0xf3n0PLZP1EO6jsJPoMRPg77Gm4oit5Zp6s37ek3A3Vxh-ntoASpkrkxGTG9kVtRNt'; +config.REFRESH_TOKEN = 'Atzr|IwEBICA3kDhfSJxlwvnQp9AD1o115AC_KBbFd5GBg8oN_QHWn2or5xFQ09BruuK6a07tGHtTt_9q2Y21mnOMH4RDtYXTVG9ADgLE6mHWKZFxYVwt3kHMiUJdY5lJcsOtWLoblrS-bJ0BEXXK5nVDt_bSI5IB7NUf-9QVZxhovRH_ANSxdTjJT0_rMIAZY3WEj68FEap49q_pg72BhnxHVZD2TC3zvX96_DN65HE5SoSgT7OiMAeiJewB_SyemW_HxBwaB-_X-G1ifOtnrzZ4gXTpOrEUlHI2YPuvRMBMtmz1h-nXDZYv3vwU3RA57Qj_ZNVcScj8_RXf2xq8w48v0PzZFXYBSalfnqPq6eUvSSbAJUp6bB8y596JlvR5dFQe_Z--X0Gkfo85IcyrI9D44vK9sJhrGhCVi2FDDa8pHczmNSen99JYZvDif-zpYzgbcymAkOV0gC7JvYMxlZfETT3NTBy7eVA7fJI1SZaeA_qW49xRcBkZBu5gkqTpeGWUU1cGr2aXRVVmXGM22NfV5E7KzvEBsCeHml_tCfxZeKY8Msd8hJb0Cd59u-_hsuc8oNjsOpIdFF976dY3uTmAgHWpG2PH'; +config.TOKEN_EXPIRES_IN = 1515100500; + +config.SKILL_ID = 'amzn1.ask.skill.efbf0564-a732-4ba9-958f-57939138adae'; + +config.CLIENT_ID = 'amzn1.application-oa2-client.c748ca56ded04a95b236979898585ff7'; +config.CLIENT_SECRET = '6dea8125cecd049d3c4cff7bb5bdfd3ff17bc6fed246c4c8f6b519d9ed08d0b3'; + +module.exports=config; \ No newline at end of file diff --git a/backend/express.js b/backend/express.js index 29e89b2..017d375 100644 --- a/backend/express.js +++ b/backend/express.js @@ -1,4 +1,7 @@ var amazonHelper = require('./helpers/amazon'); +var databaseHelper = require('./helpers/database'); + +const config = require('./config'); require('isomorphic-fetch'); @@ -7,11 +10,6 @@ var alexa = require('alexa-app'); var bodyParser = require('body-parser'); -const dbURL = 'mongodb://localhost:27017/tellall'; -const PORT = 5000; -const TOKEN = 'Atza|IwEBIMv0spn-eZhjP8-R2Jjkb4VUi-EY4V3MX-wlJyr2P6YBUmIChl7VKgRberEdQ-Wolp53SqfwHbxlSo4-rdUgFYFAxImB622boeqUBBCtwybP0OTBJBT1CVm_FBr48Li5LkwR9DxPciCnc052ddohpjGuGZxsCqIJo2c-7LPEhH0Lx63VOFgfPIfBsrVqjDbPNrr5ApPUijKYZWurktb6ytTks8eFUSTyv3FDbE5HEng0qpE4mgSjdgBkEc8BgUfpm2QGvctINH9SioUJOJonxgTPYhbD4BEd-jdQHsltKFzkb3-Rm3lFhEu3_CQFxCeQ6yGe1the_qID3vnPfpSY6hyblgF5L_5d7bE7BWg8fm6rSwXv67L2sMwBOji6cuR0wVVvXfYxmgGIMGkVQrq-SPSdtCpx2BvBz2SqqN6a_98svP8ukvhwc_oJsN6VwEmTDFgutNf-XuGkuVii-k9-DncwuwD00LvJG1FhBvbvSyuv-a3LAJSqTmroemTDG0xzLn7ULFY5p-93sM0_ZNGFeW-lL_r1ldqM_5lFRKDta1Tkg7lT9-rHftgnpGs4zv7vGLIPzHpNiXjsKyCk-wMQrihhWlR15kiz7oKDeTf6wCIETg'; -const USER_ID = 'amzn1.ask.account.AGE34MG3VIQ75D4Q5CXFK25GUOXHZ2MIVSII3QUCOT3CP44IPE25PLD3DWW7HLWO2KVC7PC7VUXSZZEFPVHJ6PTDYVYYESJE35CE6VEXCE3BI3AK24TGO4CJXFF3SZ7IA4QVJRZC6EN4MUF2WUP4IB4CGDLRZZMYQENOFNBYWFXZHMZ5PA6S6ERK7NGEPUSDHXWKH26UGMIATMI'; - var MongoClient = require ('mongodb').MongoClient; var ObjectID = require ('mongodb').ObjectID; @@ -55,40 +53,33 @@ router.get ('/deleteSkill/:skillID', async (req, res, next) => { }); router.post ('/updateSkill/:id', async (req, res, next) => { - try { + let id = req.params.id; let skill = req.body; delete skill._id; if (id !== '-1'){ - let completeResult = {databaseUpdate:false, amazonUpdate:false, amazonMessage:''}; - - let result = db.collection('skill_list').update({_id: ObjectID(id)}, skill,{upsert:true}, (err, result)=>{ - if (err) throw(err); - - completeResult.databaseUpdate = (JSON.parse(result).nModified===1); - - let generatedInteractionModel = amazonHelper.generateInteractionModel(skill); - - fetch(`https://api.amazonalexa.com/v0/skills/${skill.skillID}/interactionModel/locales/en-US`, { - method: 'POST', - headers: { - Authorization: TOKEN - }, - body: generatedInteractionModel - }).then(l=>l.text()).then(result=>{ - completeResult.amazonUpdate = (JSON.stringify(result)===JSON.stringify({})); - completeResult.amazonMessage = JSON.stringify(result); - res.json(completeResult); - }); + amazonHelper.updateSkill(skill,db).then(amazonResult=>{ + if (amazonResult===200 || amazonResult===202){ + //Skill uploaded, it's ok to update database + let result = db.collection('skill_list').update({_id: ObjectID(id)}, skill,{upsert:true}, (err, result)=>{ + if (JSON.parse(result).nModified===1){ + //database update ok + res.json({result:0, message:'ok'}); + }else{ + res.json({result:-1, message:'ok'}); + } + }); + }else{ + res.json({result:-1, message:'Amazon result ' + amazonResult}); + } + }).catch(e=>{ + //skill upload went wrong, don't update database, send error + res.json({result:-1, message:e}); }); + }else{ //no new skills for now } - - } catch (e) { - console.log ('error:', e); - next (e); - } }); var skillID = 'amzn1.ask.skill.7115bfc9-313e-4728-830b-ebd19ce96cb3'; @@ -168,10 +159,16 @@ app.use (bodyParser.json ()); app.use ('/', router); -MongoClient.connect (dbURL).then (database => { +MongoClient.connect (config.dbURL).then (database => { db = database; db.collection ('intent_list'); - app.listen (PORT, () => - console.log ('Express server running on port ' + PORT) - ); + + databaseHelper.initModule(db); + + app.listen (config.PORT, () =>{ + console.log ('Express server running on port ' + config.PORT); + databaseHelper.loadTokens(); + }); +}).catch(e=>{ + console.log("error : " + e); }); \ No newline at end of file diff --git a/backend/helpers/amazon.js b/backend/helpers/amazon.js index 2ebcf9f..2bca863 100644 --- a/backend/helpers/amazon.js +++ b/backend/helpers/amazon.js @@ -1,6 +1,7 @@ require('isomorphic-fetch'); - -var ObjectID = require ('mongodb').ObjectID; +const config = require('../config'); +var request = require("request"); +var token = require('./token'); var getBuildStatus = function(skillID){ @@ -18,45 +19,102 @@ var getBuildStatus = function(skillID){ } } -module.exports = { - generateInteractionModel: function(skill){ - try{ - let result = {}; - let allIntents = []; - let defaultIntents = [{ - name: "AMAZON.CancelIntent", - samples: [] - }, - { - name: "AMAZON.HelpIntent", - samples: [] - }, - { - name: "AMAZON.StopIntent", - samples: [] - }]; - - /* - defaultIntents.map(intent=>{ - allIntents.push(intent); - }); - */ - - skill.intents.map(intent=>{ - allIntents.push({name: intent.intentName, samples: intent.questions}); - }); - - result.interactionModel = {}; - - result.interactionModel.languageModel = { - invocationName: skill.invocationName, - intents: allIntents - }; - - return JSON.stringify(result); - }catch(e){ - console.log("error generate : " + e); - } - } +var refreshToken = function(db){ + return new Promise((resolve, reject)=>{ + var options = { method: 'POST', + url: 'https://api.amazon.com/auth/o2/token', + headers: + { 'cache-control': 'no-cache', + 'content-type': 'application/x-www-form-urlencoded' }, + form: + { grant_type: 'refresh_token', + refresh_token: config.REFRESH_TOKEN, + client_id: config.CLIENT_ID, + client_secret: config.CLIENT_SECRET } }; + + request(options, function (error, response, body) { + if (error) reject(error); + parsedResponse = JSON.parse(body); + if (parsedResponse.refresh_token){ + try{ + token.updateTokens(parsedResponse.refresh_token, parsedResponse.access_token, parsedResponse.expires_in,db); + resolve(); + }catch(e){ + reject(e); + } + } + }); + }); +} +var generateInteractionModel = function(skill){ + let result = {}; + let allIntents = []; + let defaultIntents = [{ + name: "AMAZON.CancelIntent", + samples: [] + }, + { + name: "AMAZON.HelpIntent", + samples: [] + }, + { + name: "AMAZON.StopIntent", + samples: [] + }]; + + /* + defaultIntents.map(intent=>{ + allIntents.push(intent); + }); + */ + + skill.intents.map(intent=>{ + allIntents.push({name: intent.intentName, samples: intent.questions}); + }); + + result.interactionModel = {}; + + result.interactionModel.languageModel = { + invocationName: skill.invocationName, + intents: allIntents + }; + + return JSON.stringify(result); +} + +var uploadSkill = function(skill){ + return fetch(`https://api.amazonalexa.com/v0/skills/${skill.skillID}/interactionModel/locales/en-US`, { + method: 'POST', + headers: { + Authorization: config.TOKEN + }, + body: generateInteractionModel(skill) + }); +} + +module.exports = { + updateSkill: function(skill, db){ + console.log("update skill function"); + + return new Promise((resolve,reject)=>{ + if (new Date() / 1000 > config.TOKEN_EXPIRES_IN){ + refreshToken(db).then(()=>{ + uploadSkill(skill).then(response=>{ + resolve(response.status); + }); + }).catch(e=>{ + reject(e); + }); + }else{ + uploadSkill(skill).then(response=>{ + resolve(response.status); + }).catch(e=>{ + reject(e); + }) + } + }); + + + } }; \ No newline at end of file diff --git a/backend/helpers/database.js b/backend/helpers/database.js new file mode 100644 index 0000000..ac580b8 --- /dev/null +++ b/backend/helpers/database.js @@ -0,0 +1,21 @@ +const config = require('../config'); + +var db = null; + + +module.exports = { + initModule : function(databaseObject){ + db=databaseObject; + }, + + loadTokens : function(){ + db.collection('token_list').findOne().then(tokens=>{ + config.TOKEN = tokens.access_token; + config.REFRESH_TOKEN = tokens.refresh_token; + config.TOKEN_EXPIRES_IN = tokens.expires_in; + console.log("Tokens loaded"); + }).catch(e=>{ + console.log("Error loading tokens"); + }) + } +} \ No newline at end of file diff --git a/backend/helpers/token.js b/backend/helpers/token.js new file mode 100644 index 0000000..bb95186 --- /dev/null +++ b/backend/helpers/token.js @@ -0,0 +1,19 @@ +const config = require('../config'); +var ObjectID = require ('mongodb').ObjectID; + +module.exports = { + updateTokens : function (refresh_token, access_token, expires_in, db){ + let newTokenDocument = { + id:1, + refresh_token:refresh_token, + access_token: access_token, + expires_in: (new Date() / 1000) + expires_in + } + let result = db.collection('token_list').update({id:1}, newTokenDocument,{upsert:true}, (err, result)=>{ + if (err) throw new Error(err); + config.REFRESH_TOKEN = refresh_token; + config.TOKEN = access_token; + config.TOKEN_EXPIRES_IN = newTokenDocument.expires_in; + }); + } +}; \ No newline at end of file diff --git a/backend/package.json b/backend/package.json index a496860..16afe98 100644 --- a/backend/package.json +++ b/backend/package.json @@ -8,7 +8,8 @@ "body-parser": "^1.13.1", "ejs": "^2.3.1", "express": "^4.13.0", - "isomorphic-fetch": "^2.2.1" + "isomorphic-fetch": "^2.2.1", + "request": "^2.83.0" }, "author": "Matt Kruse (http://mattkruse.com/)", "license": "MIT" diff --git a/web/src/App.js b/web/src/App.js index e2b5455..df1c76f 100644 --- a/web/src/App.js +++ b/web/src/App.js @@ -97,16 +97,14 @@ class App extends Component { handleSaveLaunchRequestClick(name, answer){ this.setState({invocationName: name, invocationAnswer: answer}); - try{ + console.log("handleSaveLaunchRequest"); updateSkill(this.createSkill(this.state.allIntents,name,answer)).then(l=>l.text()).then(result=>{ let jResult = JSON.parse(result); - if (!(jResult.databaseUpdate && jResult.amazonUpdate)){ - alert('Database update : ' + jResult.databaseUpdate + '\r\nAmazon update : ' + jResult.amazonUpdate + '\r\nMessage : ' + jResult.amazonMessage); - } + console.log(jResult.result); + console.log(jResult.message); + }).catch(e=>{ + console.log("Error :" + e); }); - }catch(e){ - alert("exception"); - } } handleDeleteIntentClick(selectedIntent){ @@ -120,12 +118,13 @@ class App extends Component { this.setState({allIntents: newAllIntents, selectedIntent: {intentName:'', questions:[''],answer:''}}); updateSkill(this.createSkill(newAllIntents)).then(l=>l.text()).then(result=>{ let jResult = JSON.parse(result); - if (!(jResult.databaseUpdate && jResult.amazonUpdate)){ - alert('Database update : ' + jResult.databaseUpdate + '\r\nAmazon update : ' + jResult.amazonUpdate + '\r\nMessage : ' + jResult.amazonMessage); - } + console.log(jResult.result); + console.log(jResult.message); + }).catch(e=>{ + console.log("error : " + e); }); }catch(e){ - alert("exception"); + console.log("error : " + e); } } } @@ -141,16 +140,15 @@ class App extends Component { newAllIntents[this.state.selectedIndex] = selectedIntent; this.setState({allIntents: newAllIntents, selectedIntent: selectedIntent}); } - try{ - updateSkill(this.createSkill(newAllIntents)).then(l=>l.text()).then(result=>{ - let jResult = JSON.parse(result); - if (!(jResult.databaseUpdate && jResult.amazonUpdate)){ - alert('Database update : ' + jResult.databaseUpdate + '\r\nAmazon update : ' + jResult.amazonUpdate + '\r\nMessage : ' + jResult.amazonMessage); - } - }); - }catch(e){ - alert("exception"); - } + + updateSkill(this.createSkill(newAllIntents)).then(l=>l.text()).then(result=>{ + let jResult = JSON.parse(result); + console.log(jResult.result); + console.log(jResult.message); + }).catch(e=>{ + console.log("error : " + e); + }); + } handleAddIntentClick(){