mirror of
https://github.com/lnbits/lnbits-legend.git
synced 2024-11-20 10:39:59 +01:00
feat: remember paired devices
This commit is contained in:
parent
713909292e
commit
83f998fe37
@ -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>
|
||||||
|
@ -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
|
},
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
|
@ -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 () {}
|
||||||
|
@ -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') : ''
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user