diff --git a/lnbits/extensions/watchonly/static/components/serial-signer/serial-signer.html b/lnbits/extensions/watchonly/static/components/serial-signer/serial-signer.html new file mode 100644 index 000000000..84c48bc02 --- /dev/null +++ b/lnbits/extensions/watchonly/static/components/serial-signer/serial-signer.html @@ -0,0 +1,234 @@ +
+ + + + + Configure + Set the Serial Port communication parameters + + + + + + Login + Enter password for Hardware Wallet. + + + + + + Logout + Clear password for HWW. + + + + + + Restore + Restore wallet from existing word list. + + + + + Show Seed + Show seed on the Hardware Wallet display. + + + + + Wipe + Clean-up the wallet. New random seed. + + + + + Help + View available comands. + + + + + Console + Show the serial port communication messages + + + + + + + + Enter password for Hardware Wallet (8 numbers/letters) + + +
+ Login + Cancel +
+
+
+
+ + + + + This action will remove all data from the Hardware Wallet. Please + create a back-up for the seed! + + Enter new password for Hardware Wallet (8 numbers/letters) + + + This action cannot be reversed! + + +
+ Wipe + Cancel +
+
+
+
+ + + + + For test purposes only. Do not enter word list with real funds!!! + +


+ Enter new word list separated by space + + + +
+ Enter new password (8 numbers/letters) + + + +

