feat: remember paired devices

This commit is contained in:
Vlad Stan 2022-08-19 14:59:59 +03:00
parent 713909292e
commit 83f998fe37
5 changed files with 129 additions and 80 deletions

View File

@ -1,4 +1,15 @@
<div> <div>
<div class="row q-mt-md">
<div class="col-12">
<q-input
filled
dense
v-model.trim="config.name"
label="Name (optional)"
></q-input>
</div>
</div>
<q-separator class="q-mt-sm"></q-separator>
<div class="row q-mt-md"> <div class="row q-mt-md">
<div class="col-12"> <div class="col-12">
<q-input <q-input
@ -64,4 +75,28 @@
></q-input> ></q-input>
</div> </div>
</div> </div>
<q-separator class="q-mt-sm"></q-separator>
<div class="row q-mt-md">
<div class="col-12">
<q-input
filled
dense
v-model.trim="config.buttonOnePin"
type="number"
label="Pin Number (Button 1)"
></q-input>
</div>
</div>
<div class="row q-mt-md">
<div class="col-12">
<q-input
filled
dense
v-model.trim="config.buttonTwoPin"
type="number"
label="Pin Number (Button 2)"
></q-input>
</div>
</div>
</div> </div>

View File

@ -2,23 +2,15 @@ async function serialPortConfig(path) {
const t = await loadTemplateAsync(path) const t = await loadTemplateAsync(path)
Vue.component('serial-port-config', { Vue.component('serial-port-config', {
name: 'serial-port-config', name: 'serial-port-config',
props: ['config'],
template: t, template: t,
data() { data() {
return { return {
config: {
baudRate: 9600,
bufferSize: 255,
dataBits: 8,
flowControl: 'none',
parity: 'none',
stopBits: 1
}
} }
}, },
methods: { methods: {
getConfig: function () {
return this.config },
}
}
}) })
} }

View File

@ -46,6 +46,27 @@
> >
</q-item-section> </q-item-section>
</q-item> </q-item>
<q-item
v-for="device in pairedDevices"
:key="device.id"
v-if="!selectedPort"
clickable
v-close-popup
>
<q-item-section>
<q-item-label @click="openSerialPortConfig(device.id)"
>Paired Device ({{device.config.name || 'no-name'}})
</q-item-label>
<q-item-label caption @click="openSerialPortConfig(device.id)"
>{{device.id}}
</q-item-label>
<q-item-label caption @click="removePairedDevice(device.id)">
<q-btn v-close-popup flat color="grey" class="q-ml-auto"
>Forget</q-btn
>
</q-item-label>
</q-item-section>
</q-item>
<q-item <q-item
v-if="selectedPort" v-if="selectedPort"
clickable clickable
@ -123,33 +144,10 @@
<q-card class="q-pa-lg q-pt-xl lnbits__dialog-card"> <q-card class="q-pa-lg q-pt-xl lnbits__dialog-card">
<q-form @submit="hwwConfigAndConnect" class="q-gutter-md"> <q-form @submit="hwwConfigAndConnect" class="q-gutter-md">
<span>Enter Config</span> <span>Enter Config</span>
<q-input
filled
dense
v-model.trim="hww.deviceConfig.name"
label="Name (optional)"
></q-input>
<q-separator></q-separator>
<serial-port-config <serial-port-config
ref="serialPortConfig" ref="serialPortConfig"
:config="hww.config" :config="config"
></serial-port-config> ></serial-port-config>
<q-separator></q-separator>
<q-input
filled
dense
v-model.trim="hww.deviceConfig.buttonOnePin"
type="number"
label="Pin Number (Button 1)"
></q-input>
<q-input
filled
dense
v-model.trim="hww.deviceConfig.buttonTwoPin"
type="number"
label="Pin Number (Button 2)"
></q-input>
<div class="row q-mt-lg"> <div class="row q-mt-lg">
<q-btn unelevated color="primary" type="submit">Connect</q-btn> <q-btn unelevated color="primary" type="submit">Connect</q-btn>

View File

