From f63b70c36578f966bce88a74a80d4e9d0f04a78a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D0=B0=D0=B2=D0=B5=D0=BB=D0=B8=D0=B9=20=D0=A1=D0=B0?= =?UTF-8?q?=D0=B2=D0=B5=D0=BD=D0=BE=D0=BA?= <56991906+Lisoveliy@users.noreply.github.com> Date: Wed, 13 Nov 2024 18:12:34 +0300 Subject: [PATCH] feat: added support for time indicator --- app.js | 20 ++- app.json | 3 +- lib/totp-quickjs/OTPGenerator.js | 4 +- lib/totp-quickjs/index.js | 70 +++++++---- page/index.js | 208 +++++++++++++++++++++---------- page/totplogic/totps.js | 11 -- 6 files changed, 204 insertions(+), 112 deletions(-) delete mode 100644 page/totplogic/totps.js diff --git a/app.js b/app.js index 13d6c22..d9a7f87 100644 --- a/app.js +++ b/app.js @@ -1,8 +1,22 @@ +import { TOTP } from "./lib/totp-quickjs" +import { LocalStorage } from "@zos/storage" + +const localStorage = new LocalStorage() App({ - globalData: {}, - onCreate(options) { - console.log('app on create invoke') + globalData: { + TOTPS: localStorage.getItem('TOTPs') || [] }, + onCreate(options) { + localStorage.setItem('TOTPs', [ + new TOTP('JBSWY3DPEHPK3PXP', 'GitHub', 'Lisoveliy'), + new TOTP('JBSWY3DPEHPK3PXP', 'GitHub', 'Lisoveliy'), + new TOTP('JBSWY3DPEHPK3PXP', 'GitHub', 'Lisoveliy'), + new TOTP('JBSWY3DPEHPK3PXP', 'GitHub', 'Lisoveliy'), + new TOTP('JBSWY3DPEHPK3PXP', 'GitHub', 'Lisoveliy'), + new TOTP('JBSWY3DPEHPK3PXPAF', 'my.contabo.com', 'Contabo-Customer-Control-Panel-11755808'), + new TOTP('JBSWY3DPEHPK3PXP', 'GitHub', 'Lisoveliy'), + new TOTP('JBSWY3DPEHPK3PXPAF', 'my.contabo.com', 'Contabo-Customer-Control-Panel-11755808')]) + }, onDestroy(options) { console.log('app on destroy invoke') diff --git a/app.json b/app.json index 62100cc..25db52d 100644 --- a/app.json +++ b/app.json @@ -13,7 +13,8 @@ "description": "TOTP Authenticator for Amazfit devices" }, "permissions": [ - "data:os.device.info" + "data:os.device.info", + "device:os.local_storage" ], "runtime": { "apiVersion": { diff --git a/lib/totp-quickjs/OTPGenerator.js b/lib/totp-quickjs/OTPGenerator.js index 50f4867..c06d42a 100644 --- a/lib/totp-quickjs/OTPGenerator.js +++ b/lib/totp-quickjs/OTPGenerator.js @@ -13,10 +13,10 @@ export function getHOTP(counter, secret, digits = 6, hashType = 'SHA-1'){ //Stage 1: Prepare data const rawDataCounter = new DataView(new ArrayBuffer(8)) - rawDataCounter.setUint32(0, counter >> 32) rawDataCounter.setUint32(4, counter) + const bCounter = new Uint8Array(rawDataCounter.buffer) - + console.log(bCounter) const bSecret = new Uint8Array(decode(secret).match(/.{1,2}/g).map(chunk => parseInt(chunk, 16))); //confirmed //Stage 2: Hash data diff --git a/lib/totp-quickjs/index.js b/lib/totp-quickjs/index.js index 501c298..4fd694f 100644 --- a/lib/totp-quickjs/index.js +++ b/lib/totp-quickjs/index.js @@ -1,61 +1,77 @@ import { getHOTP } from "./OTPGenerator.js" -"use bigint" /** * TOTP instance */ -export class TOTP{ +export class TOTP { /** * * @param {string} secret base32 encoded string * @param {string} issuer issuer of TOTP + * @param {string} client client of TOTP * @param {number} [digits=6] number of digits in OTP token * @param {number} [fetchTime=30] period of token in seconds * @param {number} [timeOffset=0] time offset for token in seconds * @param {string} [hashType='SHA-1'] type of hash (more in jsSHA documentation) */ - constructor(secret, - issuer, - digits = 6, - fetchTime = 30, - timeOffset = 0, - hashType = 'SHA-1') - { - this.secret = secret - this.issuer = issuer - this.digits = digits - this.fetchTime = fetchTime - this.timeOffset = timeOffset - this.hashType = hashType - } + constructor(secret, + issuer, + client, + digits = 6, + fetchTime = 30, + timeOffset = 0, + hashType = 'SHA-1') { + this.secret = secret + this.issuer = issuer + this.client = client + this.digits = digits + this.fetchTime = fetchTime + this.timeOffset = timeOffset + this.hashType = hashType + } + static copy(totp){ + return new TOTP( + secret = totp.secret, + issuer = totp.TOTPissuer, + client = totp.client, + digits = totp.digits, + fetchTime = totp.fetchTime, + timeOffset = totp.timeOffset, + hashType = totp.hashType + ) + } /** * * @param {number} time time for counter (default unix time epoch) * @returns OTP instance */ - getOTP(time = Date.now()){ + getOTP(time = Date.now()) { const unixTime = (time / 1000 + this.timeOffset) / this.fetchTime const otp = getHOTP(Math.floor(unixTime), this.secret, this.digits) - const expireTime = time + - (this.fetchTime - - (time / 1000 + this.timeOffset) % - this.fetchTime) * 1000 - - return new OTP(otp, expireTime) + const expireTime = time + + (this.fetchTime - + (time / 1000 + this.timeOffset) % + this.fetchTime) * 1000 + const createdTime = time - (((time / 1000 + this.timeOffset) % + this.fetchTime) * 1000) + + + return new OTP(otp, createdTime, expireTime) } } /** * Class for TOTP.getOTP result */ -export class OTP{ +export class OTP { /** * * @param {string} otp OTP string - * @param {number} expireTime time in seconds to reset OTP + * @param {number} createdTime time in unix epoch created OTP + * @param {number} expireTime time in unix epoch to expire OTP */ - constructor(otp, expireTime) - { + constructor(otp, createdTime, expireTime) { this.otp = otp + this.createdTime = createdTime this.expireTime = expireTime } } \ No newline at end of file diff --git a/page/index.js b/page/index.js index 6a6615a..2f4cc74 100644 --- a/page/index.js +++ b/page/index.js @@ -1,76 +1,148 @@ import { getDeviceInfo } from '@zos/device' +import { TOTP } from '../lib/totp-quickjs' import { push } from '@zos/router' -import { setStatusBarVisible, createWidget, widget, align, prop, text_style, event } from '@zos/ui' +import { setStatusBarVisible, createWidget, widget, align, prop, text_style, event, deleteWidget } from '@zos/ui' -import { TOTPBuffer } from './totplogic/totps.js' +const app = getApp() +const renderWidgets = [] Page({ - build() { - setStatusBarVisible(false); - buf = new TOTPBuffer(); + onInit() { + const buffer = app._options.globalData.TOTPS + console.log(buffer.length) + if (buffer.length < 1) + setStatusBarVisible(true) + else + setStatusBarVisible(false) + }, + build() { + const buffer = app._options.globalData.TOTPS + if (buffer.length < 1) { - const { width, height } = getDeviceInfo() - buffer = buf.getTOTPs(); - if(buffer.length < 1){ - createWidget(widget.BUTTON, { - x: width / 2 - 40, - y: height / 2 - 20, - w: 80, - h: 80, - text: '+', - radius: 50, - text_size: 40, - normal_color: 0x303030, - press_color: 0x181c18, - click_func: () => { - push({ - url: 'page/tip' - }) - } - }) - }else{ - const buttonWidth = width - width / 20; - const buttonHeight = height / 4; - const margin = 10; - let totpHeight = margin; - - for(let i = 0; i < buffer.length; i++){ - console.log(buffer[i]) - createWidget(widget.FILL_RECT, { - x: width / 2 - buttonWidth / 2, - y: totpHeight, - w: buttonWidth, - h: buttonHeight, - color: 0x303030, - radius: 20 - }) - createWidget(widget.TEXT, { - x: 0, - y: totpHeight + 10, - w: width, - h: 26, - color: 0xa0a0a0, - text_size: 24, - align_h: align.CENTER_H, - align_v: align.CENTER_V, - text_style: text_style.NONE, - text: (buffer[i].expireTime - Date.now()) / 1000 - }) - createWidget(widget.TEXT, { - x: 0, - y: totpHeight + 60, - w: width, - h: 36, - color: 0xffffff, - text_size: 36, - align_h: align.CENTER_H, - align_v: align.CENTER_V, - text_style: text_style.NONE, - text: buffer[i].otp - }) + createWidget(widget.BUTTON, { + x: width / 2 - 40, + y: height / 2 - 20, + w: 80, + h: 80, + text: '+', + radius: 50, + text_size: 40, + normal_color: 0x303030, + press_color: 0x181c18, + click_func: () => { + push({ + url: 'page/tip' + }) + } + }) - totpHeight += margin + buttonHeight; - } - } - } + } else { + renderContainers(buffer) + renderTOTPs(buffer) + setInterval(() => { + renderWidgets.forEach(x => deleteWidget(x)) + renderTOTPs(buffer) + }, 500) + } + } }) + +function renderContainers(buffer) { + const { width, height } = getDeviceInfo() + const buttonWidth = width - width / 20; + const buttonHeight = height / 4; + const margin = 10; + let totpHeight = margin; + + for (let i = 0; i < buffer.length; i++) { + const otpData = TOTP.copy(buffer[i]).getOTP() + createWidget(widget.FILL_RECT, { + x: width / 2 - buttonWidth / 2, + y: totpHeight, + w: buttonWidth, + h: buttonHeight, + color: 0x303030, + radius: 20 + }) + createWidget(widget.TEXT, { + x: 0 + (width - buttonWidth) / 2, + y: totpHeight + 10, + w: width - (width - buttonWidth), + h: 26, + color: 0xa0a0a0, + text_size: 24, + align_h: align.CENTER_H, + align_v: align.CENTER_V, + text_style: text_style.NONE, + text: buffer[i].issuer + ': ' + buffer[i].client + }) + + totpHeight += margin + buttonHeight; + } +} +function renderTOTPs(buffer) { + const { width, height } = getDeviceInfo() + const buttonWidth = width - width / 20; + const buttonHeight = height / 4; + const margin = 10; + let totpHeight = margin; + + for (let i = 0; i < buffer.length; i++) { + const otpData = TOTP.copy(buffer[i]).getOTP() + renderWidgets.push( + createWidget(widget.TEXT, { + x: 0, + y: totpHeight + 50, + w: width, + h: 40, + color: 0xffffff, + text_size: 40, + align_h: align.CENTER_H, + align_v: align.CENTER_V, + text_style: text_style.NONE, + text: otpData.otp + })) + + const expireDif = Math.abs((((Date.now() - otpData.createdTime) / 1000) + / buffer[i].fetchTime) - 1) + renderWidgets.push( + expireTimeWg = createWidget(widget.ARC, { + x: buttonWidth - 50, + y: totpHeight + 52, + w: 40, + h: 40, + line_width: 5, + color: 0x1ca9c9, + start_angle: -90, + end_angle: (expireDif * 360) - 90, + text: expireDif + }) + ) + + totpHeight += margin + buttonHeight; + } +} + +function RenderExpireWg(otpData, totpHeight, buttonWidth, buffer, i) { + const interval = setInterval(() => { + const expireDif = Math.abs((((Date.now() - otpData.createdTime) / 1000) + / buffer[i].fetchTime) - 1) + if (Date.now() > otpData.expireTime) { + clearInterval(interval) + return + } + + deleteWidget(expireTimeWg) + expireTimeWg = createWidget(widget.ARC, { + x: buttonWidth - 50, + y: totpHeight + 52, + w: 40, + h: 40, + line_width: 5, + color: 0x1ca9c9, + start_angle: -90, + end_angle: (expireDif * 360) - 90, + text: expireDif + }) + }, 100) +} diff --git a/page/totplogic/totps.js b/page/totplogic/totps.js deleted file mode 100644 index 1aca2be..0000000 --- a/page/totplogic/totps.js +++ /dev/null @@ -1,11 +0,0 @@ -import { TOTP } from "../../lib/totp-quickjs"; - -export class TOTPBuffer{ - constructor(){ - - } - - getTOTPs(){ - return [new TOTP('JBSWY3DPEHPK3PXP').getOTP()] - } -} \ No newline at end of file