aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--htdocs/amazon.js323
-rw-r--r--htdocs/binding.css2
-rw-r--r--htdocs/binding.js24
-rw-r--r--htdocs/index.html20
4 files changed, 367 insertions, 2 deletions
diff --git a/htdocs/amazon.js b/htdocs/amazon.js
new file mode 100644
index 0000000..05704f1
--- /dev/null
+++ b/htdocs/amazon.js
@@ -0,0 +1,323 @@
+/*
+ * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0/
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+AMAZON = function() {
+
+var afb;
+var alexaWs;
+
+var base = {
+ base: "api",
+ token: "HELLO",
+};
+
+// GUID generator for generating device serial number.
+function guid() {
+ function s4() {
+ return Math.floor((1 + Math.random()) * 0x10000)
+ .toString(16)
+ .substring(1);
+ }
+ return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
+}
+
+/*********************************************/
+/**** ****/
+/**** AMAZON_cbl ****/
+/**** ****/
+/*********************************************/
+var AMAZON_Cbl;
+{
+ const amazonHostUrl = "https://api.amazon.com";
+ const amazonCodePairUrl = amazonHostUrl + "/auth/O2/create/codepair";
+ const amazonTokenUrl = amazonHostUrl + "/auth/O2/token";
+ const deviceSerialNumber = guid();
+ var clientID = localStorage.getItem("client_id");
+ var productID = localStorage.getItem("product_id");
+ var alexaVAAddress = localStorage.getItem("alexa_va_address");
+ var alexaVAConnected = false;
+ var alexaVAAddressInput;
+ var clientIDInput;
+ var productIDInput;
+
+ AMAZON_Cbl = function() {
+ // Alexa VA Address
+ const alexaVAAddressInput = document.getElementById('alexa-va-address');
+ alexaVAAddress = alexaVAAddressInput.value;
+ connectToAlexaVA(alexaVAAddress);
+
+ alexaVAAddressInput.addEventListener("change",(evt) => {
+ var newAlexaVAAddress = alexaVAAddressInput.value;
+ if (alexaVAAddress != newAlexaVAAddress) {
+ connectToAlexaVA(newAlexaVAAddress);
+ localStorage.setItem("alexa_va_address", newAlexaVAAddress);
+ }
+ });
+
+ // Client ID
+ const clientIDInput = document.getElementById('client-id');
+ clientIDInput.addEventListener("change",(evt) => {
+ var newClientID = clientIDInput.value;
+ if (clientID != newClientID) {
+ clientID = newClientID;
+ localStorage.setItem("client_id", newClientID);
+ }
+ });
+
+ // Product ID
+ const productIDInput = document.getElementById('product-id');
+ productIDInput.addEventListener("change",(evt) => {
+ var newProductID = productIDInput.value;
+ if (productID != newProductID) {
+ productID = newProductID;
+ localStorage.setItem("product_id", newProductID);
+ }
+ });
+ }
+
+ function connectToAlexaVA(address) {
+ base.host = address;
+ afb = new AFB(base, "secret");
+
+ function onopen() {
+ console.log("Connected to Alexa VA");
+ alexaVAConnected = true;
+ }
+
+ function onabort() {
+ console.log("Alexa VA connection aborted.");
+ alexaVAConnected = false;
+ }
+
+ alexaWs = new afb.ws(onopen, onabort);
+ }
+
+ function sendRequest(httpReq, paramsJson, url, responseCb) {
+ httpReq.onreadystatechange = responseCb;
+ var paramsQueryString = Object.keys(paramsJson).map(key => key + '=' + paramsJson[key]).join('&');
+ httpReq.open("POST", url, true);
+ httpReq.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
+ httpReq.send(paramsQueryString);
+ }
+
+ //**********************************************
+ // Generic function to call VA binder
+ //***********************************************
+ function callVABinder(voiceAgent, verb, query) {
+ console.log(voiceAgent.api, verb, query);
+
+ // ws.call return a Promise
+ return alexaWs.call(voiceAgent.api + '/' + verb, query)
+ .then(function (res) {
+ log.reply(res);
+ count++;
+ return res;
+ })
+ .catch(function (err) {
+ log.reply(err);
+ count++;
+ throw err;
+ });
+ };
+
+ function updateAccessToken(voiceAgent, tokenResponseJson) {
+ if (alexaVAAddress === undefined || alexaVAAddress === null) {
+ console.log("No Alexa VA. So not updating the access token.");
+ return;
+ }
+
+ // store the access and refresh tokens.
+ if (typeof(Storage) !== "undefined") {
+ localStorage.setItem("access_token", tokenResponseJson["access_token"]);
+ localStorage.setItem("refresh_token", tokenResponseJson["refresh_token"]);
+ }
+
+ // Set the auth token
+ if (alexaVAConnected) {
+ // Set new token
+ const query = {"token": tokenResponseJson["access_token"]};
+ callVABinder(voiceAgent, 'setAuthToken', query);
+ }
+
+ // Refresh the token as soon as it expires.
+ setTimeout(refreshToken, tokenResponseJson["expires_in"] * 1000);
+ }
+
+ function refreshToken(voiceAgent) {
+ if (voiceAgent == "undefined") {
+ console.log("Error: VoiceAgent undefined");
+ return;
+ }
+
+ var refreshToken = localStorage.getItem("refresh_token");
+ if (refreshToken == null) {
+ console.log("Error: No refresh token");
+ return;
+ }
+
+ var paramsJson = {
+ "grant_type":"refresh_token",
+ "refresh_token":refreshToken,
+ "client_id":clientID,
+ };
+
+ const tokenRefreshReq = new XMLHttpRequest();
+ sendRequest(tokenRefreshReq, paramsJson, amazonTokenUrl, function() {
+ if (tokenRefreshReq.readyState == 4) {
+ if (tokenRefreshReq.status == 200) {
+ console.log("Got access token " + tokenRefreshReq.responseText);
+ var tokenResponseJson = JSON.parse(tokenRefreshReq.responseText);
+ updateAccessToken(voiceAgent, tokenResponseJson);
+ } else {
+ console.log("Failed to refresh access token: " + tokenRefreshReq.responseText);
+ }
+ }
+ });
+ }
+
+ function displayUserCodeAndURI(authResponseJson) {
+ const modal = document.getElementById('login-with-amazon');
+ const cblStatusDiv = document.createElement("div");
+ const cblStatusMsg = document.createElement("p");
+ const blank = "_blank";
+
+ var cblPage = authResponseJson["verification_uri"] + "?cbl-code=" + authResponseJson["user_code"]
+ var msg = "To use Alexa,you must sign in to Amazon.<br> Go to " +
+ "<a href=" + cblPage + " target="+ blank+ " >" +
+ cblPage + "</a>";
+ cblStatusMsg.innerHTML = msg;
+ cblStatusDiv.appendChild(cblStatusMsg);
+ modal.appendChild(cblStatusDiv);
+
+ const closeBtn = document.createElement("button");
+ closeBtn.addEventListener('click', (evt) => {
+ modal.close();
+ });
+ closeBtn.style = "margin: 10px";
+ closeBtn.innerHTML = "Close";
+ modal.appendChild(closeBtn);
+ }
+
+ function hideLoginUI() {
+ const loginDiv = document.getElementById('login-area');
+ loginDiv.style.display = "none";
+ }
+
+ function login(voiceAgent) {
+ if (voiceAgent == undefined) {
+ console.log("Error: VoiceAgent undefined");
+ return;
+ }
+
+ const modal = document.getElementById('login-with-amazon');
+ const submitBtn = document.getElementById('submit-btn');
+ const cancelBtn = document.getElementById('cancel-btn');
+ submitBtn.addEventListener('click', (evt) => {
+ console.log("Alexa Destination address set to: " + alexaVAAddress);
+ startLoginProcess(voiceAgent);
+ });
+
+ cancelBtn.addEventListener('click', (evt) => {
+ modal.close();
+ });
+
+ const alexaVAAddressInput = document.getElementById('alexa-va-address');
+ alexaVAAddressInput.value = alexaVAAddress;
+
+ const clientIDInput = document.getElementById('client-id');
+ clientIDInput.value = clientID;
+
+ const productIDInput = document.getElementById('product-id');
+ productIDInput.value = productID;
+
+ modal.showModal();
+ }
+
+ function startLoginProcess(voiceAgent) {
+ if (clientID == null || productID == null || alexaVAAddress == null) {
+ console.log("Required information missing to start login process.");
+ return;
+ }
+
+ var reqJson = {
+ "response_type": "device_code",
+ "client_id": clientID,
+ "scope":"alexa:all",
+ "scope_data": JSON.stringify({
+ "alexa:all": {
+ "productID":productID,
+ "productInstanceAttributes" : {
+ "deviceSerialNumber": deviceSerialNumber
+ }
+ }
+ })
+ };
+
+ const authReq = new XMLHttpRequest();
+ var tokenUrl = amazonTokenUrl;
+ sendRequest(authReq, reqJson, amazonCodePairUrl, function() {
+ if (authReq.readyState == 4) {
+ if (authReq.status == 200) {
+ var authResponse = JSON.parse(authReq.responseText);
+ console.log("Got auth codepair " + authReq.responseText);
+ hideLoginUI();
+ displayUserCodeAndURI(authResponse);
+ var maxTokenReqCnt = authResponse["expires_in"] / authResponse["interval"];
+ var tokenReqFuncId = setTimeout(function tokenReqFunc() {
+ var reqJson = {
+ "grant_type":"device_code",
+ "device_code":authResponse["device_code"],
+ "user_code":authResponse["user_code"]
+ };
+ const tokenReq = new XMLHttpRequest();
+ sendRequest(tokenReq, reqJson, tokenUrl, function() {
+ if (tokenReq.readyState == 4) {
+ if (tokenReq.status == 200) {
+ console.log("Got access token " + tokenReq.responseText);
+ var tokenResponseJson = JSON.parse(tokenReq.responseText);
+ updateAccessToken(voiceAgent, tokenResponseJson);
+ }
+ else {
+ maxTokenReqCnt--;
+ console.log("Retrying... " + tokenReq.responseText);
+ setTimeout(tokenReqFunc, authResponse["interval"] * 1000);
+ }
+ }
+ });
+ }, authResponse["interval"] * 1000);
+ // Cancel if max token request attempts are reached.
+ if (maxTokenReqCnt == 0) {
+ console.log("Reached max token request attemps limit.");
+ }
+ } else {
+ console.log(authReq.status);
+ }
+ }
+ });
+ }
+
+ AMAZON_Cbl.prototype = {
+ login: login,
+ refreshToken: refreshToken,
+ };
+}
+/*********************************************/
+/**** ****/
+/**** ****/
+/**** ****/
+/*********************************************/
+return {
+ cbl: AMAZON_Cbl
+};
+}; \ No newline at end of file
diff --git a/htdocs/binding.css b/htdocs/binding.css
index 99f84b4..9ee1303 100644
--- a/htdocs/binding.css
+++ b/htdocs/binding.css
@@ -88,7 +88,7 @@ dialog::backdrop {
}
h3.dialogheader {
- background-color: rgb(177, 177, 236);
+ background-color: lightgreen;
padding: 1ch;
}
diff --git a/htdocs/binding.js b/htdocs/binding.js
index c24d62e..7e7439b 100644
--- a/htdocs/binding.js
+++ b/htdocs/binding.js
@@ -3,6 +3,8 @@ var ws;
var evtIdx = 0;
var count = 0;
+var amazon = new AMAZON();
+var amazonCbl;
//**********************************************
// Logger
@@ -10,7 +12,7 @@ var count = 0;
var log = {
command: function (api, verb, query) {
console.log("subscribe api=" + api + " verb=" + verb + " query=", query);
- var question = urlWS + "/" + api + "/" + verb + "?query=" + JSON.stringify(query);
+ var question = afb.url + "/" + api + "/" + verb + "?query=" + JSON.stringify(query);
log._write("question", count + ": " + log.syntaxHighlight(question));
},
@@ -92,6 +94,8 @@ function init(elemID, api, verb, query) {
document.getElementById("connected").innerHTML = "Binder WS Active";
document.getElementById("connected").style.background = "lightgreen";
ws.onevent("*", log.event);
+ // Fetch and render voice agents.
+ fetchAndRenderVoiceAgents();
}
function onabort() {
@@ -165,6 +169,24 @@ function addVoiceAgent(containerDiv, voiceAgent, isDefault) {
subscribeBtn.innerHTML = 'Subscribe';
agentDiv.appendChild(subscribeBtn);
+ // Login implementation for Alexa Voice Agent
+ if (voiceAgent.name == "Alexa") {
+ amazonCbl = new amazon.cbl();
+ if (typeof(Storage) !== "undefined" &&
+ localStorage.getItem("access_token") !== null &&
+ localStorage.getItem("refresh_token") !== null) {
+ amazonCbl.refreshToken(voiceAgent);
+ } else {
+ const loginWithAmazonBtn = document.createElement("button");
+ loginWithAmazonBtn.addEventListener('click', (evt) => {
+ loginWithAmazonBtn.style.visibility = "hidden";
+ amazonCbl.login(voiceAgent);
+ });
+ loginWithAmazonBtn.innerHTML = 'Login With Amazon!!';
+ agentDiv.appendChild(loginWithAmazonBtn);
+ }
+ }
+
containerDiv.appendChild(agentDiv);
}
diff --git a/htdocs/index.html b/htdocs/index.html
index 0480c35..bf5f840 100644
--- a/htdocs/index.html
+++ b/htdocs/index.html
@@ -4,6 +4,7 @@
<title>VSHL API Test</title>
<link rel="stylesheet" href="binding.css">
<script type="text/javascript" src="AFB.js"></script>
+ <script type="text/javascript" src="amazon.js"></script>
<script type="text/javascript" src="binding.js"></script>
</head>
@@ -69,6 +70,25 @@
</footer>
</dialog>
+ <dialog id="login-with-amazon">
+ <h3 class="dialogheader">Login with Amazon !!</h3>
+ <div id="login-area">
+ <div>
+ Alexa VA URL: <input type="text" id="alexa-va-address" value="localhost:1111"> <br><br>
+ Client ID : <input type="text" id="client-id"> <br><br>
+ Product ID : <input type="text" id="product-id"> <br><br>
+ To generate client and product ID, please register a new AVS product for
+ <i><b>Other devices and platforms</b></i> using instructions in
+ <a href="https://developer.amazon.com/docs/alexa-voice-service/register-a-product.html" target="_blank">this </a>
+ link.<br><br>
+ </div>
+ <footer id ="login-with-amazon-footer">
+ <button id="submit-btn" type="button" style="margin: 10px">Login</button>
+ <button id="cancel-btn" type="button" style="margin: 10px">Cancel</button>
+ </footer>
+ </div>
+ </dialog>
+
<div id="top" class="row">
<div id='actions' class="col1">
<div>