@ -36,12 +36,8 @@ async function serialSigner(path) {
xpubResolve: null, xpubResolve: null,
seedWordPosition: 1, seedWordPosition: 1,
showSeedDialog: false, showSeedDialog: false,
config: null, // config: null,
deviceConfig: {
name: null,
buttonOnePin: 0,
buttonTwoPin: 35
},
confirm: { confirm: {
outputIndex: 0, outputIndex: 0,
showFee: false showFee: false
@ -53,12 +49,30 @@ async function serialSigner(path) {
} }
}, },
computed: {
pairedDevices: {
get: function () {
return (
JSON.parse(window.localStorage.getItem('lnbits-paired-devices')) ||
[]
)
},
set: function (devices) {
window.localStorage.setItem(
'lnbits-paired-devices',
JSON.stringify(devices)
)
}
}
},
methods: { methods: {
satBtc(val, showUnit = true) { satBtc(val, showUnit = true) {
return satOrBtc(val, showUnit, this.satsDenominated) return satOrBtc(val, showUnit, this.satsDenominated)
}, },
openSerialPortDialog: async function () { openSerialPortDialog: async function () {
await this.openSerialPort() this.config = {...HWW_DEFAULT_CONFIG}
await this.openSerialPort(this.config)
}, },
openSerialPort: async function (config = {baudRate: 9600}) { openSerialPort: async function (config = {baudRate: 9600}) {
if (!this.checkSerialPortSupported()) return false if (!this.checkSerialPortSupported()) return false
@ -74,11 +88,10 @@ async function serialSigner(path) {
try { try {
this.selectedPort = await navigator.serial.requestPort() this.selectedPort = await navigator.serial.requestPort()
this.selectedPort.addEventListener('connect', event => { this.selectedPort.addEventListener('connect', event => {
console.log('### this.selectedPort event: connected!', event) // do nothing
}) })
this.selectedPort.addEventListener('disconnect', () => { this.selectedPort.addEventListener('disconnect', () => {
console.log('### this.selectedPort event: disconnected!', event)
this.selectedPort = null this.selectedPort = null
this.hww.authenticated = false this.hww.authenticated = false
this.$q.notify({ this.$q.notify({
@ -113,7 +126,13 @@ async function serialSigner(path) {
return false return false
} }
}, },
openSerialPortConfig: async function () { openSerialPortConfig: async function (deviceId) {
const device = this.getPairedDevice(deviceId)
if (device) {
this.config = device.config
} else {
this.config = {...HWW_DEFAULT_CONFIG}
}
this.hww.showConfigDialog = true this.hww.showConfigDialog = true
}, },
closeSerialPort: async function () { closeSerialPort: async function () {
@ -289,7 +308,6 @@ async function serialSigner(path) {
} }
}, },
handlePingResponse: function (res = '') { handlePingResponse: function (res = '') {
console.log('### handlePingResponse', res)
const [status, deviceId] = res.split(' ') const [status, deviceId] = res.split(' ')
this.deviceId = deviceId this.deviceId = deviceId
@ -373,8 +391,10 @@ async function serialSigner(path) {
}, },
hwwConfigAndConnect: async function () { hwwConfigAndConnect: async function () {
this.hww.showConfigDialog = false this.hww.showConfigDialog = false
const config = this.$refs.serialPortConfig.getConfig() if (this.config.deviceId) {
await this.openSerialPort(config) this.updatePairedDeviceConfig(this.config.deviceId, this.config)
}
await this.openSerialPort(this.config)
return true return true
}, },
hwwLogin: async function () { hwwLogin: async function () {
@ -525,7 +545,6 @@ async function serialSigner(path) {
}, },
hwwCheckPairing: async function () { hwwCheckPairing: async function () {
const iv = window.crypto.getRandomValues(new Uint8Array(16)) const iv = window.crypto.getRandomValues(new Uint8Array(16))
console.log('### this.sharedSecret', this.sharedSecret)
const encrypted = await this.encryptMessage( const encrypted = await this.encryptMessage(
this.sharedSecret, this.sharedSecret,
iv, iv,
@ -582,8 +601,8 @@ async function serialSigner(path) {
await this.sendCommandClearText(COMMAND_PAIR, [ await this.sendCommandClearText(COMMAND_PAIR, [
publicKeyHex, publicKeyHex,
this.hww.deviceConfig.buttonOnePin, this.config.buttonOnePin,
this.hww.deviceConfig.buttonTwoPin this.config.buttonTwoPin
]) ])
this.$q.notify({ this.$q.notify({
type: 'positive', type: 'positive',
@ -600,7 +619,6 @@ async function serialSigner(path) {
} }
}, },
handlePairResponse: async function (res = '') { handlePairResponse: async function (res = '') {
console.log('### handlePairResponse', res)
const [statusCode, data] = res.trim().split(' ') const [statusCode, data] = res.trim().split(' ')
let pubKeyHex, errorMessage, captionMessage let pubKeyHex, errorMessage, captionMessage
switch (statusCode) { switch (statusCode) {
@ -645,15 +663,14 @@ async function serialSigner(path) {
.bytesToHex(sharedSecredHash) .bytesToHex(sharedSecredHash)
.substring(0, 5) .substring(0, 5)
.toUpperCase() .toUpperCase()
console.log('### fingerprint', fingerprint)
//
LNbits.utils LNbits.utils
.confirmDialog('Confirm code from display: ' + fingerprint) .confirmDialog('Confirm code from display: ' + fingerprint)
.onOk(() => { .onOk(() => {
this.addPairedDevice( this.addPairedDevice(
this.deviceId, this.deviceId,
nobleSecp256k1.utils.bytesToHex(this.sharedSecret) nobleSecp256k1.utils.bytesToHex(this.sharedSecret),
this.config
) )
this.$q.notify({ this.$q.notify({
@ -773,7 +790,6 @@ async function serialSigner(path) {
}, },
handleShowSeedResponse: function (res = '') { handleShowSeedResponse: function (res = '') {
const args = res.trim().split(' ') const args = res.trim().split(' ')
console.log('### handleShowSeedResponse: ', res)
}, },
hwwRestore: async function () { hwwRestore: async function () {
try { try {
@ -817,7 +833,6 @@ async function serialSigner(path) {
}, },
sendCommandClearText: async function (command, attrs = []) { sendCommandClearText: async function (command, attrs = []) {
const message = [command].concat(attrs).join(' ') const message = [command].concat(attrs).join(' ')
console.log('### encryptedIvHex', message)
await this.writer.write(message + '\n') await this.writer.write(message + '\n')
}, },
extractCommand: async function (value) { extractCommand: async function (value) {
@ -863,7 +878,6 @@ async function serialSigner(path) {
const command = data const command = data
.substring(len.length + 1, +len + len.length + 1) .substring(len.length + 1, +len + len.length + 1)
.trim() .trim()
console.log('### decryptData ', data, command)
return command return command
} catch (error) { } catch (error) {
return '/error Failed to decrypt message from device!' return '/error Failed to decrypt message from device!'
@ -883,37 +897,35 @@ async function serialSigner(path) {
const decryptedBytes = aesCbc.decrypt(encryptedBytes) const decryptedBytes = aesCbc.decrypt(encryptedBytes)
return decryptedBytes return decryptedBytes
}, },
getPairedDevices: function () {
console.log(
'### getPairedDevices',
window.localStorage.getItem('lnbits-paired-devices')
)
return (
JSON.parse(window.localStorage.getItem('lnbits-paired-devices')) || []
)
},
getPairedDevice: function (deviceId) { getPairedDevice: function (deviceId) {
const devices = this.getPairedDevices() return this.pairedDevices.find(d => d.id === deviceId)
return devices.find(d => d.id === deviceId)
}, },
removePairedDevice: function (deviceId) { removePairedDevice: function (deviceId) {
const devices = this.getPairedDevices() const devices = this.pairedDevices
const deviceIndex = devices.indexOf(d => d.id === deviceId) const deviceIndex = devices.findIndex(d => d.id === deviceId)
if (deviceIndex !== -1) { if (deviceIndex !== -1) {
devices.splice(deviceIndex, 1) devices.splice(deviceIndex, 1)
} }
this.pairedDevices = devices
}, },
addPairedDevice: function (deviceId, sharedSecretHex) { addPairedDevice: function (deviceId, sharedSecretHex, config) {
const devices = this.getPairedDevices() const devices = this.pairedDevices
devices.push({ config.deviceId = deviceId
devices.unshift({
id: deviceId, id: deviceId,
sharedSecretHex: sharedSecretHex, sharedSecretHex: sharedSecretHex,
pairingDate: new Date().toISOString() pairingDate: new Date().toISOString(),
config
}) })
window.localStorage.setItem( this.pairedDevices = devices
'lnbits-paired-devices', },
JSON.stringify(devices) updatePairedDeviceConfig(deviceId, config) {
) const device = this.getPairedDevice(deviceId)
if (device) {
this.removePairedDevice(deviceId)
this.addPairedDevice(deviceId, device.sharedSecretHex, config)
}
} }
}, },
created: async function () {} created: async function () {}

View File

@ -19,6 +19,18 @@ const COMMAND_CHECK_PAIRING = '/check-pairing'
const DEFAULT_RECEIVE_GAP_LIMIT = 20 const DEFAULT_RECEIVE_GAP_LIMIT = 20
const PAIRING_CONTROL_TEXT = 'lnbits' const PAIRING_CONTROL_TEXT = 'lnbits'
const HWW_DEFAULT_CONFIG = Object.freeze({
name: '',
buttonOnePin: 0,
buttonTwoPin: 35,
baudRate: 9600,
bufferSize: 255,
dataBits: 8,
flowControl: 'none',
parity: 'none',
stopBits: 1
})
const blockTimeToDate = blockTime => const blockTimeToDate = blockTime =>
blockTime ? moment(blockTime * 1000).format('LLL') : '' blockTime ? moment(blockTime * 1000).format('LLL') : ''