2021-10-31 14:16:46 +00:00
|
|
|
<!DOCTYPE html>
|
2021-10-29 14:36:30 +00:00
|
|
|
<html>
|
2021-10-31 14:16:46 +00:00
|
|
|
<head>
|
|
|
|
<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=0"/>
|
2021-11-09 15:24:47 +00:00
|
|
|
|
2021-10-31 14:16:46 +00:00
|
|
|
<link rel="stylesheet" href="../../css/spectre.min.css">
|
|
|
|
<style type="text/css">
|
|
|
|
body{font-family:sans-serif}
|
|
|
|
body div{display:none}
|
|
|
|
body.select div#tokens,body.editing div#edit,body.scanning div#scan,body.showqr div#tokenqr{display:block}
|
|
|
|
#tokens th,#tokens td{padding:5px}
|
|
|
|
#tokens tr:nth-child(odd){background-color:#ccc}
|
|
|
|
#tokens tr:nth-child(even){background-color:#eee}
|
|
|
|
#qr-canvas{margin:auto;width:calc(100%-20px);max-width:400px}
|
|
|
|
#advbtn,#scan,#tokenqr table{text-align:center}
|
|
|
|
#edittoken tbody#adv{display:none}
|
|
|
|
#edittoken.showadv tbody#adv{display:table-row-group}
|
|
|
|
#advbtn button:before,#advbtn button:after{content:"\25bc"}
|
|
|
|
#edittoken.showadv #advbtn button:before,#edittoken.showadv #advbtn button:after{content:"\25b2"}
|
|
|
|
button{height:3em}
|
2021-11-09 15:24:47 +00:00
|
|
|
table button{width:100%}
|
2021-11-12 16:59:34 +00:00
|
|
|
form.totp tr.hotp,form.hotp tr.totp{display:none}
|
2021-10-31 14:16:46 +00:00
|
|
|
</style>
|
2021-11-09 15:24:47 +00:00
|
|
|
|
2021-10-31 14:16:46 +00:00
|
|
|
<!-- https://rawgit.com/sitepoint-editors/jsqrcode/master/src/qr_packed.js -->
|
|
|
|
<script src="qr_packed.js"></script>
|
2021-11-09 15:24:47 +00:00
|
|
|
|
2021-10-31 14:16:46 +00:00
|
|
|
<!-- https://davidshimjs.github.io/qrcodejs/ -->
|
2021-11-01 02:07:45 +00:00
|
|
|
<script src="../../core/lib/qrcode.min.js"></script>
|
2021-11-09 15:24:47 +00:00
|
|
|
|
2021-10-31 14:16:46 +00:00
|
|
|
<script type="text/javascript">
|
2021-11-09 15:24:47 +00:00
|
|
|
|
|
|
|
/* Start of all TOTP URLs */
|
2021-11-12 16:59:34 +00:00
|
|
|
const otpAuthUrl = 'otpauth://';
|
|
|
|
|
|
|
|
const tokentypes = ['TOTP (Timed)', 'HOTP (Counter)'];
|
2021-11-09 15:24:47 +00:00
|
|
|
|
2021-12-01 14:15:55 +00:00
|
|
|
/* Settings */
|
|
|
|
var settings = {tokens:[], misc:{}};
|
|
|
|
var tokens = settings.tokens;
|
2021-11-09 15:24:47 +00:00
|
|
|
|
|
|
|
/* Remove any non-base-32 characters from the given string and collapses
|
|
|
|
* whitespace to a single space. Optionally removes all whitespace from
|
|
|
|
* the string.
|
|
|
|
*/
|
2021-11-02 05:02:31 +00:00
|
|
|
function base32clean(val, nows) {
|
2021-11-11 11:41:45 +00:00
|
|
|
var ret = val.replaceAll(/\s+/g, ' ');
|
|
|
|
ret = ret.replaceAll(/[^A-Za-z2-7 ]/g, '');
|
2021-11-02 05:02:31 +00:00
|
|
|
if (nows) {
|
2021-11-11 11:41:45 +00:00
|
|
|
ret = ret.replaceAll(/\s+/g, '');
|
2021-11-02 05:02:31 +00:00
|
|
|
}
|
|
|
|
return ret;
|
|
|
|
}
|
2021-11-09 15:24:47 +00:00
|
|
|
|
|
|
|
/* Save changes to a token to the global tokens[] array.
|
|
|
|
* id is the index into the global tokens[].
|
|
|
|
* forget is a flag indicating if the token should be forgotten.
|
|
|
|
*/
|
2021-10-31 14:16:46 +00:00
|
|
|
function saveEdit(id, forget) {
|
|
|
|
if (forget) {
|
2021-11-11 11:41:45 +00:00
|
|
|
if (confirm('Forget token?')) {
|
2021-11-09 15:24:47 +00:00
|
|
|
tokens.splice(id, 1);
|
|
|
|
updateTokens();
|
|
|
|
}
|
2021-10-31 14:16:46 +00:00
|
|
|
} else {
|
2021-11-11 11:41:45 +00:00
|
|
|
let fe = document.forms['edittoken'].elements;
|
2021-11-12 16:59:34 +00:00
|
|
|
let d = parseInt(fe['digits'].value);
|
|
|
|
let p = parseInt(fe['period'].value);
|
|
|
|
let c = parseInt(fe['count'].value);
|
|
|
|
switch (fe['type'].value) {
|
|
|
|
case tokentypes[1]: p = (c > 0) ? -c : 0; break;
|
|
|
|
default : p = (p > 0) ? p : 30; break;
|
|
|
|
}
|
2021-11-09 15:24:47 +00:00
|
|
|
tokens[id] = {
|
2021-11-11 11:41:45 +00:00
|
|
|
'algorithm':fe['algorithm'].value,
|
2021-11-12 16:59:34 +00:00
|
|
|
'digits':((d > 0) ? d : 6),
|
|
|
|
'period':p,
|
2021-11-11 11:41:45 +00:00
|
|
|
'issuer':fe['issuer'].value,
|
|
|
|
'account':fe['account'].value,
|
|
|
|
'secret':base32clean(fe['secret'].value, false),
|
|
|
|
'label':fe['label'].value
|
2021-10-31 14:16:46 +00:00
|
|
|
};
|
2021-11-09 15:24:47 +00:00
|
|
|
updateTokens();
|
2021-10-31 14:16:46 +00:00
|
|
|
}
|
|
|
|
}
|
2021-11-09 15:24:47 +00:00
|
|
|
|
|
|
|
/* Generate and display a QR-code representing the current token.
|
|
|
|
*/
|
2021-10-31 14:16:46 +00:00
|
|
|
function showQrCode() {
|
2021-11-11 11:41:45 +00:00
|
|
|
var fe = document.forms['edittoken'].elements;
|
2021-11-09 15:24:47 +00:00
|
|
|
var url = new String(otpAuthUrl);
|
2021-11-12 16:59:34 +00:00
|
|
|
switch (fe['type'].value) {
|
|
|
|
case tokentypes[1]: url += 'hotp/'; break;
|
|
|
|
default : url += 'totp/'; break;
|
|
|
|
}
|
2021-11-11 11:41:45 +00:00
|
|
|
if (fe['account'].value.length > 0) {
|
|
|
|
url += encodeURIComponent(fe['account'].value);
|
|
|
|
} else {
|
|
|
|
url += encodeURIComponent(fe['label'].value);
|
|
|
|
}
|
|
|
|
url += '?';
|
|
|
|
if (fe['issuer'].value.length > 0) {
|
|
|
|
url += 'issuer=' + encodeURIComponent(fe['issuer'].value);
|
|
|
|
url += '&';
|
|
|
|
}
|
|
|
|
url += 'secret=' + base32clean(fe['secret'].value, true);
|
2021-11-12 16:59:34 +00:00
|
|
|
switch (fe['type'].value) {
|
|
|
|
case tokentypes[1]:
|
|
|
|
if (parseInt(fe['count'].value) != 0) {
|
|
|
|
url += '&counter=' + fe['count'].value;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
if (parseInt(fe['period'].value) != 30) {
|
|
|
|
url += '&period=' + fe['period'].value;
|
|
|
|
}
|
|
|
|
break;
|
2021-10-31 14:16:46 +00:00
|
|
|
}
|
2021-11-11 11:41:45 +00:00
|
|
|
if (parseInt(fe['digits'].value) != 6) {
|
|
|
|
url += '&digits=' + fe['digits'].value;
|
2021-10-31 14:16:46 +00:00
|
|
|
}
|
2021-11-11 11:41:45 +00:00
|
|
|
if (fe['algorithm'].value != 'SHA1') {
|
|
|
|
url += '&algorithm=' + fe['algorithm'].value;
|
2021-10-31 14:16:46 +00:00
|
|
|
}
|
|
|
|
tokenqr.clear();
|
|
|
|
tokenqr.makeCode(url);
|
2021-11-11 11:41:45 +00:00
|
|
|
document.body.className = 'showqr';
|
2021-10-31 14:16:46 +00:00
|
|
|
}
|
2021-11-09 15:24:47 +00:00
|
|
|
|
2021-11-12 16:59:34 +00:00
|
|
|
function onTypeChanged() {
|
|
|
|
var f = document.forms['edittoken'];
|
|
|
|
var fe = f.elements;
|
|
|
|
if (fe['type'].value == tokentypes[0]) { f.classList.add('totp'); f.classList.remove('hotp'); }
|
|
|
|
if (fe['type'].value == tokentypes[1]) { f.classList.add('hotp'); f.classList.remove('totp'); }
|
|
|
|
}
|
|
|
|
|
2021-11-09 15:24:47 +00:00
|
|
|
/* Generate a form for editing the specified token.
|
|
|
|
* id is the index into the global tokens[].
|
|
|
|
*/
|
|
|
|
function editToken(id) {
|
2021-11-12 16:59:34 +00:00
|
|
|
var p;
|
|
|
|
const selectMarkup = function(name, ary, cur, onchg) {
|
|
|
|
var ret = '<select name="' + name + '"' + ((typeof onchg == 'string') ? ' onchange="' + onchg + '"' : '') + '>';
|
2021-10-31 14:16:46 +00:00
|
|
|
for (let i = 0; i < ary.length; i++) {
|
2021-11-11 11:41:45 +00:00
|
|
|
ret += '<option' + ((ary[i] == cur) ? ' selected=selected' : '') + '>' + ary[i] + '</option>';
|
2021-10-31 14:16:46 +00:00
|
|
|
}
|
2021-11-11 11:41:45 +00:00
|
|
|
return ret + '</select>';
|
2021-10-31 14:16:46 +00:00
|
|
|
};
|
|
|
|
scanning=false;
|
2021-11-11 11:41:45 +00:00
|
|
|
var markup = '<form id="edittoken"><input type="hidden" name="tokenid" value="' + id + '"><table>';
|
|
|
|
markup += '<tr><td>Name:</td><td><input name="label" type="text" value="' + tokens[id].label + '"></td></tr>';
|
|
|
|
markup += '<tr><td>Secret:</td><td><input name="secret" type="text" value="' + tokens[id].secret + '"></td></tr>';
|
|
|
|
markup += '<tbody id="adv">';
|
|
|
|
markup += '<tr><td>Account:</td><td><input name="account" type="text" value="' + tokens[id].account + '"></td></tr>';
|
|
|
|
markup += '<tr><td>Issuer:</td><td><input name="issuer" type="text" value="' + tokens[id].issuer + '"></td></tr>';
|
2021-11-12 16:59:34 +00:00
|
|
|
p = parseInt(tokens[id].period);
|
|
|
|
markup += '<tr><td>Type:</td><td>';
|
|
|
|
markup += selectMarkup('type', tokentypes, (tokens[id].period > 0) ? tokentypes[0] : tokentypes[1], 'onTypeChanged()');
|
|
|
|
markup += '</td></tr>';
|
|
|
|
markup += '<tr class="totp"><td>Period:</td><td><input name="period" type="text" value="' + ((p > 0) ? p : 30) + '"></td></tr>';
|
|
|
|
markup += '<tr class="hotp"><td>Count:</td><td><input name="count" type="text" value="' + ((p >= 0) ? 0 : -p) + '"></td></tr>';
|
2021-11-11 11:41:45 +00:00
|
|
|
markup += '<tr><td>Digits:</td><td>';
|
2021-11-19 12:30:38 +00:00
|
|
|
markup += selectMarkup('digits', ['6','7','8','9','10'], tokens[id].digits);
|
2021-11-11 11:41:45 +00:00
|
|
|
markup += '</td></tr>';
|
|
|
|
markup += '<tr><td>Hash:</td><td>';
|
|
|
|
markup += selectMarkup('algorithm', ['SHA1','SHA256','SHA512'], tokens[id].algorithm);
|
|
|
|
markup += '</td></tr>';
|
|
|
|
markup += '</tbody><tr><td id="advbtn" colspan="2">';
|
|
|
|
markup += '<button type="button" onclick="document.getElementById(\'edittoken\').classList.toggle(\'showadv\')">Advanced</button>';
|
|
|
|
markup += '</td></tr></table></form>';
|
|
|
|
markup += '<button type="button" onclick="updateTokens()">Cancel Edit</button>';
|
|
|
|
markup += '<button type="button" onclick="saveEdit(' + id + ', false)">Save Changes</button>';
|
2021-10-31 14:16:46 +00:00
|
|
|
if (tokens[id].isnew) {
|
2021-11-11 11:41:45 +00:00
|
|
|
markup += '<button type="button" onclick="startScan()">Scan QR Code</button>';
|
2021-10-31 14:16:46 +00:00
|
|
|
} else {
|
2021-11-11 11:41:45 +00:00
|
|
|
markup += '<button type="button" onclick="showQrCode()">Show QR Code</button>';
|
|
|
|
markup += '<button type="button" onclick="saveEdit(' + id + ', true)">Forget Token</button>';
|
2021-10-31 14:16:46 +00:00
|
|
|
}
|
2021-11-11 11:41:45 +00:00
|
|
|
document.getElementById('edit').innerHTML = markup;
|
|
|
|
document.body.className = 'editing';
|
2021-11-12 16:59:34 +00:00
|
|
|
onTypeChanged();
|
2021-10-31 14:16:46 +00:00
|
|
|
}
|
2021-11-09 15:24:47 +00:00
|
|
|
|
|
|
|
/* Create a new blank token and open the editor for it.
|
|
|
|
*/
|
2021-10-31 14:16:46 +00:00
|
|
|
function addToken() {
|
2021-11-11 11:41:45 +00:00
|
|
|
tokens[tokens.length] = {'algorithm':'SHA1','digits':6,'period':30,'issuer':'','account':'','secret':'','label':'','isnew':true};
|
2021-11-09 15:24:47 +00:00
|
|
|
editToken(tokens.length - 1);
|
2021-10-31 14:16:46 +00:00
|
|
|
}
|
2021-11-09 15:24:47 +00:00
|
|
|
|
|
|
|
/* Move the specified token up or down in the global tokens[].
|
|
|
|
* id is the index in the global tokens[] of the token to move.
|
|
|
|
* dir is the direction to move: -1=up, 1=down.
|
|
|
|
*/
|
|
|
|
function moveToken(id, dir) {
|
2021-10-31 14:16:46 +00:00
|
|
|
tokens.splice(id + dir, 0, tokens.splice(id, 1)[0]);
|
|
|
|
updateTokens();
|
|
|
|
}
|
2021-11-09 15:24:47 +00:00
|
|
|
|
|
|
|
/* Update the display listing all the tokens.
|
|
|
|
*/
|
2021-10-31 14:16:46 +00:00
|
|
|
function updateTokens() {
|
2021-11-09 15:24:47 +00:00
|
|
|
const tokenButton = function(fn, id, label, dir) {
|
2021-11-11 11:41:45 +00:00
|
|
|
return '<button type="button" onclick="' + fn + '(' + id + (dir ? ',' + dir : '') + ')">' + label + '</button>';
|
2021-10-31 14:16:46 +00:00
|
|
|
};
|
2021-11-11 11:41:45 +00:00
|
|
|
var markup = '<table><tr><th>Token</th><th colspan="2">Order</th></tr>';
|
2021-11-09 15:24:47 +00:00
|
|
|
/* any tokens marked new are cancelled new additions and must be removed */
|
2021-10-31 14:16:46 +00:00
|
|
|
for (let i = 0; i < tokens.length; i++) {
|
|
|
|
if (tokens[i].isnew) {
|
2021-11-09 15:24:47 +00:00
|
|
|
tokens.splice(i--, 1);
|
2021-10-31 14:16:46 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
for (let i = 0; i < tokens.length; i++) {
|
2021-11-11 11:41:45 +00:00
|
|
|
markup += '<tr><td>';
|
|
|
|
markup += tokenButton('editToken', i, tokens[i].label);
|
|
|
|
markup += '</td><td>';
|
2021-10-31 14:16:46 +00:00
|
|
|
if (i < (tokens.length - 1)) {
|
2021-11-11 11:41:45 +00:00
|
|
|
markup += tokenButton('moveToken', i, '▼', 1);
|
2021-10-31 14:16:46 +00:00
|
|
|
}
|
2021-11-11 11:41:45 +00:00
|
|
|
markup += '</td><td>';
|
2021-10-31 14:16:46 +00:00
|
|
|
if (i > 0) {
|
2021-11-11 11:41:45 +00:00
|
|
|
markup += tokenButton('moveToken', i, '▲', -1);
|
2021-10-31 14:16:46 +00:00
|
|
|
}
|
2021-11-11 11:41:45 +00:00
|
|
|
markup += '</td></tr>';
|
2021-10-31 14:16:46 +00:00
|
|
|
}
|
2021-11-11 11:41:45 +00:00
|
|
|
markup += '</table>';
|
|
|
|
markup += '<button type="button" onclick="addToken()">Add Token</button>';
|
|
|
|
markup += '<button type="button" onclick="saveTokens()">Save to watch</button>';
|
|
|
|
document.getElementById('tokens').innerHTML = markup;
|
|
|
|
document.body.className = 'select';
|
2021-10-31 14:16:46 +00:00
|
|
|
}
|
2021-11-09 15:24:47 +00:00
|
|
|
|
2021-10-31 14:16:46 +00:00
|
|
|
/* Original QR-code reader: https://www.sitepoint.com/create-qr-code-reader-mobile-website/ */
|
|
|
|
qrcode.callback = res => {
|
|
|
|
if (res) {
|
2021-11-09 15:24:47 +00:00
|
|
|
if (res.startsWith(otpAuthUrl)) {
|
|
|
|
res = decodeURIComponent(res);
|
|
|
|
var paramsidx = res.indexOf('?');
|
|
|
|
var params = res.substr(paramsidx+1).split('&');
|
|
|
|
var t = {
|
2021-11-11 11:41:45 +00:00
|
|
|
'algorithm':'SHA1',
|
|
|
|
'digits':'6',
|
2021-11-12 16:59:34 +00:00
|
|
|
'counter':'0',
|
2021-11-11 11:41:45 +00:00
|
|
|
'period':'30',
|
|
|
|
'secret':'',
|
|
|
|
'issuer':''
|
2021-11-09 15:24:47 +00:00
|
|
|
};
|
|
|
|
var otpok = true;
|
|
|
|
for (let pi in params) {
|
|
|
|
var param = params[pi].split('=');
|
|
|
|
if (param[0] in t) {
|
|
|
|
t[param[0]] = param[1];
|
|
|
|
} else {
|
|
|
|
otpok = false;
|
|
|
|
}
|
|
|
|
}
|
2021-11-11 11:41:45 +00:00
|
|
|
t['account'] = res.substring(res.lastIndexOf('/', paramsidx)+1, paramsidx);
|
|
|
|
if ((t['account'] == '') || (t['secret'] == '')) {
|
2021-10-31 14:16:46 +00:00
|
|
|
otpok = false;
|
|
|
|
}
|
2021-11-09 15:24:47 +00:00
|
|
|
if (otpok) {
|
|
|
|
scanning = false;
|
|
|
|
editToken(parseInt(document.forms['edittoken'].elements['tokenid'].value));
|
2021-11-11 11:41:45 +00:00
|
|
|
t['label'] = (t['issuer'] == '') ? t['account'] : t['issuer'] + ' (' + t['account'] + ')';
|
2021-12-01 14:21:21 +00:00
|
|
|
t['label'] = t['label'].substr(0, 10);
|
2021-11-11 11:41:45 +00:00
|
|
|
var fe = document.forms['edittoken'].elements;
|
2021-11-12 16:59:34 +00:00
|
|
|
if (res.startsWith(otpAuthUrl + 'hotp/')) {
|
|
|
|
t['period'] = '30';
|
|
|
|
fe['type'].value = tokentypes[1];
|
|
|
|
} else {
|
|
|
|
t['counter'] = '0';
|
|
|
|
fe['type'].value = tokentypes[0];
|
|
|
|
}
|
2021-11-11 11:41:45 +00:00
|
|
|
fe['algorithm'].value = t['algorithm'];
|
|
|
|
fe['digits' ].value = t['digits' ];
|
2021-11-12 16:59:34 +00:00
|
|
|
fe['count' ].value = t['counter' ];
|
2021-11-11 11:41:45 +00:00
|
|
|
fe['period' ].value = t['period' ];
|
|
|
|
fe['secret' ].value = t['secret' ];
|
|
|
|
fe['issuer' ].value = t['issuer' ];
|
|
|
|
fe['account' ].value = t['account' ];
|
|
|
|
fe['label' ].value = t['label' ];
|
2021-11-12 16:59:34 +00:00
|
|
|
onTypeChanged();
|
2021-11-09 15:24:47 +00:00
|
|
|
}
|
2021-10-31 14:16:46 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
function startScan() {
|
2021-11-11 11:41:45 +00:00
|
|
|
document.body.className = 'scanning';
|
2021-10-31 14:16:46 +00:00
|
|
|
navigator.mediaDevices
|
2021-11-11 11:41:45 +00:00
|
|
|
.getUserMedia({video:{facingMode:'environment'}})
|
2021-10-31 14:16:46 +00:00
|
|
|
.then(function(stream){
|
|
|
|
scanning=true;
|
2021-11-11 11:41:45 +00:00
|
|
|
video.setAttribute('playsinline',true);
|
2021-10-31 14:16:46 +00:00
|
|
|
video.srcObject = stream;
|
|
|
|
video.play();
|
|
|
|
scanTick();
|
|
|
|
doScan();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
function scanTick() {
|
|
|
|
canvasElement.height = video.videoHeight;
|
|
|
|
canvasElement.width = video.videoWidth;
|
|
|
|
canvas.drawImage(video,0,0,canvasElement.width,canvasElement.height);
|
|
|
|
if (scanning) {
|
|
|
|
requestAnimationFrame(scanTick);
|
|
|
|
} else {
|
|
|
|
video.srcObject.getTracks().forEach(track => {
|
|
|
|
track.stop();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
function doScan() {
|
|
|
|
try {
|
|
|
|
qrcode.decode();
|
|
|
|
} catch (e) {
|
|
|
|
setTimeout(doScan,300);
|
|
|
|
}
|
|
|
|
}
|
2021-11-09 15:24:47 +00:00
|
|
|
|
|
|
|
/* Load settings JSON file from the watch.
|
|
|
|
*/
|
2021-10-31 14:16:46 +00:00
|
|
|
function loadTokens() {
|
2021-11-11 11:41:45 +00:00
|
|
|
Util.showModal('Loading...');
|
2021-12-01 14:15:55 +00:00
|
|
|
Puck.eval(`require('Storage').readJSON(${JSON.stringify('authentiwatch.json')})`,data=>{
|
2021-10-29 14:36:30 +00:00
|
|
|
Util.hideModal();
|
2021-12-01 14:15:55 +00:00
|
|
|
if (data.data ) settings.tokens = data.data ; /* v0.02 settings */
|
|
|
|
if (data.tokens) settings.tokens = data.tokens; /* v0.03+ settings */
|
|
|
|
if (data.misc ) settings.misc = data.misc ; /* v0.03+ settings */
|
|
|
|
tokens = settings.tokens;
|
|
|
|
updateTokens();
|
2021-10-29 14:36:30 +00:00
|
|
|
});
|
|
|
|
}
|
2021-11-09 15:24:47 +00:00
|
|
|
/* Save settings as a JSON file on the watch.
|
|
|
|
*/
|
2021-10-31 14:16:46 +00:00
|
|
|
function saveTokens() {
|
2021-11-11 11:41:45 +00:00
|
|
|
Util.showModal('Saving...');
|
2021-12-01 14:15:55 +00:00
|
|
|
let newsettings={tokens:tokens,misc:settings.misc};
|
|
|
|
Puck.write(`\x10require('Storage').writeJSON(${JSON.stringify('authentiwatch.json')},${JSON.stringify(newsettings)})\n`,()=>{
|
2021-11-02 16:05:17 +00:00
|
|
|
Util.hideModal();
|
|
|
|
});
|
2021-10-31 14:16:46 +00:00
|
|
|
}
|
2021-10-29 14:36:30 +00:00
|
|
|
function onInit() {
|
2021-10-31 14:16:46 +00:00
|
|
|
loadTokens();
|
|
|
|
updateTokens();
|
2021-10-29 14:36:30 +00:00
|
|
|
}
|
2021-10-31 14:16:46 +00:00
|
|
|
</script>
|
|
|
|
</head>
|
|
|
|
<body class="select">
|
|
|
|
<h1>Authentiwatch</h1>
|
|
|
|
<div id="tokens">
|
|
|
|
<p>No watch comms.</p>
|
|
|
|
</div>
|
|
|
|
<div id="scan">
|
|
|
|
<table>
|
|
|
|
<tr><td><canvas id="qr-canvas"></canvas></td></tr>
|
2021-11-11 11:41:45 +00:00
|
|
|
<tr><td><button type="button" onclick="editToken(parseInt(document.forms['edittoken'].elements['tokenid'].value))">Cancel</button></td></tr>
|
2021-10-31 14:16:46 +00:00
|
|
|
</table>
|
|
|
|
</div>
|
|
|
|
<div id="edit">
|
|
|
|
</div>
|
|
|
|
<div id="tokenqr">
|
|
|
|
<table><tr><td id="qrcode"></td></tr><tr><td>
|
2021-11-11 11:41:45 +00:00
|
|
|
<button type="button" onclick="document.body.className='editing'">Back</button>
|
2021-10-31 14:16:46 +00:00
|
|
|
</td></tr></table>
|
|
|
|
</div>
|
|
|
|
<script type="text/javascript">
|
2021-11-11 11:41:45 +00:00
|
|
|
const video=document.createElement('video');
|
|
|
|
const canvasElement=document.getElementById('qr-canvas');
|
|
|
|
const canvas=canvasElement.getContext('2d');
|
2021-10-31 14:16:46 +00:00
|
|
|
let scanning=false;
|
2021-11-11 11:41:45 +00:00
|
|
|
const tokenqr=new QRCode(document.getElementById('qrcode'), '');
|
2021-10-31 14:16:46 +00:00
|
|
|
</script>
|
|
|
|
<script src="../../core/lib/interface.js"></script>
|
|
|
|
</body>
|
2021-10-29 14:36:30 +00:00
|
|
|
</html>
|