Hi folks,
Just want to share some custom website solution for particle device. My goal was to use 2FA (as does not exist any example even on official Particle documentation ), be universal as deep as possible, to be useful for any user, no JQuery !!! and be able to run with SimpleHTTPServer over linux dist.
Ofcourse there is lots “to do” eg: logout button and is not perfect but is working well !!!
To run this, there is no reqirments ! can run directly with no server just all files has to be on the same folder, also background pic has to be included otherwhise default bg. color will be used.
-
) index.html (name has to be index.html to be able to run with SimpleHTTPServer over Linux dist. var durTok is really important ! provide how long the token will be valid)
**update**
to avoid exposing device ID and token in navigation bar and in header I use sessionStorage instead query string. It’s still not safe but at least no one can see them Also added logout button.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<style>
html {
background: url(F.png) no-repeat center center fixed #4CAF50;
-webkit-background-size: cover;
background-size: cover;
}
</style>
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/particle-api-js@8/dist/particle.min.js">
</script>
</head>
<body style="font-size:24px;color:black;">
<h1 style="border-radius:20px;" align="center";>Arek IO</h1>
<div align="center">
<label> user <input style="border-radius:20px;border: 3px solid red;" required type="text" id="username" value="" title="user name"/></label>
<label> password <input style="border-radius:20px;border: 3px solid red;" type="password" id="password" value="" title="password"/></label>
<p>
<button style="border-radius:20px;border: 3px solid red;font-size:21px" id='start' onclick="set_credentials();" >Log-in</button>
<div>
<script>
document.getElementById('username').focus();
document.getElementById('username').select();
var particle = new Particle();
var token;
var mfa_tokus;
var all_dev;
function set_credentials() {
var username1 = document.getElementById('username').value;
var password1 = document.getElementById('password').value;
var durTok = 28800;
particle.login({username: username1, password: password1, tokenDuration: durTok}).then(
function(data){
console.log('API call completed on promise resolve: ', data.body.access_token);
token = data.body.access_token;
var jsuserpaswd = [username1, password1, token];
sessionStorage.setItem("jsArray", JSON.stringify(jsuserpaswd));
location.href='Device_list.html';
},
function(err) {
console.log('API call completed on promise fail: ', err);
if (err.body.error === 'mfa_required') {
mfa_tokus = err.body.mfa_token;
var jsuserpaswd = [username1, password1, mfa_tokus];
sessionStorage.setItem("jsArray", JSON.stringify(jsuserpaswd));
location.href='nex_stage.html';
}else{
if (confirm(err.body.error_description)){
location.href='index.html';
}else{
console.log('bad');
}
}
}
);
}
var input = document.getElementById("password");
// Execute a function when the user releases a key on the keyboard
input.addEventListener("keyup", function(event) {
// Number 13 is the "Enter" key on the keyboard
if (event.keyCode === 13) {
// Cancel the default action, if needed
event.preventDefault();
// Trigger the button element with a click
document.getElementById("start").click();
}
});
</script>
</body>
</html>
- ) next stage.html is for 2FA my goal
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset="UTF-8">
<title>Next stage</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<style>
html {
height: 100%;
margin: 0;
text-align:center;
background-image: url("F2.png");
background-position: center;
background-repeat: no-repeat;
background-size: cover;
background-color: #4CAF50;
}
</style>
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/particle-api-js@8/dist/particle.min.js">
</script>
</head>
<body>
<div>
<label style="font-size:21px" >2FA key <input style="border-radius:20px;border: 3px solid red;font-size:18px" type="password" id="OTP" value="" title="OTP"/></label>
<p>
<button style="border-radius:20px;border: 3px solid red;font-size:21px" id='go' onclick="mfaChallenge();" >go go go !!!</button>
</div>
<script>
document.getElementById('OTP').focus();
document.getElementById('OTP').select();
var jsarray = JSON.parse(sessionStorage.getItem("jsArray"));
var mfa_tokus = jsarray[2];
var particle = new Particle();
function mfaChallenge() {
var otp1 = document.getElementById('OTP').value;
particle.sendOtp({ mfaToken:mfa_tokus, otp:otp1 }).then(
function(data){
console.log('API call completed on promise resolve: ', data.body.access_token);
var token = data.body.access_token;
//getListDev(token);
jsarray.splice(2, 1, token);
sessionStorage.setItem("jsArray", JSON.stringify(jsarray));
location.href='Device_list.html'; //?='+ token;
},
function(err) {
console.log('API call completed on promise fail: ', err);
if (confirm(err.body.error_description)){
location.href='index.html';
}
}
);
}
var input = document.getElementById("OTP");
// Execute a function when the user releases a key on the keyboard
input.addEventListener("keyup", function(event) {
// Number 13 is the "Enter" key on the keyboard
if (event.keyCode === 13) {
// Cancel the default action, if needed
event.preventDefault();
// Trigger the button element with a click
document.getElementById("go").click();
}
});
</script>
</body>
</html>
3.) Device list.html - as name suggests
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
<style>
html {
background: url(F2.png) no-repeat center center fixed #4CAF50;
-webkit-background-size: cover;
background-size: cover;
}
body {
color: #212121;
}
.btn-fs {
position: absolute;
top: 10px;
left: 10px;
padding: 5px;
color: black;
border: 3px solid green;
border-radius: 5px;
z-index: 10;
cursor: pointer;
}
td {
border: 1px solid #4bb762;
height: 49px;
width: 800px;
text-align: center;
color: black;
}
</style>
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/particle-api-js@8/dist/particle.min.js">
</script>
</head>
<body>
<p style= 'font-size:30px;padding-top:20px;color:black'>Device's table </p>
<p>
<button id='ref' onclick="reflesz();" ><i class="fa fa-refresh" aria-hidden="true" style="font-size: 54px; color: #39a0ef;"></i></button>
<table id="myTable">
<tr>
<td>device name</td>
<td>ip address</td>
<td>dev id</td>
<td>is cellular</td>
</tr>
</table>
<br>
<div class="btn-fs" id="btnFS">
Log out
</div>
<script>
var particle = new Particle();
var all_dev;
var jsarray = JSON.parse(sessionStorage.getItem("jsArray"));
var token = jsarray[2];
function reflesz(){
var i = all_dev.length;
if(i != NaN){
document.getElementById("ref").disabled = true;
var table = document.getElementById("myTable");
for (elements in all_dev){
table.deleteRow(i);
i--;
}
getListDev();
}
}
function logout(){
particle.deleteAccessToken({ username: jsarray[0], password: jsarray[1], token: token }).then(function(data) {
console.log('data on deleting accessToken: ', data);
setTimeout(function(){ location.href='index.html'; }, 300);
}, function(err) {
console.log('error on deleting accessToken: ', err);
if (confirm(err.body.error_description)){
location.href='index.html';
}
});}
function getListDev(){
var devicesPr = particle.listDevices({ auth: token});
devicesPr.then(
function(devices){
console.log('Devices: ', devices);
all_dev = devices.body;
myFunction();
},
function(err) {
console.log('List devices call failed: ', err);
console.log('json error', err.body.error);
if (confirm(err.body.error_description)) {
document.location.href='index.html';
}else {
console.log('bad');
}
});
}
function myFunction() {
var i = 0;
for (elements in all_dev){
var table = document.getElementById("myTable");
var row = table.insertRow(1);
var cell1 = row.insertCell(0);
var cell2 = row.insertCell(1);
var cell3 = row.insertCell(2);
var cell4 = row.insertCell(3);
var exten = ".html?";
var DevName = "univetsall"; //all_dev[i].name;
var DevID = all_dev[i].id;
if(jsarray.length < all_dev.length + 3){
jsarray.splice(i+3, 0, all_dev[i].id);
}
//cell1.innerHTML = '<a href =' + DevName + exten + DevID + '=' + token +'>' + all_dev[i].name;
cell1.innerHTML = '<a href =' + DevName + exten + '=' + (i + 3) +'>' + all_dev[i].name;
cell2.innerHTML = all_dev[i].last_ip_address;
cell3.innerHTML = DevID; //new Date(all_dev[i].last_heard).toString().slice(0,16) + ' at ' + new Date(all_dev[i].last_heard).toString().slice(16,24) ;
cell4.innerHTML = all_dev[i].cellular;
if(all_dev[i].cellular == true){
cell4.style.backgroundColor = 'rgba(' + 64 + ',' + 128 + ',' + 255 + ',' + 0.3 + ')';
}
else{
cell4.style.backgroundColor = 'rgba(' + 255 + ',' + 0 + ',' + 0 + ',' + 0.3 + ')';
}
i++;
}
sessionStorage.setItem("jsArray", JSON.stringify(jsarray));
document.getElementById("ref").disabled = false;
}
window.addEventListener('load',function() {
/* hack to prevent firing the init script before the window object's values are populated */
setTimeout(getListDev,200);
},false);
var fs = document.getElementById('btnFS');
fs.addEventListener('click', logout);
</script>
</body>
</html>
4.) universal.html - here is end of my input, rest is depend on individual user variable and function name
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<style>
html {
background: url(F2.png) no-repeat center center fixed #4CAF50;
-webkit-background-size: cover;
background-size: cover;
}
body {
color: #212121;
}
.btn-fs {
position: absolute;
top: 10px;
left: 10px;
padding: 5px;
color: black;
border: 3px solid green;
border-radius: 5px;
z-index: 10;
cursor: pointer;
}
.btn-fs2 {
position: absolute;
top: 45px;
left: 10px;
padding: 5px;
color: black;
border: 3px solid green;
border-radius: 5px;
z-index: 10;
cursor: pointer;
}
td {
border: 1px solid #4bb762;
height: 49px;
width: 800px;
text-align: center;
color: black;
top: 65px;
left: 10px;
}
table {
margin-top: 85px;
}
</style>
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/particle-api-js@8/dist/particle.min.js">
</script>
<script type="text/javascript">
var dane = 0;
var particle = new Particle();
var all_dev;
var tempToken = location.search.substring(1).split("=")
var jsarray = JSON.parse(sessionStorage.getItem("jsArray"));
var token1 = jsarray[2];
var devID = jsarray[parseInt(tempToken[1])];
function reflesz(){
var i = 1;
document.getElementById("btnFS2").disabled = true;
var table = document.getElementById("myTable");
table.deleteRow(i);
get_aur2_attribute();
}
function get_aur2_attribute(){
particle.getDevice({ deviceId: devID, auth: token1 }).then(
function(data) {
console.log('Device variable retrieved successfully:', data.body);
dane = data.body;
myFunction();
},
function(err) {
console.log('An error occurred while getting attrs:', err);
if (confirm(err.body.error_description)) {
document.location.href='index.html';
}else {
console.log('bad!!!');
}
});
}
function myFunction(){
var table = document.getElementById("myTable");
var row = table.insertRow(1);
var cell1 = row.insertCell(0);
var cell2 = row.insertCell(1);
var cell3 = row.insertCell(2);
var cell4 = [];
var cell5 = [];
var exten = ".html?";
var DevName = dane.name;
var DevID = dane.id;
for(key in dane){
var i = 0;
if(key == 'functions'){
if(dane[key] != null ){
if(dane[key].length != 0 ){
table.rows[0].cells[3].colSpan = dane[key].length;
for(elements in dane[key] ){
const iterator = dane[key].values();
for (const value of iterator) {
var exten = ".html?"
var unikalus = value; //"gege";
cell4.push(row.insertCell(dane[key].length+2) );
cell4[dane[key].length -1].innerHTML = '<a href =' + unikalus + exten + '=' + tempToken[1] +'>' + value;
}
}
}
}
else{
table.rows[0].cells[3].colSpan = 1;
var value = "no functions exposed";
cell4.push(row.insertCell(3) );
cell4[0].innerHTML = value;
}
}
if(key == 'variables'){
if(dane[key] != null ){
if(Object.keys(dane[key]).length != 0 ){
console.log('Device variable length:', Object.keys(dane[key]).length);
table.rows[0].cells[4].colSpan = Object.keys(dane[key]).length;
for(elements in dane[key]){
var exten = ".html?";
var unikalus = elements; //"gege";
cell5.push(row.insertCell(i + 3) );
cell5[i].innerHTML = '<a href =' + unikalus + exten + '=' + tempToken[1] +'>' + elements;
i++;
}
}
}
else{
table.rows[0].cells[4].colSpan = 1;
var value = "no variables exposed";
cell5.push(row.insertCell(3) );
cell5[0].innerHTML = value;
}
}
}
cell1.innerHTML = dane.name;
cell2.innerHTML = new Date(dane.last_heard).toString().slice(0,16) + ' at ' + new Date(dane.last_heard).toString().slice(16,24) ;
cell3.innerHTML = dane.connected;
if(dane.connected == true){
cell3.style.backgroundColor = 'rgba(' + 64 + ',' + 128 + ',' + 255 + ',' + 0.3 + ')';
}
else{
cell3.style.backgroundColor = 'rgba(' + 255 + ',' + 0 + ',' + 0 + ',' + 0.3 + ')';
}
document.getElementById("btnFS2").disabled = false;
}
window.addEventListener('load',function() {
/* hack to prevent firing the init script before the window object's values are populated */
setTimeout( get_aur2_attribute,500);
},false);
</script>
</head>
<body>
<div class="btn-fs" id="btnFS">
Go back
</div>
<div class="btn-fs2" id="btnFS2">
Get Atributte
</div>
<script>
var ga = document.getElementById('btnFS2');
ga.addEventListener('click', reflesz);
var fs = document.getElementById('btnFS');
fs.addEventListener('click', goBack);
function goBack() {
document.location.href='Device_list.html'// ?='+ token[1];
}
</script>
<div>
<table id="myTable">
<tr>
<td>device name</td>
<td>last heard</td>
<td>on line</td>
<td>functions</td>
<td >variables</td>
</tr>
</table>
<br>
</div>
</body>
</html>
To run this with SimpleHTTPServer eg; on ubuntu just make a folder with all of above file, run terminal, go to loction of the folder and run command:
python -m SimpleHTTPServer 80
some pictures how is looks like and an example how variable named “status” can looks