2020-10-05 12:17:18 +09:00
/// <reference path="vaultbridge.js" />
2019-11-11 14:22:04 +09:00
/// file: vaultbridge.js
var vaultui = ( function ( ) {
/ * *
* @ param { string } type
* @ param { string } txt
* @ param { string } id
* /
2023-10-27 11:54:15 +09:00
function VaultFeedback ( type , txt , id ) {
2019-11-11 14:22:04 +09:00
var self = this ;
this . type = type ;
this . txt = txt ;
this . id = id ;
/ * *
* @ param { string } str
* @ param { string } by
* /
this . replace = function ( str , by ) {
2023-10-27 11:54:15 +09:00
return new VaultFeedback ( self . type , self . txt . replace ( str , by ) , self . id ) ;
2019-11-11 14:22:04 +09:00
} ;
}
var VaultFeedbacks = {
2023-10-27 11:54:15 +09:00
vaultLoading : new VaultFeedback ( "?" , "Checking BTCPay Server Vault is running..." , "vault-loading" ) ,
vaultDenied : new VaultFeedback ( "failed" , "The user declined access to the vault." , "vault-denied" ) ,
vaultGranted : new VaultFeedback ( "ok" , "Access to vault granted by owner." , "vault-granted" ) ,
noVault : new VaultFeedback ( "failed" , "BTCPay Server Vault does not seem to be running, you can download it on <a target=\"_blank\" href=\"https://github.com/btcpayserver/BTCPayServer.Vault/releases/latest\">Github</a>." , "no-vault" ) ,
noWebsockets : new VaultFeedback ( "failed" , "Web sockets are not supported by the browser." , "no-websocket" ) ,
errorWebsockets : new VaultFeedback ( "failed" , "Error of the websocket while connecting to the backend." , "error-websocket" ) ,
bridgeConnected : new VaultFeedback ( "ok" , "BTCPayServer successfully connected to the vault." , "bridge-connected" ) ,
vaultNeedUpdate : new VaultFeedback ( "failed" , "Your BTCPay Server Vault version is outdated. Please <a target=\"_blank\" href=\"https://github.com/btcpayserver/BTCPayServer.Vault/releases/latest\">download</a> the latest version." , "vault-outdated" ) ,
noDevice : new VaultFeedback ( "failed" , "No device connected." , "no-device" ) ,
needInitialized : new VaultFeedback ( "failed" , "The device has not been initialized." , "need-initialized" ) ,
fetchingDevice : new VaultFeedback ( "?" , "Fetching device..." , "fetching-device" ) ,
deviceFound : new VaultFeedback ( "ok" , "Device found: {{0}}" , "device-selected" ) ,
fetchingXpubs : new VaultFeedback ( "?" , "Fetching public keys..." , "fetching-xpubs" ) ,
askXpubs : new VaultFeedback ( "?" , "Select your address type and account" , "fetching-xpubs" ) ,
fetchedXpubs : new VaultFeedback ( "ok" , "Public keys successfully fetched." , "xpubs-fetched" ) ,
unexpectedError : new VaultFeedback ( "failed" , "An unexpected error happened. ({{0}})" , "unknown-error" ) ,
invalidNetwork : new VaultFeedback ( "failed" , "The device is targeting a different chain." , "invalid-network" ) ,
needPin : new VaultFeedback ( "?" , "Enter the pin." , "need-pin" ) ,
incorrectPin : new VaultFeedback ( "failed" , "Incorrect pin code." , "incorrect-pin" ) ,
invalidPasswordConfirmation : new VaultFeedback ( "failed" , "Invalid password confirmation." , "invalid-password-confirm" ) ,
wrongWallet : new VaultFeedback ( "failed" , "This device can't sign the transaction. (Wrong device, wrong passphrase or wrong device fingerprint in your wallet settings)" , "wrong-wallet" ) ,
wrongKeyPath : new VaultFeedback ( "failed" , "This device can't sign the transaction. (The wallet keypath in your wallet settings seems incorrect)" , "wrong-keypath" ) ,
needPassphrase : new VaultFeedback ( "?" , "Enter the passphrase." , "need-passphrase" ) ,
needPassphraseOnDevice : new VaultFeedback ( "?" , "Please, enter the passphrase on the device." , "need-passphrase-on-device" ) ,
signingTransaction : new VaultFeedback ( "?" , "Please review and confirm the transaction on your device..." , "ask-signing" ) ,
reviewAddress : new VaultFeedback ( "?" , "Sending... Please review the address on your device..." , "ask-signing" ) ,
signingRejected : new VaultFeedback ( "failed" , "The user refused to sign the transaction" , "user-reject" ) ,
2019-11-11 14:22:04 +09:00
} ;
/ * *
* @ param { string } backend _uri
* /
function VaultBridgeUI ( backend _uri ) {
/ * *
* @ type { VaultBridgeUI }
* /
var self = this ;
2023-12-06 09:17:58 +09:00
/ * *
* @ type { string }
* /
2019-11-11 14:22:04 +09:00
this . backend _uri = backend _uri ;
/ * *
* @ type { vault . VaultBridge }
* /
this . bridge = null ;
/ * *
* @ type { string }
* /
this . psbt = null ;
2019-12-04 15:54:08 +09:00
this . xpub = null ;
2020-10-05 12:17:18 +09:00
this . retryShowing = false ;
function showRetry ( ) {
2021-02-11 11:48:54 +01:00
var button = $ ( "#vault-retry" ) ;
2020-10-05 12:17:18 +09:00
self . retryShowing = true ;
button . show ( ) ;
}
2023-10-27 11:54:15 +09:00
this . currentFeedback = 1 ;
2019-11-11 14:22:04 +09:00
/ * *
* @ param { VaultFeedback } feedback
* /
function show ( feedback ) {
2024-05-20 01:57:46 +02:00
const $icon = document . querySelector ( ` .vault-feedback.vault-feedback ${ self . currentFeedback } .vault-feedback-icon ` ) ;
let iconClasses = '' ;
2019-11-11 14:22:04 +09:00
if ( feedback . type == "?" ) {
2024-05-20 01:57:46 +02:00
iconClasses = "icon-dots feedback-icon-loading" ;
2019-11-11 14:22:04 +09:00
}
else if ( feedback . type == "ok" ) {
2024-05-20 01:57:46 +02:00
iconClasses = "icon-checkmark feedback-icon-success" ;
$icon . innerHTML = $icon . innerHTML . replace ( "#dots" , "#checkmark" ) ;
2019-11-11 14:22:04 +09:00
}
else if ( feedback . type == "failed" ) {
2024-05-20 01:57:46 +02:00
iconClasses = "icon-cross feedback-icon-failed" ;
$icon . innerHTML = $icon . innerHTML . replace ( "#dots" , "#cross" ) ;
2020-10-05 12:17:18 +09:00
showRetry ( ) ;
2019-11-11 14:22:04 +09:00
}
2024-05-20 01:57:46 +02:00
$icon . setAttribute ( 'class' , ` vault-feedback-icon icon me-2 ${ iconClasses } ` ) ;
const $content = document . querySelector ( ` .vault-feedback.vault-feedback ${ self . currentFeedback } .vault-feedback-content ` ) ;
$content . innerHTML = feedback . txt ;
2023-10-27 11:54:15 +09:00
if ( feedback . type === 'ok' )
self . currentFeedback ++ ;
if ( feedback . type === 'failed' )
self . currentFeedback = 1 ;
2019-11-11 14:22:04 +09:00
}
function showError ( json ) {
if ( json . hasOwnProperty ( "error" ) ) {
for ( var key in VaultFeedbacks ) {
if ( VaultFeedbacks . hasOwnProperty ( key ) && VaultFeedbacks [ key ] . id == json . error ) {
2019-12-05 22:03:17 +09:00
if ( VaultFeedbacks . unexpectedError === VaultFeedbacks [ key ] ) {
show ( VaultFeedbacks . unexpectedError . replace ( "{{0}}" , json . message ) ) ;
}
else {
show ( VaultFeedbacks [ key ] ) ;
}
2019-11-11 14:22:04 +09:00
if ( json . hasOwnProperty ( "details" ) )
console . warn ( json . details ) ;
return ;
}
}
2019-12-05 22:03:17 +09:00
show ( VaultFeedbacks . unexpectedError . replace ( "{{0}}" , json . message ) ) ;
2019-11-11 14:22:04 +09:00
if ( json . hasOwnProperty ( "details" ) )
console . warn ( json . details ) ;
}
}
2023-12-06 09:17:58 +09:00
function showMessage ( message ) {
let type = 'ok' ;
if ( message . type === 'Error' )
type = 'failed' ;
if ( message . type === 'Processing' )
type = '?' ;
show ( new VaultFeedback ( type , message . message , "" ) ) ;
if ( type . debug )
console . warn ( type . debug ) ;
}
2019-11-11 14:22:04 +09:00
async function needRetry ( json ) {
if ( json . hasOwnProperty ( "error" ) ) {
var handled = false ;
if ( json . error === "need-device" ) {
handled = true ;
if ( await self . askForDevice ( ) )
return true ;
}
if ( json . error === "need-pin" ) {
handled = true ;
if ( await self . askForPin ( ) )
return true ;
}
2019-11-22 19:12:30 +09:00
if ( json . error === "need-passphrase" ) {
handled = true ;
if ( await self . askForPassphrase ( ) )
return true ;
}
2019-12-10 18:16:52 +09:00
if ( json . error === "need-passphrase-on-device" ) {
2019-12-04 17:16:37 +09:00
handled = true ;
2019-12-10 18:16:52 +09:00
show ( VaultFeedbacks . needPassphraseOnDevice ) ;
self . bridge . socket . send ( "ask-passphrase" ) ;
2019-12-04 17:16:37 +09:00
var json = await self . bridge . waitBackendMessage ( ) ;
if ( json . hasOwnProperty ( "error" ) ) {
showError ( json ) ;
return false ;
}
return true ;
}
2019-11-11 14:22:04 +09:00
if ( ! handled ) {
showError ( json ) ;
}
}
return false ;
}
2020-10-05 12:17:18 +09:00
this . waitRetryPushed = function ( ) {
2021-02-11 11:48:54 +01:00
var button = $ ( "#vault-retry" ) ;
2020-10-05 12:17:18 +09:00
return new Promise ( function ( resolve ) {
button . click ( function ( ) {
// Cleanup old feedback
var icon = $ ( ".vault-feedback-icon" ) ;
icon . removeClass ( ) ;
icon . addClass ( "vault-feedback-icon" ) ;
var content = $ ( ".vault-feedback-content" ) ;
content . html ( '' ) ;
///////////////////
button . hide ( ) ;
self . retryShowing = false ;
resolve ( true ) ;
} ) ;
} ) ;
} ;
2019-11-11 14:22:04 +09:00
this . ensureConnectedToBackend = async function ( ) {
2020-10-05 12:17:18 +09:00
if ( self . retryShowing ) {
await self . waitRetryPushed ( ) ;
}
2023-10-27 11:54:15 +09:00
if ( ! self . bridge || self . bridge . socket . readyState !== 1 ) {
2019-11-11 14:22:04 +09:00
$ ( "#vault-dropdown" ) . css ( "display" , "none" ) ;
show ( VaultFeedbacks . vaultLoading ) ;
try {
await vault . askVaultPermission ( ) ;
} catch ( ex ) {
if ( ex == vault . errors . notRunning )
show ( VaultFeedbacks . noVault ) ;
else if ( ex == vault . errors . denied )
show ( VaultFeedbacks . vaultDenied ) ;
return false ;
}
show ( VaultFeedbacks . vaultGranted ) ;
try {
self . bridge = await vault . connectToBackendSocket ( self . backend _uri ) ;
show ( VaultFeedbacks . bridgeConnected ) ;
} catch ( ex ) {
if ( ex == vault . errors . socketNotSupported )
show ( VaultFeedbacks . noWebsockets ) ;
if ( ex == vault . errors . socketError )
show ( VaultFeedbacks . errorWebsockets ) ;
return false ;
}
}
return true ;
} ;
2023-12-06 09:17:58 +09:00
this . sendBackendCommand = async function ( command ) {
if ( ! self . bridge || self . bridge . socket . readyState !== 1 ) {
self . bridge = await vault . connectToBackendSocket ( self . backend _uri ) ;
}
show ( VaultFeedbacks . vaultLoading ) ;
self . bridge . socket . send ( command ) ;
while ( true ) {
var json = await self . bridge . waitBackendMessage ( ) ;
if ( json . command === 'showMessage' ) {
showMessage ( json ) ;
if ( json . type === "Error" ) {
showRetry ( ) ;
return false ;
}
}
if ( json . command == 'done' ) {
return true ;
}
}
}
2019-12-10 21:22:46 +09:00
this . askForDisplayAddress = async function ( rootedKeyPath ) {
if ( ! await self . ensureConnectedToBackend ( ) )
return false ;
show ( VaultFeedbacks . reviewAddress ) ;
self . bridge . socket . send ( "display-address" ) ;
self . bridge . socket . send ( rootedKeyPath ) ;
var json = await self . bridge . waitBackendMessage ( ) ;
if ( json . hasOwnProperty ( "error" ) ) {
if ( await needRetry ( json ) )
return await self . askForDisplayAddress ( rootedKeyPath ) ;
return false ;
}
return true ;
}
2019-11-11 14:22:04 +09:00
this . askForDevice = async function ( ) {
if ( ! await self . ensureConnectedToBackend ( ) )
return false ;
show ( VaultFeedbacks . fetchingDevice ) ;
self . bridge . socket . send ( "ask-device" ) ;
var json = await self . bridge . waitBackendMessage ( ) ;
if ( json . hasOwnProperty ( "error" ) ) {
showError ( json ) ;
return false ;
}
show ( VaultFeedbacks . deviceFound . replace ( "{{0}}" , json . model ) ) ;
return true ;
} ;
2023-10-27 11:54:15 +09:00
2019-11-11 14:22:04 +09:00
this . askForXPubs = async function ( ) {
if ( ! await self . ensureConnectedToBackend ( ) )
return false ;
2019-12-04 15:54:08 +09:00
self . bridge . socket . send ( "ask-xpub" ) ;
2019-12-04 16:34:25 +09:00
var json = await self . bridge . waitBackendMessage ( ) ;
if ( json . hasOwnProperty ( "error" ) ) {
if ( await needRetry ( json ) )
return await self . askForXPubs ( ) ;
return false ;
}
2022-12-14 04:06:54 +01:00
try {
var selectedXPubs = await self . getXpubSettings ( ) ;
self . bridge . socket . send ( JSON . stringify ( selectedXPubs ) ) ;
show ( VaultFeedbacks . fetchingXpubs ) ;
json = await self . bridge . waitBackendMessage ( ) ;
if ( json . hasOwnProperty ( "error" ) ) {
if ( await needRetry ( json ) )
return await self . askForXPubs ( ) ;
return false ;
}
show ( VaultFeedbacks . fetchedXpubs ) ;
self . xpub = json ;
return true ;
} catch ( err ) {
showError ( { error : true , message : err } ) ;
2019-11-11 14:22:04 +09:00
}
} ;
2019-12-04 15:54:08 +09:00
/ * *
* @ returns { Promise < { addressType : string , accountNumber : number } > }
* /
this . getXpubSettings = function ( ) {
show ( VaultFeedbacks . askXpubs ) ;
$ ( "#vault-xpub" ) . css ( "display" , "block" ) ;
$ ( "#vault-confirm" ) . css ( "display" , "block" ) ;
$ ( "#vault-confirm" ) . text ( "Confirm" ) ;
return new Promise ( function ( resolve , reject ) {
$ ( "#vault-confirm" ) . click ( async function ( e ) {
e . preventDefault ( ) ;
$ ( "#vault-xpub" ) . css ( "display" , "none" ) ;
$ ( "#vault-confirm" ) . css ( "display" , "none" ) ;
$ ( this ) . unbind ( ) ;
2022-12-14 04:06:54 +01:00
const addressType = $ ( "select[name=\"addressType\"]" ) . val ( ) ;
const accountNumber = parseInt ( $ ( "input[name=\"accountNumber\"]" ) . val ( ) ) ;
if ( addressType && ! isNaN ( accountNumber ) ) {
resolve ( { addressType , accountNumber } ) ;
} else {
reject ( "Provide an address type and account number" )
}
2019-12-04 17:16:37 +09:00
} ) ;
} ) ;
} ;
2019-11-11 14:22:04 +09:00
/ * *
* @ returns { Promise < string > }
* /
this . getUserEnterPin = function ( ) {
show ( VaultFeedbacks . needPin ) ;
$ ( "#pin-input" ) . css ( "display" , "block" ) ;
$ ( "#vault-confirm" ) . css ( "display" , "block" ) ;
2019-11-21 16:38:43 +09:00
$ ( "#vault-confirm" ) . text ( "Confirm the pin code" ) ;
2019-11-11 14:22:04 +09:00
return new Promise ( function ( resolve , reject ) {
var pinCode = "" ;
2019-12-04 15:54:08 +09:00
$ ( "#vault-confirm" ) . click ( async function ( e ) {
e . preventDefault ( ) ;
2019-11-11 14:22:04 +09:00
$ ( "#pin-input" ) . css ( "display" , "none" ) ;
$ ( "#vault-confirm" ) . css ( "display" , "none" ) ;
$ ( this ) . unbind ( ) ;
$ ( ".pin-button" ) . unbind ( ) ;
$ ( "#pin-display-delete" ) . unbind ( ) ;
resolve ( pinCode ) ;
} ) ;
$ ( "#pin-display-delete" ) . click ( function ( ) {
pinCode = "" ;
$ ( "#pin-display" ) . val ( "" ) ;
} ) ;
$ ( ".pin-button" ) . click ( function ( ) {
var id = $ ( this ) . attr ( 'id' ) . replace ( "pin-" , "" ) ;
pinCode = pinCode + id ;
$ ( "#pin-display" ) . val ( $ ( "#pin-display" ) . val ( ) + "*" ) ;
} ) ;
} ) ;
} ;
/ * *
* @ returns { Promise < string > }
* /
this . getUserPassphrase = function ( ) {
show ( VaultFeedbacks . needPassphrase ) ;
$ ( "#passphrase-input" ) . css ( "display" , "block" ) ;
$ ( "#vault-confirm" ) . css ( "display" , "block" ) ;
2019-11-21 16:38:43 +09:00
$ ( "#vault-confirm" ) . text ( "Confirm the passphrase" ) ;
2019-11-11 14:22:04 +09:00
return new Promise ( function ( resolve , reject ) {
2019-12-04 16:34:25 +09:00
$ ( "#vault-confirm" ) . click ( async function ( e ) {
e . preventDefault ( ) ;
2019-11-11 14:22:04 +09:00
var passphrase = $ ( "#Password" ) . val ( ) ;
if ( passphrase !== $ ( "#PasswordConfirmation" ) . val ( ) ) {
2019-11-21 16:38:43 +09:00
show ( VaultFeedbacks . invalidPasswordConfirmation ) ;
2019-11-11 14:22:04 +09:00
return ;
}
$ ( "#passphrase-input" ) . css ( "display" , "none" ) ;
$ ( "#vault-confirm" ) . css ( "display" , "none" ) ;
$ ( this ) . unbind ( ) ;
resolve ( passphrase ) ;
} ) ;
} ) ;
} ;
2019-11-22 19:12:30 +09:00
this . askForPassphrase = async function ( ) {
if ( ! await self . ensureConnectedToBackend ( ) )
return false ;
var passphrase = await self . getUserPassphrase ( ) ;
self . bridge . socket . send ( "set-passphrase" ) ;
self . bridge . socket . send ( passphrase ) ;
return true ;
}
2019-11-11 14:22:04 +09:00
/ * *
* @ returns { Promise }
* /
this . askForPin = async function ( ) {
if ( ! await self . ensureConnectedToBackend ( ) )
return false ;
2019-12-04 17:16:37 +09:00
2019-11-11 14:22:04 +09:00
self . bridge . socket . send ( "ask-pin" ) ;
var json = await self . bridge . waitBackendMessage ( ) ;
if ( json . hasOwnProperty ( "error" ) ) {
2019-12-03 13:53:50 +09:00
if ( json . error == "device-already-unlocked" )
return true ;
2019-11-11 14:22:04 +09:00
if ( await needRetry ( json ) )
return await self . askForPin ( ) ;
return false ;
}
var pinCode = await self . getUserEnterPin ( ) ;
2019-11-22 19:12:30 +09:00
self . bridge . socket . send ( pinCode ) ;
2019-11-11 14:22:04 +09:00
var json = await self . bridge . waitBackendMessage ( ) ;
if ( json . hasOwnProperty ( "error" ) ) {
showError ( json ) ;
return false ;
}
return true ;
}
/ * *
* @ returns { Promise < Boolean > }
* /
this . askSignPSBT = async function ( args ) {
if ( ! await self . ensureConnectedToBackend ( ) )
return false ;
show ( VaultFeedbacks . signingTransaction ) ;
self . bridge . socket . send ( "ask-sign" ) ;
var json = await self . bridge . waitBackendMessage ( ) ;
if ( json . hasOwnProperty ( "error" ) ) {
if ( await needRetry ( json ) )
return await self . askSignPSBT ( args ) ;
return false ;
}
self . bridge . socket . send ( JSON . stringify ( args ) ) ;
json = await self . bridge . waitBackendMessage ( ) ;
if ( json . hasOwnProperty ( "error" ) ) {
if ( await needRetry ( json ) )
return await self . askSignPSBT ( args ) ;
return false ;
}
self . psbt = json . psbt ;
return true ;
} ;
2019-12-04 16:16:41 +09:00
this . closeBridge = function ( ) {
if ( self . bridge ) {
self . bridge . close ( ) ;
}
} ;
2019-11-11 14:22:04 +09:00
}
return {
VaultFeedback : VaultFeedback ,
VaultBridgeUI : VaultBridgeUI
} ;
} ) ( ) ;