/*
* 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.
Go to " +
"" +
cblPage + "";
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
};
};