lnbits-legend/lnbits/extensions/watchonly/static/components/serial-signer/serial-signer.js

536 lines
16 KiB
JavaScript
Raw Normal View History

2022-07-26 20:06:07 +03:00
async function serialSigner(path) {
const t = await loadTemplateAsync(path)
Vue.component('serial-signer', {
name: 'serial-signer',
template: t,
props: ['sats-denominated'],
2022-07-26 20:06:07 +03:00
data: function () {
return {
2022-07-29 18:10:13 +03:00
network: 'Mainnet', // todo: hardcoded for now
2022-07-26 20:06:07 +03:00
selectedPort: null,
writableStreamClosed: null,
writer: null,
readableStreamClosed: null,
reader: null,
receivedData: '',
config: {},
2022-07-27 12:26:07 +03:00
2022-07-26 20:06:07 +03:00
hww: {
password: null,
showPassword: false,
mnemonic: null,
showMnemonic: false,
authenticated: false,
showPasswordDialog: false,
showWipeDialog: false,
showRestoreDialog: false,
2022-07-27 11:32:03 +03:00
showConfirmationDialog: false,
2022-07-26 20:06:07 +03:00
showSignedPsbt: false,
sendingPsbt: false,
signingPsbt: false,
loginResolve: null,
2022-08-01 18:53:26 +03:00
psbtSentResolve: null,
xpubResolve: null,
2022-07-28 12:39:06 +03:00
confirm: {
outputIndex: 0,
showFee: false
2022-07-28 12:39:06 +03:00
}
2022-07-27 12:26:07 +03:00
},
2022-07-28 12:39:06 +03:00
tx: null, // todo: move to hww
2022-07-27 12:26:07 +03:00
showConsole: false
2022-07-26 20:06:07 +03:00
}
},
methods: {
2022-07-28 12:39:06 +03:00
satBtc(val, showUnit = true) {
return satOrBtc(val, showUnit, this.satsDenominated)
},
2022-07-26 20:06:07 +03:00
openSerialPort: async function () {
if (!this.checkSerialPortSupported()) return false
if (this.selectedPort) return true
2022-07-26 20:06:07 +03:00
try {
navigator.serial.addEventListener('connect', event => {
console.log('### navigator.serial event: connected!', event)
})
navigator.serial.addEventListener('disconnect', () => {
console.log('### navigator.serial event: disconnected!', event)
2022-07-26 20:06:07 +03:00
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()
return true
2022-07-26 20:06:07 +03:00
} catch (error) {
this.selectedPort = null
this.$q.notify({
type: 'warning',
message: 'Cannot open serial port!',
caption: `${error}`,
timeout: 10000
})
return false
2022-07-26 20:06:07 +03:00
}
},
2022-07-27 09:16:36 +03:00
closeSerialPort: async function () {
try {
if (this.writer) this.writer.close()
if (this.writableStreamClosed) await this.writableStreamClosed
if (this.reader) this.reader.cancel()
if (this.readableStreamClosed)
await this.readableStreamClosed.catch(() => {
/* Ignore the error */
})
if (this.selectedPort) await this.selectedPort.close()
this.selectedPort = null
this.$q.notify({
type: 'positive',
message: 'Serial port disconnected!',
timeout: 5000
})
} catch (error) {
this.selectedPort = null
this.$q.notify({
type: 'warning',
message: 'Cannot close serial port!',
caption: `${error}`,
timeout: 10000
})
2022-07-27 09:27:51 +03:00
} finally {
this.hww.authenticated = false
2022-07-27 09:16:36 +03:00
}
},
isConnected: function () {
return !!this.selectedPort
},
isAuthenticated: function () {
return this.hww.authenticated
},
isAuthenticating: function () {
if (this.isAuthenticated()) return false
return new Promise(resolve => {
this.loginResolve = resolve
})
},
2022-07-27 11:32:03 +03:00
isSendingPsbt: async function () {
if (!this.hww.sendingPsbt) return false
return new Promise(resolve => {
this.psbtSentResolve = resolve
})
},
2022-08-01 18:53:26 +03:00
isFetchingXpub: async function () {
return new Promise(resolve => {
this.xpubResolve = resolve
})
},
2022-07-26 20:06:07 +03:00
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')
if (value) {
this.handleSerialPortResponse(value)
this.updateSerialPortConsole(value)
}
if (done) return
}
} catch (error) {
this.$q.notify({
type: 'warning',
message: 'Serial port communication error!',
caption: `${error}`,
timeout: 10000
})
}
}
},
handleSerialPortResponse: function (value) {
2022-08-01 18:53:26 +03:00
const command = value.split(' ')[0]
const commandData = value.substring(command.length).trim()
switch (command) {
case COMMAND_SIGN_PSBT:
this.handleSignResponse(commandData)
break
case COMMAND_PASSWORD:
this.handleLoginResponse(commandData)
break
case COMMAND_PASSWORD_CLEAR:
this.handleLogoutResponse(commandData)
break
case COMMAND_SEND_PSBT:
this.handleSendPsbtResponse(commandData)
break
case COMMAND_WIPE:
this.handleWipeResponse(commandData)
break
case COMMAND_XPUB:
this.handleXpubResponse(commandData)
break
default:
console.log('### console', value)
}
2022-07-26 20:06:07 +03:00
},
updateSerialPortConsole: function (value) {
this.receivedData += value + '\n'
2022-07-27 12:26:07 +03:00
const textArea = document.getElementById('serial-port-console')
2022-07-26 20:06:07 +03:00
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
})
}
},
2022-07-28 12:39:06 +03:00
hwwConfirmNext: async function () {
this.hww.confirm.outputIndex += 1
if (this.hww.confirm.outputIndex >= this.tx.outputs.length) {
this.hww.confirm.showFee = true
}
await this.writer.write(COMMAND_CONFIRM_NEXT + '\n')
},
2022-07-28 15:02:17 +03:00
cancelOperation: async function () {
try {
await this.writer.write(COMMAND_CANCEL + '\n')
} catch (error) {
this.$q.notify({
type: 'warning',
message: 'Failed to send cancel!',
caption: `${error}`,
timeout: 10000
})
}
},
2022-07-26 20:06:07 +03:00
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'
2022-07-29 18:10:13 +03:00
if (this.loginResolve) {
this.loginResolve(this.hww.authenticated)
}
2022-07-26 20:06:07 +03:00
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
})
}
},
2022-07-28 12:39:06 +03:00
hwwSendPsbt: async function (psbtBase64, tx) {
2022-07-26 20:06:07 +03:00
try {
2022-07-28 12:39:06 +03:00
this.tx = tx
2022-07-26 20:06:07 +03:00
this.hww.sendingPsbt = true
2022-07-29 18:10:13 +03:00
await this.writer.write(
COMMAND_SEND_PSBT + ' ' + this.network + ' ' + psbtBase64 + '\n'
)
2022-07-26 20:06:07 +03:00
this.$q.notify({
type: 'positive',
message: 'Data sent to serial port device!',
timeout: 5000
})
} catch (error) {
2022-07-27 11:32:03 +03:00
this.hww.sendingPsbt = false
2022-07-26 20:06:07 +03:00
this.$q.notify({
type: 'warning',
message: 'Failed to send data to serial port!',
caption: `${error}`,
timeout: 10000
})
}
},
handleSendPsbtResponse: function (res = '') {
2022-08-01 15:53:01 +03:00
try {
const psbtOK = res.trim() === '1'
if (!psbtOK) {
this.$q.notify({
type: 'warning',
message: 'Failed to send PSBT!',
caption: `${res}`,
timeout: 10000
})
return
}
this.hww.confirm.outputIndex = 0
this.hww.showConfirmationDialog = true
this.hww.confirm = {
outputIndex: 0,
showFee: false
}
this.hww.sendingPsbt = false
} catch (error) {
this.$q.notify({
type: 'warning',
message: 'Failed to send PSBT!',
caption: `${error}`,
timeout: 10000
})
} finally {
this.psbtSentResolve()
2022-07-28 12:39:06 +03:00
}
2022-07-26 20:06:07 +03:00
},
hwwSignPsbt: async function () {
try {
2022-07-28 12:39:06 +03:00
this.hww.showConfirmationDialog = false
2022-07-26 20:06:07 +03:00
this.hww.signingPsbt = true
await this.writer.write(COMMAND_SIGN_PSBT + '\n')
} 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'
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
})
}
},
2022-08-01 18:53:26 +03:00
hwwXpub: async function (path) {
try {
await this.writer.write(
COMMAND_XPUB + ' ' + this.network + ' ' + path + '\n'
)
} catch (error) {
this.$q.notify({
type: 'warning',
message: 'Failed to fetch XPub!',
caption: `${error}`,
timeout: 10000
})
}
},
handleXpubResponse: function (res = '') {
const args = res.trim().split(' ')
if (args.length < 3 || args[0].trim() !== '1') {
this.$q.notify({
type: 'warning',
message: 'Failed to fetch XPub!',
caption: `${res}`,
timeout: 10000
})
this.xpubResolve({})
return
}
const xpub = args[1].trim()
const fingerprint = args[2].trim()
this.xpubResolve({xpub, fingerprint})
},
2022-07-26 20:06:07 +03:00
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
}
2022-07-27 11:32:03 +03:00
},
updateSignedPsbt: async function (value) {
this.$emit('signed:psbt', value)
2022-07-26 20:06:07 +03:00
}
},
created: async function () {}
})
}