+ + For test purposes only. Do not enter word list with real funds. + + +
+ Restore + Cancel +
+
+
+
+
diff --git a/lnbits/extensions/watchonly/static/components/serial-signer/serial-signer.js b/lnbits/extensions/watchonly/static/components/serial-signer/serial-signer.js new file mode 100644 index 000000000..47f0bf8a6 --- /dev/null +++ b/lnbits/extensions/watchonly/static/components/serial-signer/serial-signer.js @@ -0,0 +1,380 @@ +async function serialSigner(path) { + const t = await loadTemplateAsync(path) + Vue.component('serial-signer', { + name: 'serial-signer', + template: t, + + props: [], + data: function () { + return { + selectedPort: null, + writableStreamClosed: null, + writer: null, + readableStreamClosed: null, + reader: null, + showAdvancedConfig: false, + receivedData: '', + config: {}, + hww: { + password: null, + showPassword: false, + mnemonic: null, + showMnemonic: false, + authenticated: false, + showPasswordDialog: false, + showWipeDialog: false, + showRestoreDialog: false, + showConsole: false, + showSignedPsbt: false, + sendingPsbt: false, + signingPsbt: false, + psbtSent: false + } + } + }, + + methods: { + openSerialPort: async function () { + if (!this.checkSerialPortSupported()) return + console.log('### openSerialPort', this.selectedPort) + try { + navigator.serial.addEventListener('connect', event => { + console.log('### navigator.serial event: connected!', event) + }) + + navigator.serial.addEventListener('disconnect', () => { + this.hww.authenticated = false + this.$q.notify({ + type: 'warning', + message: 'Disconnected from Serial Port!', + timeout: 10000 + }) + }) + this.selectedPort = await navigator.serial.requestPort() + // Wait for the serial port to open. + await this.selectedPort.open({baudRate: 9600}) + this.startSerialPortReading() + + const textEncoder = new TextEncoderStream() + this.writableStreamClosed = textEncoder.readable.pipeTo( + this.selectedPort.writable + ) + + this.writer = textEncoder.writable.getWriter() + } catch (error) { + this.selectedPort = null + this.$q.notify({ + type: 'warning', + message: 'Cannot open serial port!', + caption: `${error}`, + timeout: 10000 + }) + } + }, + checkSerialPortSupported: function () { + if (!navigator.serial) { + this.$q.notify({ + type: 'warning', + message: 'Serial port communication not supported!', + caption: + 'Make sure your browser supports Serial Port and that you are using HTTPS.', + timeout: 10000 + }) + return false + } + return true + }, + startSerialPortReading: async function () { + const port = this.selectedPort + + while (port && port.readable) { + const textDecoder = new TextDecoderStream() + this.readableStreamClosed = port.readable.pipeTo(textDecoder.writable) + this.reader = textDecoder.readable.getReader() + const readStringUntil = readFromSerialPort(this.reader) + + try { + while (true) { + const {value, done} = await readStringUntil('\n') + console.log('### value', value) + if (value) { + this.handleSerialPortResponse(value) + this.updateSerialPortConsole(value) + } + console.log('### startSerialPortReading DONE', done) + if (done) return + } + } catch (error) { + this.$q.notify({ + type: 'warning', + message: 'Serial port communication error!', + caption: `${error}`, + timeout: 10000 + }) + } + } + console.log('### startSerialPortReading port', port) + }, + handleSerialPortResponse: function (value) { + const msg = value.split(' ') + if (msg[0] == COMMAND_SIGN_PSBT) this.handleSignResponse(msg[1]) + else if (msg[0] == COMMAND_PASSWORD) this.handleLoginResponse(msg[1]) + else if (msg[0] == COMMAND_PASSWORD_CLEAR) + this.handleLogoutResponse(msg[1]) + else if (msg[0] == COMMAND_SEND_PSBT) + this.handleSendPsbtResponse(msg[1]) + else if (msg[0] == COMMAND_WIPE) this.handleWipeResponse(msg[1]) + else console.log('### handleSerialPortResponse', value) + }, + updateSerialPortConsole: function (value) { + this.receivedData += value + '\n' + const textArea = document.getElementById( + 'watchonly-serial-port-data-input' + ) + if (textArea) textArea.scrollTop = textArea.scrollHeight + }, + hwwShowPasswordDialog: async function () { + try { + this.hww.showPasswordDialog = true + await this.writer.write(COMMAND_PASSWORD + '\n') + } catch (error) { + this.$q.notify({ + type: 'warning', + message: 'Failed to connect to Hardware Wallet!', + caption: `${error}`, + timeout: 10000 + }) + } + }, + hwwShowWipeDialog: async function () { + try { + this.hww.showWipeDialog = true + await this.writer.write(COMMAND_WIPE + '\n') + } catch (error) { + this.$q.notify({ + type: 'warning', + message: 'Failed to connect to Hardware Wallet!', + caption: `${error}`, + timeout: 10000 + }) + } + }, + hwwShowRestoreDialog: async function () { + try { + this.hww.showRestoreDialog = true + await this.writer.write(COMMAND_WIPE + '\n') + } catch (error) { + this.$q.notify({ + type: 'warning', + message: 'Failed to connect to Hardware Wallet!', + caption: `${error}`, + timeout: 10000 + }) + } + }, + hwwLogin: async function () { + try { + await this.writer.write( + COMMAND_PASSWORD + ' ' + this.hww.password + '\n' + ) + } catch (error) { + this.$q.notify({ + type: 'warning', + message: 'Failed to send password to Hardware Wallet!', + caption: `${error}`, + timeout: 10000 + }) + } finally { + this.hww.showPasswordDialog = false + this.hww.password = null + this.hww.showPassword = false + } + }, + handleLoginResponse: function (res = '') { + this.hww.authenticated = res.trim() === '1' + if (this.hww.authenticated) { + this.$q.notify({ + type: 'positive', + message: 'Login successfull!', + timeout: 10000 + }) + } else { + this.$q.notify({ + type: 'warning', + message: 'Wrong password, try again!', + timeout: 10000 + }) + } + }, + hwwLogout: async function () { + try { + await this.writer.write(COMMAND_PASSWORD_CLEAR + '\n') + } catch (error) { + this.$q.notify({ + type: 'warning', + message: 'Failed to logout from Hardware Wallet!', + caption: `${error}`, + timeout: 10000 + }) + } + }, + handleLogoutResponse: function (res = '') { + this.hww.authenticated = !(res.trim() === '1') + if (this.hww.authenticated) { + this.$q.notify({ + type: 'warning', + message: 'Failed to logout from Hardware Wallet', + timeout: 10000 + }) + } + }, + hwwExecuteDefaultCommand: function () { + if (this.hww.authenticated) { + this.hwwSendPsbt() + } else { + this.hwwShowPasswordDialog() + } + }, + hwwSendPsbt: async function () { + try { + this.hww.sendingPsbt = true + await this.writer.write( + COMMAND_SEND_PSBT + ' ' + this.payment.psbtBase64 + '\n' + ) + this.$q.notify({ + type: 'positive', + message: 'Data sent to serial port device!', + timeout: 5000 + }) + } catch (error) { + this.$q.notify({ + type: 'warning', + message: 'Failed to send data to serial port!', + caption: `${error}`, + timeout: 10000 + }) + } + }, + handleSendPsbtResponse: function (res = '') { + this.hww.psbtSent = true + this.hww.sendingPsbt = false + }, + hwwSignPsbt: async function () { + try { + this.hww.signingPsbt = true + await this.writer.write(COMMAND_SIGN_PSBT + '\n') + this.$q.notify({ + type: 'positive', + message: 'PSBT signed!', + timeout: 5000 + }) + } catch (error) { + this.$q.notify({ + type: 'warning', + message: 'Failed to sign PSBT!', + caption: `${error}`, + timeout: 10000 + }) + } + }, + handleSignResponse: function (res = '') { + this.hww.signingPsbt = false + this.updateSignedPsbt(res) + if (this.hww.authenticated) { + this.$q.notify({ + type: 'positive', + message: 'Transaction Signed', + timeout: 10000 + }) + } + }, + hwwHelp: async function () { + try { + await this.writer.write(COMMAND_HELP + '\n') + this.$q.notify({ + type: 'positive', + message: 'Check display or console for details!', + timeout: 5000 + }) + } catch (error) { + this.$q.notify({ + type: 'warning', + message: 'Failed to ask for help!', + caption: `${error}`, + timeout: 10000 + }) + } + }, + hwwWipe: async function () { + try { + this.hww.showWipeDialog = false + await this.writer.write(COMMAND_WIPE + ' ' + this.hww.password + '\n') + } catch (error) { + this.$q.notify({ + type: 'warning', + message: 'Failed to ask for help!', + caption: `${error}`, + timeout: 10000 + }) + } finally { + this.hww.password = null + this.hww.showPassword = false + } + }, + handleWipeResponse: function (res = '') { + const wiped = res.trim() === '1' + console.log('### wiped', wiped) + if (wiped) { + this.$q.notify({ + type: 'positive', + message: 'Wallet wiped!', + timeout: 10000 + }) + } else { + this.$q.notify({ + type: 'warning', + message: 'Failed to wipe wallet!', + caption: `${error}`, + timeout: 10000 + }) + } + }, + hwwShowSeed: async function () { + try { + await this.writer.write(COMMAND_SEED + '\n') + } catch (error) { + this.$q.notify({ + type: 'warning', + message: 'Failed to show seed!', + caption: `${error}`, + timeout: 10000 + }) + } + }, + hwwRestore: async function () { + try { + await this.writer.write( + COMMAND_RESTORE + ' ' + this.hww.mnemonic + '\n' + ) + await this.writer.write( + COMMAND_PASSWORD + ' ' + this.hww.password + '\n' + ) + } catch (error) { + this.$q.notify({ + type: 'warning', + message: 'Failed to restore from seed!', + caption: `${error}`, + timeout: 10000 + }) + } finally { + this.hww.showRestoreDialog = false + this.hww.mnemonic = null + this.hww.showMnemonic = false + this.hww.password = null + this.hww.showPassword = false + } + } + }, + created: async function () {} + }) +} diff --git a/lnbits/extensions/watchonly/static/components/wallet-config/wallet-config.html b/lnbits/extensions/watchonly/static/components/wallet-config/wallet-config.html index 269110692..137c53b95 100644 --- a/lnbits/extensions/watchonly/static/components/wallet-config/wallet-config.html +++ b/lnbits/extensions/watchonly/static/components/wallet-config/wallet-config.html @@ -1,12 +1,7 @@
-
-
-
{{satBtc(total)}}
-
-
-
+
+
+
+
{{satBtc(total)}}
+
+
+
+ +
diff --git a/lnbits/extensions/watchonly/static/js/index.js b/lnbits/extensions/watchonly/static/js/index.js index 2d050c874..d493e1c76 100644 --- a/lnbits/extensions/watchonly/static/js/index.js +++ b/lnbits/extensions/watchonly/static/js/index.js @@ -9,9 +9,7 @@ const watchOnly = async () => { await feeRate('static/components/fee-rate/fee-rate.html') await sendTo('static/components/send-to/send-to.html') await payment('static/components/payment/payment.html') - - //emplate static/components/payment/payment.html - //lnbits/extensions/watchonly/static/components/payment/payment.html + await serialSigner('static/components/serial-signer/serial-signer.html') Vue.filter('reverse', function (value) { // slice to make a copy of array, then reverse the copy @@ -274,56 +272,7 @@ const watchOnly = async () => { } }, //################### SERIAL PORT ################### - checkSerialPortSupported: function () { - if (!navigator.serial) { - this.$q.notify({ - type: 'warning', - message: 'Serial port communication not supported!', - caption: - 'Make sure your browser supports Serial Port and that you are using HTTPS.', - timeout: 10000 - }) - return false - } - return true - }, - openSerialPort: async function () { - if (!this.checkSerialPortSupported()) return - console.log('### openSerialPort', this.serial.selectedPort) - try { - navigator.serial.addEventListener('connect', event => { - console.log('### navigator.serial event: connected!', event) - }) - navigator.serial.addEventListener('disconnect', () => { - this.hww.authenticated = false - this.$q.notify({ - type: 'warning', - message: 'Disconnected from Serial Port!', - timeout: 10000 - }) - }) - this.serial.selectedPort = await navigator.serial.requestPort() - // Wait for the serial port to open. - await this.serial.selectedPort.open({baudRate: 9600}) - this.startSerialPortReading() - - const textEncoder = new TextEncoderStream() - this.serial.writableStreamClosed = textEncoder.readable.pipeTo( - this.serial.selectedPort.writable - ) - - this.serial.writer = textEncoder.writable.getWriter() - } catch (error) { - this.serial.selectedPort = null - this.$q.notify({ - type: 'warning', - message: 'Cannot open serial port!', - caption: `${error}`, - timeout: 10000 - }) - } - }, closeSerialPort: async function () { try { console.log('### closeSerialPort', this.serial.selectedPort) @@ -354,305 +303,11 @@ const watchOnly = async () => { } }, - startSerialPortReading: async function () { - const port = this.serial.selectedPort - - while (port && port.readable) { - const textDecoder = new TextDecoderStream() - this.serial.readableStreamClosed = port.readable.pipeTo( - textDecoder.writable - ) - this.serial.reader = textDecoder.readable.getReader() - const readStringUntil = readFromSerialPort(this.serial) - - try { - while (true) { - const {value, done} = await readStringUntil('\n') - console.log('### value', value) - if (value) { - this.handleSerialPortResponse(value) - this.updateSerialPortConsole(value) - } - console.log('### startSerialPortReading DONE', done) - if (done) return - } - } catch (error) { - this.$q.notify({ - type: 'warning', - message: 'Serial port communication error!', - caption: `${error}`, - timeout: 10000 - }) - } - } - console.log('### startSerialPortReading port', port) - }, - - handleSerialPortResponse: function (value) { - const msg = value.split(' ') - if (msg[0] == COMMAND_SIGN_PSBT) this.handleSignResponse(msg[1]) - else if (msg[0] == COMMAND_PASSWORD) this.handleLoginResponse(msg[1]) - else if (msg[0] == COMMAND_PASSWORD_CLEAR) - this.handleLogoutResponse(msg[1]) - else if (msg[0] == COMMAND_SEND_PSBT) - this.handleSendPsbtResponse(msg[1]) - else if (msg[0] == COMMAND_WIPE) this.handleWipeResponse(msg[1]) - else console.log('### handleSerialPortResponse', value) - }, - updateSerialPortConsole: function (value) { - this.serial.receivedData += value + '\n' - const textArea = document.getElementById( - 'watchonly-serial-port-data-input' - ) - if (textArea) textArea.scrollTop = textArea.scrollHeight - }, sharePsbtWithAnimatedQRCode: async function () { console.log('### sharePsbtWithAnimatedQRCode') }, //################### HARDWARE WALLET ################### - hwwShowPasswordDialog: async function () { - try { - this.hww.showPasswordDialog = true - await this.serial.writer.write(COMMAND_PASSWORD + '\n') - } catch (error) { - this.$q.notify({ - type: 'warning', - message: 'Failed to connect to Hardware Wallet!', - caption: `${error}`, - timeout: 10000 - }) - } - }, - hwwShowWipeDialog: async function () { - try { - this.hww.showWipeDialog = true - await this.serial.writer.write(COMMAND_WIPE + '\n') - } catch (error) { - this.$q.notify({ - type: 'warning', - message: 'Failed to connect to Hardware Wallet!', - caption: `${error}`, - timeout: 10000 - }) - } - }, - hwwShowRestoreDialog: async function () { - try { - this.hww.showRestoreDialog = true - await this.serial.writer.write(COMMAND_WIPE + '\n') - } catch (error) { - this.$q.notify({ - type: 'warning', - message: 'Failed to connect to Hardware Wallet!', - caption: `${error}`, - timeout: 10000 - }) - } - }, - hwwLogin: async function () { - try { - await this.serial.writer.write( - COMMAND_PASSWORD + ' ' + this.hww.password + '\n' - ) - } catch (error) { - this.$q.notify({ - type: 'warning', - message: 'Failed to send password to Hardware Wallet!', - caption: `${error}`, - timeout: 10000 - }) - } finally { - this.hww.showPasswordDialog = false - this.hww.password = null - this.hww.showPassword = false - } - }, - handleLoginResponse: function (res = '') { - this.hww.authenticated = res.trim() === '1' - if (this.hww.authenticated) { - this.$q.notify({ - type: 'positive', - message: 'Login successfull!', - timeout: 10000 - }) - } else { - this.$q.notify({ - type: 'warning', - message: 'Wrong password, try again!', - timeout: 10000 - }) - } - }, - hwwLogout: async function () { - try { - await this.serial.writer.write(COMMAND_PASSWORD_CLEAR + '\n') - } catch (error) { - this.$q.notify({ - type: 'warning', - message: 'Failed to logout from Hardware Wallet!', - caption: `${error}`, - timeout: 10000 - }) - } - }, - handleLogoutResponse: function (res = '') { - this.hww.authenticated = !(res.trim() === '1') - if (this.hww.authenticated) { - this.$q.notify({ - type: 'warning', - message: 'Failed to logout from Hardware Wallet', - timeout: 10000 - }) - } - }, - hwwExecuteDefaultCommand: function () { - if (this.hww.authenticated) { - this.hwwSendPsbt() - } else { - this.hwwShowPasswordDialog() - } - }, - hwwSendPsbt: async function () { - try { - this.hww.sendingPsbt = true - await this.serial.writer.write( - COMMAND_SEND_PSBT + ' ' + this.payment.psbtBase64 + '\n' - ) - this.$q.notify({ - type: 'positive', - message: 'Data sent to serial port device!', - timeout: 5000 - }) - } catch (error) { - this.$q.notify({ - type: 'warning', - message: 'Failed to send data to serial port!', - caption: `${error}`, - timeout: 10000 - }) - } - }, - handleSendPsbtResponse: function (res = '') { - this.hww.psbtSent = true - this.hww.sendingPsbt = false - }, - hwwSignPsbt: async function () { - try { - this.hww.signingPsbt = true - await this.serial.writer.write(COMMAND_SIGN_PSBT + '\n') - this.$q.notify({ - type: 'positive', - message: 'PSBT signed!', - timeout: 5000 - }) - } catch (error) { - this.$q.notify({ - type: 'warning', - message: 'Failed to sign PSBT!', - caption: `${error}`, - timeout: 10000 - }) - } - }, - handleSignResponse: function (res = '') { - this.hww.signingPsbt = false - this.updateSignedPsbt(res) - if (this.hww.authenticated) { - this.$q.notify({ - type: 'positive', - message: 'Transaction Signed', - timeout: 10000 - }) - } - }, - hwwHelp: async function () { - try { - await this.serial.writer.write(COMMAND_HELP + '\n') - this.$q.notify({ - type: 'positive', - message: 'Check display or console for details!', - timeout: 5000 - }) - } catch (error) { - this.$q.notify({ - type: 'warning', - message: 'Failed to ask for help!', - caption: `${error}`, - timeout: 10000 - }) - } - }, - hwwWipe: async function () { - try { - this.hww.showWipeDialog = false - await this.serial.writer.write( - COMMAND_WIPE + ' ' + this.hww.password + '\n' - ) - } catch (error) { - this.$q.notify({ - type: 'warning', - message: 'Failed to ask for help!', - caption: `${error}`, - timeout: 10000 - }) - } finally { - this.hww.password = null - this.hww.showPassword = false - } - }, - handleWipeResponse: function (res = '') { - const wiped = res.trim() === '1' - console.log('### wiped', wiped) - if (wiped) { - this.$q.notify({ - type: 'positive', - message: 'Wallet wiped!', - timeout: 10000 - }) - } else { - this.$q.notify({ - type: 'warning', - message: 'Failed to wipe wallet!', - caption: `${error}`, - timeout: 10000 - }) - } - }, - hwwShowSeed: async function () { - try { - await this.serial.writer.write(COMMAND_SEED + '\n') - } catch (error) { - this.$q.notify({ - type: 'warning', - message: 'Failed to show seed!', - caption: `${error}`, - timeout: 10000 - }) - } - }, - hwwRestore: async function () { - try { - await this.serial.writer.write( - COMMAND_RESTORE + ' ' + this.hww.mnemonic + '\n' - ) - await this.serial.writer.write( - COMMAND_PASSWORD + ' ' + this.hww.password + '\n' - ) - } catch (error) { - this.$q.notify({ - type: 'warning', - message: 'Failed to restore from seed!', - caption: `${error}`, - timeout: 10000 - }) - } finally { - this.hww.showRestoreDialog = false - this.hww.mnemonic = null - this.hww.showMnemonic = false - this.hww.password = null - this.hww.showPassword = false - } - }, + //################### UTXOs ################### scanAllAddresses: async function () { await this.$refs.addressList.refreshAddresses() diff --git a/lnbits/extensions/watchonly/static/js/utils.js b/lnbits/extensions/watchonly/static/js/utils.js index d9559cf31..7080ab1e1 100644 --- a/lnbits/extensions/watchonly/static/js/utils.js +++ b/lnbits/extensions/watchonly/static/js/utils.js @@ -110,7 +110,7 @@ const ACCOUNT_TYPES = { const getAccountDescription = type => ACCOUNT_TYPES[type] || 'nonstandard' -const readFromSerialPort = serial => { +const readFromSerialPort = reader => { let partialChunk let fulliness = [] @@ -123,7 +123,7 @@ const readFromSerialPort = serial => { partialChunk = undefined } while (true) { - const {value, done} = await serial.reader.read() + const {value, done} = await reader.read() console.log('### serial read', value) if (value) { const values = value.split(separator) diff --git a/lnbits/extensions/watchonly/templates/watchonly/index.html b/lnbits/extensions/watchonly/templates/watchonly/index.html index de3c500d6..fea6b7f1d 100644 --- a/lnbits/extensions/watchonly/templates/watchonly/index.html +++ b/lnbits/extensions/watchonly/templates/watchonly/index.html @@ -230,96 +230,6 @@ :label="hww.authenticated ? 'Sign Tx' : 'Login'" @click="hwwExecuteDefaultCommand()" > - - - - Login - Enter password for Hardware Wallet. - - - - - - Logout - Clear password for HWW. - - - - - Sign - Sign transaction on Hardware Wallet - - - - - Restore - Restore wallet from existing word - list. - - - - - Show Seed - Show seed on the Hardware Wallet - display. - - - - - Wipe - Clean-up the wallet. New random - seed. - - - - - Help - View available comands. - - -
@@ -577,139 +487,6 @@ - - - - Enter password for Hardware Wallet (8 numbers/letters) - - -
- Login - Cancel -
-
-
-
- - - - - This action will remove all data from the Hardware Wallet. Please - create a back-up for the seed! - - Enter new password for Hardware Wallet (8 numbers/letters) - - - This action cannot be reversed! - - -
- Wipe - Cancel -
-
-
-
- - - - - For test purposes only. Do not enter word list with real funds!!! - -


- Enter new word list separated by space - - - -
- Enter new password (8 numbers/letters) - - - -

- - For test purposes only. Do not enter word list with real funds. - - -
- Restore - Cancel -
-
-
-
{% endraw %}
@@ -733,5 +510,6 @@ + {% endblock %}