" + Common.htmlEncode(slate.getSenderAddress()) + " " + Language.createTranslatableContainer("", Language.getDefaultTranslation('Copy'), [], "copy", true) + " " + Message.createLineBreak() + Message.createLineBreak() + "" + Message.createText(Language.getDefaultTranslation('You can guarantee that this payment is coming from the intended sender by having the sender confirm that this payment proof address is their payment proof address.')) + " " : (Message.createText(Language.getDefaultTranslation('The transaction doesn\'t have a payment proof.')) + Message.createLineBreak() + "" + Message.createText(Language.getDefaultTranslation('You can\'t guarantee that this payment is coming from the intended sender since the transaction doesn\'t have a payment proof.')) + " ")), preventMessages === true, function() {
+
+ // Check if cancel didn't occur and wallet exists
+ if((cancelOccurred === Common.NO_CANCEL_OCCURRED || cancelOccurred() === false) && self.wallets.walletExists(wallet.getKeyPath()) === true) {
+
+ // Check if preventing messages
+ if(preventMessages === true) {
+
+ // Hide loading
+ self.hideLoading();
+ }
+
+ // Otherwise
+ else {
+
+ // Save focus and blur
+ self.focus.save(true);
+
+ // Check if unlock display is shown
+ if(self.isUnlockDisplayShown() === true)
+
+ // Disable tabbing to everything in unlock display and disable everything in unlock display
+ self.unlockDisplay.find("*").disableTab().disable();
+
+ // Otherwise check if unlocked display is shown
+ else if(self.isUnlockedDisplayShown() === true)
+
+ // Disable unlocked
+ self.unlocked.disable();
+
+ // Keep device awake and catch errors
+ self.wakeLock.preventLock().catch(function(error) {
+
+ });
+
+ // Set sleep disabled
+ sleepDisabled = true;
+ }
+
+ // Cancel if external canceled
+ var cancelIfExternalCanceled = function() {
+
+ // Check if external cancel check if allowed
+ if(externalCancelCheckAllowed === true) {
+
+ // Check if cancel occurred
+ if(cancelOccurred !== Common.NO_CANCEL_OCCURRED && cancelOccurred() === true) {
+
+ // Disable message
+ self.message.disable();
+
+ // Set prevent cancel on hide
+ preventCancelOnHide = true;
+
+ // Check if preventing messages
+ if(preventMessages === true) {
+
+ // Check if can't be canceled
+ if(cancelOccurred === Common.NO_CANCEL_OCCURRED) {
+
+ // Prevent showing messages
+ self.message.prevent();
+ }
+
+ // Check if preventing messages and it can be canceled
+ if(preventMessages === true && cancelOccurred !== Common.NO_CANCEL_OCCURRED) {
+
+ // Reject canceled error
+ reject(Common.CANCELED_ERROR);
+ }
+
+ // Otherwise
+ else {
+
+ // Hide message
+ self.message.hide().then(function() {
+
+ // Reject canceled error
+ reject(Common.CANCELED_ERROR);
+ });
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Check if sleep is disabled
+ if(sleepDisabled === true) {
+
+ // Allow device to sleep and catch errors
+ self.wakeLock.allowLock().catch(function(error) {
+
+ // Finally
+ }).finally(function() {
+
+ // Check if unlock display is shown
+ if(self.isUnlockDisplayShown() === true)
+
+ // Enable tabbing to everything in unlock display and enable everything in unlock display
+ self.unlockDisplay.find("*").enableTab().enable();
+
+ // Otherwise check if unlocked display is shown
+ else if(self.isUnlockedDisplayShown() === true)
+
+ // Enable unlocked
+ self.unlocked.enable();
+
+ // Restore focus and don't blur
+ self.focus.restore(false);
+
+ // Check if preventing messages and it can be canceled
+ if(preventMessages === true && cancelOccurred !== Common.NO_CANCEL_OCCURRED) {
+
+ // Reject canceled error
+ reject(Common.CANCELED_ERROR);
+ }
+
+ // Otherwise
+ else {
+
+ // Hide message
+ self.message.hide().then(function() {
+
+ // Reject canceled error
+ reject(Common.CANCELED_ERROR);
+ });
+ }
+ });
+ }
+
+ // Otherwise
+ else {
+
+ // Check if unlock display is shown
+ if(self.isUnlockDisplayShown() === true)
+
+ // Enable tabbing to everything in unlock display and enable everything in unlock display
+ self.unlockDisplay.find("*").enableTab().enable();
+
+ // Otherwise check if unlocked display is shown
+ else if(self.isUnlockedDisplayShown() === true)
+
+ // Enable unlocked
+ self.unlocked.enable();
+
+ // Restore focus and don't blur
+ self.focus.restore(false);
+
+ // Check if preventing messages and it can be canceled
+ if(preventMessages === true && cancelOccurred !== Common.NO_CANCEL_OCCURRED) {
+
+ // Reject canceled error
+ reject(Common.CANCELED_ERROR);
+ }
+
+ // Otherwise
+ else {
+
+ // Hide message
+ self.message.hide().then(function() {
+
+ // Reject canceled error
+ reject(Common.CANCELED_ERROR);
+ });
+ }
+ }
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Set timeout
+ setTimeout(function() {
+
+ // Cancel if external canceled
+ cancelIfExternalCanceled();
+
+ }, Application.CANCELED_CHECK_INTERVAL_MILLISECONDS);
+ }
+ }
+ };
+
+ // Cancel if external canceled
+ cancelIfExternalCanceled();
+ }
+
+ // Otherwise
+ else {
+
+ // Return false
+ return false;
+ }
+
+ }, Language.getDefaultTranslation('No'), Language.getDefaultTranslation('Yes'), preventMessages === true, (allowUnlock === true) ? Message.VISIBLE_STATE_UNLOCK | Message.VISIBLE_STATE_UNLOCKED : Message.VISIBLE_STATE_UNLOCKED).then(function(messageResult) {
+
+ // Clear external cancel check allowed
+ externalCancelCheckAllowed = false;
+
+ // Check if message was displayed
+ if(messageResult !== Message.NOT_DISPLAYED_RESULT) {
+
+ // Check if receiving payment
+ if(messageResult === Message.SECOND_BUTTON_CLICKED_RESULT) {
+
+ // Check if preventing messages
+ if(preventMessages === true) {
+
+ // Check if can't be canceled
+ if(cancelOccurred === Common.NO_CANCEL_OCCURRED) {
+
+ // Show loading
+ self.showLoading();
+
+ // Prevent showing messages
+ self.message.prevent();
+ }
+
+ // Check if preventing messages and it can be canceled
+ if(preventMessages === true && cancelOccurred !== Common.NO_CANCEL_OCCURRED) {
+
+ // Replace message
+ self.message.replace().then(function() {
+
+ // Resolve
+ resolve();
+ });
+ }
+
+ // Otherwise
+ else {
+
+ // Hide message
+ self.message.hide().then(function() {
+
+ // Resolve
+ resolve();
+ });
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Check if sleep is disabled
+ if(sleepDisabled === true) {
+
+ // Allow device to sleep and catch errors
+ self.wakeLock.allowLock().catch(function(error) {
+
+ // Finally
+ }).finally(function() {
+
+ // Check if unlock display is shown
+ if(self.isUnlockDisplayShown() === true)
+
+ // Enable tabbing to everything in unlock display and enable everything in unlock display
+ self.unlockDisplay.find("*").enableTab().enable();
+
+ // Otherwise check if unlocked display is shown
+ else if(self.isUnlockedDisplayShown() === true)
+
+ // Enable unlocked
+ self.unlocked.enable();
+
+ // Restore focus and don't blur
+ self.focus.restore(false);
+
+ // Check if preventing messages and it can be canceled
+ if(preventMessages === true && cancelOccurred !== Common.NO_CANCEL_OCCURRED) {
+
+ // Replace message
+ self.message.replace().then(function() {
+
+ // Resolve
+ resolve();
+ });
+ }
+
+ // Otherwise
+ else {
+
+ // Hide message
+ self.message.hide().then(function() {
+
+ // Resolve
+ resolve();
+ });
+ }
+ });
+ }
+
+ // Otherwise
+ else {
+
+ // Check if unlock display is shown
+ if(self.isUnlockDisplayShown() === true)
+
+ // Enable tabbing to everything in unlock display and enable everything in unlock display
+ self.unlockDisplay.find("*").enableTab().enable();
+
+ // Otherwise check if unlocked display is shown
+ else if(self.isUnlockedDisplayShown() === true)
+
+ // Enable unlocked
+ self.unlocked.enable();
+
+ // Restore focus and don't blur
+ self.focus.restore(false);
+
+ // Check if preventing messages and it can be canceled
+ if(preventMessages === true && cancelOccurred !== Common.NO_CANCEL_OCCURRED) {
+
+ // Replace message
+ self.message.replace().then(function() {
+
+ // Resolve
+ resolve();
+ });
+ }
+
+ // Otherwise
+ else {
+
+ // Hide message
+ self.message.hide().then(function() {
+
+ // Resolve
+ resolve();
+ });
+ }
+ }
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Check if preventing messages
+ if(preventMessages === true) {
+
+ // Check if can't be canceled
+ if(cancelOccurred === Common.NO_CANCEL_OCCURRED) {
+
+ // Prevent showing messages
+ self.message.prevent();
+ }
+
+ // Check if preventing messages and it can be canceled
+ if(preventMessages === true && cancelOccurred !== Common.NO_CANCEL_OCCURRED) {
+
+ // Reject canceled error
+ reject(Common.CANCELED_ERROR);
+ }
+
+ // Otherwise
+ else {
+
+ // Hide message
+ self.message.hide().then(function() {
+
+ // Reject canceled error
+ reject(Common.CANCELED_ERROR);
+ });
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Check if sleep is disabled
+ if(sleepDisabled === true) {
+
+ // Allow device to sleep and catch errors
+ self.wakeLock.allowLock().catch(function(error) {
+
+ // Finally
+ }).finally(function() {
+
+ // Check if unlock display is shown
+ if(self.isUnlockDisplayShown() === true)
+
+ // Enable tabbing to everything in unlock display and enable everything in unlock display
+ self.unlockDisplay.find("*").enableTab().enable();
+
+ // Otherwise check if unlocked display is shown
+ else if(self.isUnlockedDisplayShown() === true)
+
+ // Enable unlocked
+ self.unlocked.enable();
+
+ // Restore focus and don't blur
+ self.focus.restore(false);
+
+ // Check if preventing messages and it can be canceled
+ if(preventMessages === true && cancelOccurred !== Common.NO_CANCEL_OCCURRED) {
+
+ // Reject canceled error
+ reject(Common.CANCELED_ERROR);
+ }
+
+ // Otherwise
+ else {
+
+ // Hide message
+ self.message.hide().then(function() {
+
+ // Reject canceled error
+ reject(Common.CANCELED_ERROR);
+ });
+ }
+ });
+ }
+
+ // Otherwise
+ else {
+
+ // Check if unlock display is shown
+ if(self.isUnlockDisplayShown() === true)
+
+ // Enable tabbing to everything in unlock display and enable everything in unlock display
+ self.unlockDisplay.find("*").enableTab().enable();
+
+ // Otherwise check if unlocked display is shown
+ else if(self.isUnlockedDisplayShown() === true)
+
+ // Enable unlocked
+ self.unlocked.enable();
+
+ // Restore focus and don't blur
+ self.focus.restore(false);
+
+ // Check if preventing messages and it can be canceled
+ if(preventMessages === true && cancelOccurred !== Common.NO_CANCEL_OCCURRED) {
+
+ // Reject canceled error
+ reject(Common.CANCELED_ERROR);
+ }
+
+ // Otherwise
+ else {
+
+ // Hide message
+ self.message.hide().then(function() {
+
+ // Reject canceled error
+ reject(Common.CANCELED_ERROR);
+ });
+ }
+ }
+ }
+ }
+ }
+
+ // Otherwise check if not preventing cancel on hide
+ else if(preventCancelOnHide === false) {
+
+ // Check if sleep is disabled
+ if(sleepDisabled === true) {
+
+ // Allow device to sleep and catch errors
+ self.wakeLock.allowLock().catch(function(error) {
+
+ // Finally
+ }).finally(function() {
+
+ // Reject canceled error
+ reject(Common.CANCELED_ERROR);
+ });
+ }
+
+ // Otherwise
+ else {
+
+ // Reject canceled error
+ reject(Common.CANCELED_ERROR);
+ }
+ }
+ });
+ }
+
+ // Otherwise
+ else {
+
+ // Check if a high priority wallets exclusive transactions lock is waiting
+ if(self.transactions.isHighPriorityWalletsExclusiveTransactionsLockWaiting(wallet.getKeyPath()) === true) {
+
+ // Reject canceled error
+ reject(Common.CANCELED_ERROR);
+ }
+
+ // Otherwise
+ else {
+
+ // Set timeout
+ setTimeout(function() {
+
+ // Prompt to approve
+ promptToApprove();
+
+ }, Application.CHECK_HARDWARE_WALLET_PRIORITY_INTERVAL_MILLISECONDS);
+ }
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Reject canceled error
+ reject(Common.CANCELED_ERROR);
+ }
+ };
+
+ // Prompt to approve
+ promptToApprove();
+ });
+ }
+
+ // Show hardware wallet connect message
+ showHardwareWalletConnectMessage(wallet, text, textArguments = [], allowUnlock = false, preventMessages = false, cancelOccurred = Common.NO_CANCEL_OCCURRED, recursivelyShown = false) {
+
+ // set self
+ var self = this;
+
+ // Return promise
+ return new Promise(function(resolve, reject) {
+
+ // Prompt to connect
+ var promptToConnect = function() {
+
+ // Check if cancel didn't occur or recursively shown
+ if(cancelOccurred === Common.NO_CANCEL_OCCURRED || cancelOccurred() === false || recursivelyShown === true) {
+
+ // Check if preventing messages, recursively shown, or messages are allowed and no message is shown
+ if(preventMessages === true || recursivelyShown === true || (self.message.getAllowed() === true && self.message.isShown() === false)) {
+
+ // Initialize prevent cancel on hide
+ var preventCancelOnHide = false;
+
+ // Initialize external cancel check allowed
+ var externalCancelCheckAllowed = true;
+
+ // Return showing message and do it immediately if preventing messages or recursively shown
+ return self.message.show(Language.getDefaultTranslation('Hardware Wallet Disconnected'), Message.createPendingResult() + Message.createLineBreak() + Message.createText(text, textArguments), preventMessages === true || recursivelyShown === true, function() {
+
+ // Check if cancel didn't occur or recursively shown and wallet exists
+ if((cancelOccurred === Common.NO_CANCEL_OCCURRED || cancelOccurred() === false || recursivelyShown === true) && self.wallets.walletExists(wallet.getKeyPath()) === true) {
+
+ // Check if wallet's hardware wallet is connected
+ if(wallet.isHardwareConnected() === true) {
+
+ // Set prevent cancel on hide
+ preventCancelOnHide = true;
+
+ // Check if preventing messages
+ if(preventMessages === true) {
+
+ // Check if can't be canceled
+ if(cancelOccurred === Common.NO_CANCEL_OCCURRED) {
+
+ // Show loading
+ self.showLoading();
+
+ // Prevent showing messages
+ self.message.prevent();
+ }
+ }
+
+ // Resolve
+ resolve();
+
+ // Return false
+ return false;
+ }
+
+ // Otherwise
+ else {
+
+ // Check if preventing messages
+ if(preventMessages === true) {
+
+ // Hide loading
+ self.hideLoading();
+ }
+
+ // Otherwise
+ else {
+
+ // Save focus and blur
+ self.focus.save(true);
+
+ // Check if unlock display is shown
+ if(self.isUnlockDisplayShown() === true)
+
+ // Disable tabbing to everything in unlock display and disable everything in unlock display
+ self.unlockDisplay.find("*").disableTab().disable();
+
+ // Otherwise check if unlocked display is shown
+ else if(self.isUnlockedDisplayShown() === true)
+
+ // Disable unlocked
+ self.unlocked.disable();
+ }
+
+ // Document wallet connect application wallet key path event
+ $(document).on(Wallet.CONNECT_EVENT + ".application" + wallet.getKeyPath().toFixed(), function(event, walletKeyPath) {
+
+ // Check if wallet's hardware wallet is connected
+ if(walletKeyPath === wallet.getKeyPath()) {
+
+ // Turn off document wallet connect application wallet key path event
+ $(document).off(Wallet.CONNECT_EVENT + ".application" + wallet.getKeyPath().toFixed());
+
+ // Clear external cancel check allowed
+ externalCancelCheckAllowed = false;
+
+ // Disable message
+ self.message.disable();
+
+ // Set prevent cancel on hide
+ preventCancelOnHide = true;
+
+ // Check if preventing messages
+ if(preventMessages === true) {
+
+ // Check if can't be canceled
+ if(cancelOccurred === Common.NO_CANCEL_OCCURRED) {
+
+ // Show loading
+ self.showLoading();
+
+ // Prevent showing messages
+ self.message.prevent();
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Check if unlock display is shown
+ if(self.isUnlockDisplayShown() === true)
+
+ // Enable tabbing to everything in unlock display and enable everything in unlock display
+ self.unlockDisplay.find("*").enableTab().enable();
+
+ // Otherwise check if unlocked display is shown
+ else if(self.isUnlockedDisplayShown() === true)
+
+ // Enable unlocked
+ self.unlocked.enable();
+
+ // Restore focus and don't blur
+ self.focus.restore(false);
+ }
+
+ // Check if preventing messages and it can be canceled
+ if(preventMessages === true && cancelOccurred !== Common.NO_CANCEL_OCCURRED) {
+
+ // Return replacing message
+ return self.message.replace().then(function() {
+
+ // Resolve
+ resolve();
+ });
+ }
+
+ // Otherwise
+ else {
+
+ // Return hiding message
+ return self.message.hide().then(function() {
+
+ // Resolve
+ resolve();
+ });
+ }
+ }
+ });
+
+ // Cancel if external canceled
+ var cancelIfExternalCanceled = function() {
+
+ // Check if external cancel check if allowed
+ if(externalCancelCheckAllowed === true) {
+
+ // Check if cancel occurred and not recursively shown
+ if(cancelOccurred !== Common.NO_CANCEL_OCCURRED && cancelOccurred() === true && recursivelyShown === false) {
+
+ // Turn off document wallet connect application wallet key path event
+ $(document).off(Wallet.CONNECT_EVENT + ".application" + wallet.getKeyPath().toFixed());
+
+ // Disable message
+ self.message.disable();
+
+ // Set prevent cancel on hide
+ preventCancelOnHide = true;
+
+ // Check if preventing messages
+ if(preventMessages === true) {
+
+ // Check if can't be canceled
+ if(cancelOccurred === Common.NO_CANCEL_OCCURRED) {
+
+ // Prevent showing messages
+ self.message.prevent();
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Check if unlock display is shown
+ if(self.isUnlockDisplayShown() === true)
+
+ // Enable tabbing to everything in unlock display and enable everything in unlock display
+ self.unlockDisplay.find("*").enableTab().enable();
+
+ // Otherwise check if unlocked display is shown
+ else if(self.isUnlockedDisplayShown() === true)
+
+ // Enable unlocked
+ self.unlocked.enable();
+
+ // Restore focus and don't blur
+ self.focus.restore(false);
+ }
+
+ // Check if preventing messages and it can be canceled
+ if(preventMessages === true && cancelOccurred !== Common.NO_CANCEL_OCCURRED) {
+
+ // Reject canceled error
+ reject(Common.CANCELED_ERROR);
+ }
+
+ // Otherwise
+ else {
+
+ // Return hiding message
+ return self.message.hide().then(function() {
+
+ // Reject canceled error
+ reject(Common.CANCELED_ERROR);
+ });
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Set timeout
+ setTimeout(function() {
+
+ // Cancel if external canceled
+ cancelIfExternalCanceled();
+
+ }, Application.CANCELED_CHECK_INTERVAL_MILLISECONDS);
+ }
+ }
+ };
+
+ // Cancel if external canceled
+ cancelIfExternalCanceled();
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Return false
+ return false;
+ }
+
+ }, Language.getDefaultTranslation('Cancel'), Language.getDefaultTranslation('Connect'), preventMessages === true || recursivelyShown === true, (allowUnlock === true) ? Message.VISIBLE_STATE_UNLOCK | Message.VISIBLE_STATE_UNLOCKED : Message.VISIBLE_STATE_UNLOCKED).then(function(messageResult) {
+
+ // Turn off document wallet connect application wallet key path event
+ $(document).off(Wallet.CONNECT_EVENT + ".application" + wallet.getKeyPath().toFixed());
+
+ // Clear external cancel check allowed
+ externalCancelCheckAllowed = false;
+
+ // Check if message was displayed
+ if(messageResult !== Message.NOT_DISPLAYED_RESULT) {
+
+ // Check if connect
+ if(messageResult === Message.SECOND_BUTTON_CLICKED_RESULT) {
+
+ // Return obtain wallets exclusive hardware lock
+ return self.wallets.obtainExclusiveHardwareLock().then(function() {
+
+ // Check if wallet still isn't connected to a hardware wallet
+ if(wallet.isHardwareConnected() === false) {
+
+ // Show hardware wallet error
+ var showHardwareWalletError = function(message) {
+
+ // Disable message
+ self.message.disable();
+
+ // Return promise
+ return new Promise(function(resolve, reject) {
+
+ // Return showing message immediately and allow showing messages
+ return self.message.show(Language.getDefaultTranslation('Hardware Wallet Error'), message, true, function() {
+
+ // Hide loading
+ self.hideLoading();
+
+ }, Language.getDefaultTranslation('OK'), Message.NO_BUTTON, true, (allowUnlock === true) ? Message.VISIBLE_STATE_UNLOCK | Message.VISIBLE_STATE_UNLOCKED : Message.VISIBLE_STATE_UNLOCKED).then(function(messageResult) {
+
+ // Check if message was displayed
+ if(messageResult !== Message.NOT_DISPLAYED_RESULT) {
+
+ // Release wallets exclusive hardware lock
+ self.wallets.releaseExclusiveHardwareLock();
+
+ // Return showing hardware connect message immediately
+ return self.showHardwareWalletConnectMessage(wallet, text, textArguments, allowUnlock, preventMessages, cancelOccurred, true).then(function() {
+
+ // Resolve
+ resolve();
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Reject error
+ reject(error);
+ });
+ }
+
+ // Otherwise
+ else {
+
+ // Release wallets exclusive hardware lock
+ self.wallets.releaseExclusiveHardwareLock();
+
+ // Reject canceled error
+ reject(Common.CANCELED_ERROR);
+ }
+ });
+ });
+ };
+
+ // Check if hardware wallets are supported
+ if(HardwareWallet.isSupported() === true) {
+
+ // Get if automatic lock state
+ var automaticLockState = self.automaticLock.getAllowed();
+
+ // Prevent inactive automatic lock
+ self.automaticLock.prevent();
+
+ // Initialize canceled
+ var canceled = false;
+
+ // Return showing message immediately and allow showing messages
+ return self.message.show(Language.getDefaultTranslation('Hardware Wallet Disconnected'), Message.createPendingResult() + Message.createLineBreak() + Message.createText(Language.getDefaultTranslation('Connecting to a hardware wallet.')), true, function() {
+
+ // Message show application hardware wallet connect event
+ $(self.message).one(Message.SHOW_EVENT + ".applicationHardwareWalletConnect", function() {
+
+ // Create hardware wallet
+ var hardwareWallet = new HardwareWallet(self);
+
+ // Message before replace application hardware wallet connect event
+ $(self.message).on(Message.BEFORE_REPLACE_EVENT + ".applicationHardwareWalletConnect", function(event, messageType, messageData) {
+
+ // Check if not canceled
+ if(canceled === false) {
+
+ // Check message type
+ switch(messageType) {
+
+ // Application hardware wallet unlock message
+ case Application.HARDWARE_WALLET_UNLOCK_MESSAGE:
+
+ // Cancel replacing message
+ self.message.cancelReplace();
+
+ // Return false to stop other replace message
+ return false;
+
+ // Application hardware wallet disconnect message
+ case Application.HARDWARE_WALLET_DISCONNECT_MESSAGE:
+
+ // Set canceled
+ canceled = true;
+
+ // Turn off message before replace application hardware wallet connect event
+ $(self.message).off(Message.BEFORE_REPLACE_EVENT + ".applicationHardwareWalletConnect");
+
+ // Restore automatic lock state
+ self.automaticLock.allow(automaticLockState);
+
+ // Check if automatic lock is locking and message doesn't allow unlock
+ if(self.automaticLock.isLocking() === true && allowUnlock === false) {
+
+ // Release wallets exclusive hardware lock
+ self.wallets.releaseExclusiveHardwareLock();
+
+ // Reject canceled error
+ reject(Common.CANCELED_ERROR);
+ }
+
+ // Otherwise
+ else {
+
+ // Show hardware wallet error
+ showHardwareWalletError(Message.createText(Language.getDefaultTranslation('That hardware wallet was disconnected.'))).then(function() {
+
+ // Resolve
+ resolve();
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Reject error
+ reject(error);
+ });
+ }
+
+ // Return false to stop other replace message
+ return false;
+ }
+ }
+ });
+
+ // Return connecting to any hardware wallet descriptor
+ return hardwareWallet.connect(HardwareWallet.ANY_HARDWARE_WALLET_DESCRIPTOR, false, Language.getDefaultTranslation('Unlock that hardware wallet to continue connecting to it.'), [], allowUnlock, true, cancelOccurred).then(function() {
+
+ // Check if not canceled
+ if(canceled === false) {
+
+ // Turn off message before replace application hardware wallet connect event
+ $(self.message).off(Message.BEFORE_REPLACE_EVENT + ".applicationHardwareWalletConnect");
+
+ // Restore automatic lock state
+ self.automaticLock.allow(automaticLockState);
+
+ // Check if automatic lock is locking and message doesn't allow unlock
+ if(self.automaticLock.isLocking() === true && allowUnlock === false) {
+
+ // Close the hardware wallet
+ hardwareWallet.close();
+
+ // Release wallets exclusive hardware lock
+ self.wallets.releaseExclusiveHardwareLock();
+
+ // Reject canceled error
+ reject(Common.CANCELED_ERROR);
+
+ // Return
+ return;
+ }
+
+ // Disable message
+ self.message.disable();
+
+ // Return connecting wallet to the applicable hardware wallet
+ return wallet.connectToApplicableHardware([hardwareWallet]).then(function() {
+
+ // Check if hardware wallet isn't in use
+ if(hardwareWallet.getInUse() === false) {
+
+ // Close hardware wallet
+ hardwareWallet.close();
+
+ // Return showing hardware wallet error
+ return showHardwareWalletError(Message.createText((wallet.getName() === Wallet.NO_NAME) ? Language.getDefaultTranslation('That hardware wallet isn\'t for Wallet %1$s.') : Language.getDefaultTranslation('That hardware wallet isn\'t for %1$y.'), [(wallet.getName() === Wallet.NO_NAME) ? wallet.getKeyPath().toFixed() : wallet.getName()])).then(function() {
+
+ // Resolve
+ resolve();
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Reject error
+ reject(error);
+ });
+ }
+
+ // Otherwise
+ else {
+
+
+ // Release wallets exclusive hardware lock
+ self.wallets.releaseExclusiveHardwareLock();
+
+ // Check if preventing messages
+ if(preventMessages === true) {
+
+ // Check if can't be canceled
+ if(cancelOccurred === Common.NO_CANCEL_OCCURRED) {
+
+ // Show loading
+ self.showLoading();
+
+ // Prevent showing messages
+ self.message.prevent();
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Check if unlock display is shown
+ if(self.isUnlockDisplayShown() === true)
+
+ // Enable tabbing to everything in unlock display and enable everything in unlock display
+ self.unlockDisplay.find("*").enableTab().enable();
+
+ // Otherwise check if unlocked display is shown
+ else if(self.isUnlockedDisplayShown() === true)
+
+ // Enable unlocked
+ self.unlocked.enable();
+
+ // Restore focus and don't blur
+ self.focus.restore(false);
+ }
+
+ // Check if preventing messages and it can be canceled
+ if(preventMessages === true && cancelOccurred !== Common.NO_CANCEL_OCCURRED) {
+
+ // Return replacing message
+ return self.message.replace().then(function() {
+
+ // Resolve
+ resolve();
+ });
+ }
+
+ // Otherwise
+ else {
+
+ // Return hiding message
+ return self.message.hide().then(function() {
+
+ // Resolve
+ resolve();
+ });
+ }
+ }
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Close the hardware wallet
+ hardwareWallet.close();
+
+ // Return showing hardware wallet error
+ return showHardwareWalletError(Message.createText(Language.getDefaultTranslation('Connecting to that hardware wallet failed.'))).then(function() {
+
+ // Resolve
+ resolve();
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Reject error
+ reject(error);
+ });
+ });
+ }
+
+ // Otherwise
+ else {
+
+ // Close the hardware wallet
+ hardwareWallet.close();
+ }
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Check if not canceled
+ if(canceled === false) {
+
+ // Turn off message before replace application hardware wallet connect event
+ $(self.message).off(Message.BEFORE_REPLACE_EVENT + ".applicationHardwareWalletConnect");
+
+ // Restore automatic lock state
+ self.automaticLock.allow(automaticLockState);
+
+ // Check if automatic lock is locking and message doesn't allow unlock
+ if(self.automaticLock.isLocking() === true && allowUnlock === false) {
+
+ // Release wallets exclusive hardware lock
+ self.wallets.releaseExclusiveHardwareLock();
+
+ // Reject canceled error
+ reject(Common.CANCELED_ERROR);
+
+ // Return
+ return;
+ }
+
+ // Check if error is canceled
+ if(error === Common.CANCELED_ERROR) {
+
+ // Release wallets exclusive hardware lock
+ self.wallets.releaseExclusiveHardwareLock();
+
+ // Check if preventing messages
+ if(preventMessages === true) {
+
+ // Check if can't be canceled
+ if(cancelOccurred === Common.NO_CANCEL_OCCURRED) {
+
+ // Prevent showing messages
+ self.message.prevent();
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Check if unlock display is shown
+ if(self.isUnlockDisplayShown() === true)
+
+ // Enable tabbing to everything in unlock display and enable everything in unlock display
+ self.unlockDisplay.find("*").enableTab().enable();
+
+ // Otherwise check if unlocked display is shown
+ else if(self.isUnlockedDisplayShown() === true)
+
+ // Enable unlocked
+ self.unlocked.enable();
+
+ // Restore focus and don't blur
+ self.focus.restore(false);
+ }
+
+ // Check if preventing messages and it can be canceled
+ if(preventMessages === true && cancelOccurred !== Common.NO_CANCEL_OCCURRED) {
+
+ // Reject canceled error
+ reject(Common.CANCELED_ERROR);
+ }
+
+ // Otherwise
+ else {
+
+ // Return hiding message
+ return self.message.hide().then(function() {
+
+ // Reject canceled error
+ reject(Common.CANCELED_ERROR);
+ });
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Return showing hardware wallet error
+ return showHardwareWalletError(error).then(function() {
+
+ // Resolve
+ resolve();
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Reject error
+ reject(error);
+ });
+ }
+ }
+ });
+ });
+
+ }, Language.getDefaultTranslation('Back'), Message.NO_BUTTON, true, (allowUnlock === true) ? Message.VISIBLE_STATE_UNLOCK | Message.VISIBLE_STATE_UNLOCKED : Message.VISIBLE_STATE_UNLOCKED).then(function(messageResult) {
+
+ // Turn off message show application hardware wallet connect event
+ $(self.message).off(Message.SHOW_EVENT + ".applicationHardwareWalletConnect");
+
+ // Check if message was displayed
+ if(messageResult !== Message.NOT_DISPLAYED_RESULT) {
+
+ // Check if not canceled
+ if(canceled === false) {
+
+ // Set canceled
+ canceled = true;
+
+ // Turn off message before replace application hardware wallet connect event
+ $(self.message).off(Message.BEFORE_REPLACE_EVENT + ".applicationHardwareWalletConnect");
+
+ // Release wallets exclusive hardware lock
+ self.wallets.releaseExclusiveHardwareLock();
+
+ // Restore automatic lock state
+ self.automaticLock.allow(automaticLockState);
+
+ // Check if automatic lock is locking and message doesn't allow unlock
+ if(self.automaticLock.isLocking() === true && allowUnlock === false) {
+
+ // Reject canceled error
+ reject(Common.CANCELED_ERROR);
+
+ // Return
+ return;
+ }
+
+ // Return showing hardware connect message immediately
+ return self.showHardwareWalletConnectMessage(wallet, text, textArguments, allowUnlock, preventMessages, cancelOccurred, true).then(function() {
+
+ // Resolve
+ resolve();
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Reject error
+ reject(error);
+ });
+ }
+ }
+ });
+ }
+
+ // Otherwise
+ else {
+
+ // Return showing hardware wallet error
+ return showHardwareWalletError(Message.createText(Language.getDefaultTranslation('Your browser doesn\'t allow using USB or Bluetooth devices.')) + Message.createText(Language.getDefaultTranslation('(?<=.) ')) + Message.createText(Language.getDefaultTranslation('Update your browser to use this feature.'))).then(function() {
+
+ // Resolve
+ resolve();
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Reject error
+ reject(error);
+ });
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Release wallets exclusive hardware lock
+ self.wallets.releaseExclusiveHardwareLock();
+
+ // Check if preventing messages
+ if(preventMessages === true) {
+
+ // Check if can't be canceled
+ if(cancelOccurred === Common.NO_CANCEL_OCCURRED) {
+
+ // Show loading
+ self.showLoading();
+
+ // Prevent showing messages
+ self.message.prevent();
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Check if unlock display is shown
+ if(self.isUnlockDisplayShown() === true)
+
+ // Enable tabbing to everything in unlock display and enable everything in unlock display
+ self.unlockDisplay.find("*").enableTab().enable();
+
+ // Otherwise check if unlocked display is shown
+ else if(self.isUnlockedDisplayShown() === true)
+
+ // Enable unlocked
+ self.unlocked.enable();
+
+ // Restore focus and don't blur
+ self.focus.restore(false);
+ }
+
+ // Check if preventing messages and it can be canceled
+ if(preventMessages === true && cancelOccurred !== Common.NO_CANCEL_OCCURRED) {
+
+ // Return replacing message
+ return self.message.replace().then(function() {
+
+ // Resolve
+ resolve();
+ });
+ }
+
+ // Otherwise
+ else {
+
+ // Return hiding message
+ return self.message.hide().then(function() {
+
+ // Resolve
+ resolve();
+ });
+ }
+ }
+ });
+ }
+
+ // Otherwise
+ else {
+
+ // Check if preventing messages
+ if(preventMessages === true) {
+
+ // Check if can't be canceled
+ if(cancelOccurred === Common.NO_CANCEL_OCCURRED) {
+
+ // Prevent showing messages
+ self.message.prevent();
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Check if unlock display is shown
+ if(self.isUnlockDisplayShown() === true)
+
+ // Enable tabbing to everything in unlock display and enable everything in unlock display
+ self.unlockDisplay.find("*").enableTab().enable();
+
+ // Otherwise check if unlocked display is shown
+ else if(self.isUnlockedDisplayShown() === true)
+
+ // Enable unlocked
+ self.unlocked.enable();
+
+ // Restore focus and don't blur
+ self.focus.restore(false);
+ }
+
+ // Check if preventing messages and it can be canceled
+ if(preventMessages === true && cancelOccurred !== Common.NO_CANCEL_OCCURRED) {
+
+ // Reject canceled error
+ reject(Common.CANCELED_ERROR);
+ }
+
+ // Otherwise
+ else {
+
+ // Return hiding message
+ return self.message.hide().then(function() {
+
+ // Reject canceled error
+ reject(Common.CANCELED_ERROR);
+ });
+ }
+ }
+ }
+
+ // Otherwise check if not preventing cancel on hide
+ else if(preventCancelOnHide === false) {
+
+ // Reject canceled error
+ reject(Common.CANCELED_ERROR);
+ }
+ });
+ }
+
+ // Otherwise
+ else {
+
+ // Check if a high priority wallets exclusive transactions lock is waiting
+ if(self.transactions.isHighPriorityWalletsExclusiveTransactionsLockWaiting(wallet.getKeyPath()) === true) {
+
+ // Reject canceled error
+ reject(Common.CANCELED_ERROR);
+ }
+
+ // Otherwise
+ else {
+
+ // Set timeout
+ setTimeout(function() {
+
+ // Prompt to connect
+ promptToConnect();
+
+ }, Application.CHECK_HARDWARE_WALLET_PRIORITY_INTERVAL_MILLISECONDS);
+ }
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Reject canceled error
+ reject(Common.CANCELED_ERROR);
+ }
+ };
+
+ // Prompt to connect
+ promptToConnect();
+ });
+ }
+
+ // Show hardware wallet unlock message
+ showHardwareWalletUnlockMessage(hardwareWallet, text, textArguments = [], allowUnlock = false, preventMessages = false, cancelOccurred = Common.NO_CANCEL_OCCURRED) {
+
+ // Set self
+ var self = this;
+
+ // Return promise
+ return new Promise(function(resolve, reject) {
+
+ // Prompt to unlock
+ var promptToUnlock = function() {
+
+ // Check if cancel didn't occur
+ if(cancelOccurred === Common.NO_CANCEL_OCCURRED || cancelOccurred() === false) {
+
+ // Check if preventing messages or messages are allowed and no message is shown
+ if(preventMessages === true || (self.message.getAllowed() === true && self.message.isShown() === false)) {
+
+ // Initialize prevent cancel on hide
+ var preventCancelOnHide = false;
+
+ // Initialize external cancel check allowed
+ var externalCancelCheckAllowed = true;
+
+ // Check hardware wallet's transport type
+ switch(hardwareWallet.transport.type) {
+
+ // Ledger type
+ case HardwareWalletDefinitions.LEDGER_TRANSPORT_TYPE:
+
+ // Set message
+ var message = Message.createPendingResult() + Message.createLineBreak() + Message.createText(text, textArguments);
+
+ // Set second button
+ var secondButton = Message.NO_BUTTON;
+
+ // Break
+ break;
+
+ // Trezor type
+ case HardwareWalletDefinitions.TREZOR_TRANSPORT_TYPE:
+
+ // Check hardware wallet's transport product name
+ switch(hardwareWallet.transport["deviceModel"]["productName"]) {
+
+ // Trezor Model One
+ case "Trezor Model One":
+
+ // Set message
+ var message = Message.createText(text, textArguments) + Message.createLineBreak() + "" + Message.createText(Language.getDefaultTranslation('Enter your pin as the following alphabetic characters to unlock the hardware wallet.')) + " " + Message.createLineBreak() + Message.createPinMatrix() + Message.createLineBreak() + Message.createLineBreak() + Message.createInput(Language.getDefaultTranslation('Pin'), [], false) + Message.createLineBreak();
+
+ // Set second button
+ var secondButton = Language.getDefaultTranslation('Unlock');
+
+ // Break
+ break;
+
+ // Trezor Model T, Trezor Safe 3, Trezor Safe 5, or default
+ case "Trezor Model T":
+ case "Trezor Safe 3":
+ case "Trezor Safe 5":
+ default:
+
+ // Set message
+ var message = Message.createPendingResult() + Message.createLineBreak() + Message.createText(text, textArguments);
+
+ // Set second button
+ var secondButton = Message.NO_BUTTON;
+
+ // Break
+ break;
+ }
+
+ // Break
+ break;
+ }
+
+ // Return showing message and do it immediately if preventing messages
+ return self.message.show(Language.getDefaultTranslation('Hardware Wallet Locked'), message, preventMessages === true, function() {
+
+ // Check if cancel didn't occur
+ if(cancelOccurred === Common.NO_CANCEL_OCCURRED || cancelOccurred() === false) {
+
+ // Check if hardware wallet is connected and locked
+ if(hardwareWallet.isConnected() === true && hardwareWallet.isLocked() === true) {
+
+ // Check if preventing messages
+ if(preventMessages === true) {
+
+ // Hide loading
+ self.hideLoading();
+ }
+
+ // Otherwise
+ else {
+
+ // Save focus and blur
+ self.focus.save(true);
+
+ // Check if unlock display is shown
+ if(self.isUnlockDisplayShown() === true)
+
+ // Disable tabbing to everything in unlock display and disable everything in unlock display
+ self.unlockDisplay.find("*").disableTab().disable();
+
+ // Otherwise check if unlocked display is shown
+ else if(self.isUnlockedDisplayShown() === true)
+
+ // Disable unlocked
+ self.unlocked.disable();
+ }
+
+ // Hardware wallet unlock application event
+ $(hardwareWallet).one(HardwareWallet.UNLOCK_EVENT + ".application", function(event) {
+
+ // Turn off hardware wallet disconnect application event
+ $(hardwareWallet).off(HardwareWallet.DISCONNECT_EVENT + ".application");
+
+ // Turn off hardware wallet before disconnect application event
+ $(hardwareWallet).off(HardwareWallet.BEFORE_DISCONNECT_EVENT + ".application");
+
+ // Turn off hardware wallet device cancel application event
+ $(hardwareWallet).off(HardwareWallet.DEVICE_CANCEL_EVENT + ".application");
+
+ // Clear external cancel check allowed
+ externalCancelCheckAllowed = false;
+
+ // Disable message
+ self.message.disable();
+
+ // Set prevent cancel on hide
+ preventCancelOnHide = true;
+
+ // Check if preventing messages
+ if(preventMessages === true) {
+
+ // Check if can't be canceled
+ if(cancelOccurred === Common.NO_CANCEL_OCCURRED) {
+
+ // Show loading
+ self.showLoading();
+
+ // Prevent showing messages
+ self.message.prevent();
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Check if unlock display is shown
+ if(self.isUnlockDisplayShown() === true)
+
+ // Enable tabbing to everything in unlock display and enable everything in unlock display
+ self.unlockDisplay.find("*").enableTab().enable();
+
+ // Otherwise check if unlocked display is shown
+ else if(self.isUnlockedDisplayShown() === true)
+
+ // Enable unlocked
+ self.unlocked.enable();
+
+ // Restore focus and don't blur
+ self.focus.restore(false);
+ }
+
+ // Check if preventing messages and it can be canceled
+ if(preventMessages === true && cancelOccurred !== Common.NO_CANCEL_OCCURRED) {
+
+ // Return replacing message
+ return self.message.replace(Application.HARDWARE_WALLET_UNLOCK_MESSAGE).then(function() {
+
+ // Resolve
+ resolve();
+ });
+ }
+
+ // Otherwise
+ else {
+
+ // Return hiding message
+ return self.message.hide().then(function() {
+
+ // Resolve
+ resolve();
+ });
+ }
+ });
+
+ // Hardware wallet disconnect application event
+ $(hardwareWallet).one(HardwareWallet.DISCONNECT_EVENT + ".application", function(event) {
+
+ // Turn off hardware wallet unlock application event
+ $(hardwareWallet).off(HardwareWallet.UNLOCK_EVENT + ".application");
+
+ // Turn off hardware wallet before disconnect application event
+ $(hardwareWallet).off(HardwareWallet.BEFORE_DISCONNECT_EVENT + ".application");
+
+ // Turn off hardware wallet device cancel application event
+ $(hardwareWallet).off(HardwareWallet.DEVICE_CANCEL_EVENT + ".application");
+
+ // Clear external cancel check allowed
+ externalCancelCheckAllowed = false;
+
+ // Disable message
+ self.message.disable();
+
+ // Set prevent cancel on hide
+ preventCancelOnHide = true;
+
+ // Check if preventing messages
+ if(preventMessages === true) {
+
+ // Check if can't be canceled
+ if(cancelOccurred === Common.NO_CANCEL_OCCURRED) {
+
+ // Show loading
+ self.showLoading();
+
+ // Prevent showing messages
+ self.message.prevent();
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Check if unlock display is shown
+ if(self.isUnlockDisplayShown() === true)
+
+ // Enable tabbing to everything in unlock display and enable everything in unlock display
+ self.unlockDisplay.find("*").enableTab().enable();
+
+ // Otherwise check if unlocked display is shown
+ else if(self.isUnlockedDisplayShown() === true)
+
+ // Enable unlocked
+ self.unlocked.enable();
+
+ // Restore focus and don't blur
+ self.focus.restore(false);
+ }
+
+ // Check if preventing messages and it can be canceled
+ if(preventMessages === true && cancelOccurred !== Common.NO_CANCEL_OCCURRED) {
+
+ // Return replacing message
+ return self.message.replace(Application.HARDWARE_WALLET_DISCONNECT_MESSAGE).then(function() {
+
+ // Resolve
+ resolve();
+ });
+ }
+
+ // Otherwise
+ else {
+
+ // Return hiding message
+ return self.message.hide().then(function() {
+
+ // Resolve
+ resolve();
+ });
+ }
+ });
+
+ // Hardware wallet before disconnect application event
+ $(hardwareWallet).one(HardwareWallet.BEFORE_DISCONNECT_EVENT + ".application", function(event) {
+
+ // Turn off hardware wallet unlock application event
+ $(hardwareWallet).off(HardwareWallet.UNLOCK_EVENT + ".application");
+
+ // Turn off hardware wallet disconnect application event
+ $(hardwareWallet).off(HardwareWallet.DISCONNECT_EVENT + ".application");
+
+ // Turn off hardware wallet device cancel application event
+ $(hardwareWallet).off(HardwareWallet.DEVICE_CANCEL_EVENT + ".application");
+
+ // Clear external cancel check allowed
+ externalCancelCheckAllowed = false;
+
+ // Disable message
+ self.message.disable();
+
+ // Set prevent cancel on hide
+ preventCancelOnHide = true;
+
+ // Check if preventing messages
+ if(preventMessages === true) {
+
+ // Check if can't be canceled
+ if(cancelOccurred === Common.NO_CANCEL_OCCURRED) {
+
+ // Show loading
+ self.showLoading();
+
+ // Prevent showing messages
+ self.message.prevent();
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Check if unlock display is shown
+ if(self.isUnlockDisplayShown() === true)
+
+ // Enable tabbing to everything in unlock display and enable everything in unlock display
+ self.unlockDisplay.find("*").enableTab().enable();
+
+ // Otherwise check if unlocked display is shown
+ else if(self.isUnlockedDisplayShown() === true)
+
+ // Enable unlocked
+ self.unlocked.enable();
+
+ // Restore focus and don't blur
+ self.focus.restore(false);
+ }
+
+ // Check if preventing messages and it can be canceled
+ if(preventMessages === true && cancelOccurred !== Common.NO_CANCEL_OCCURRED) {
+
+ // Return replacing message
+ return self.message.replace(Application.HARDWARE_WALLET_DISCONNECT_MESSAGE).then(function() {
+
+ // Resolve
+ resolve();
+ });
+ }
+
+ // Otherwise
+ else {
+
+ // Return hiding message
+ return self.message.hide().then(function() {
+
+ // Resolve
+ resolve();
+ });
+ }
+ });
+
+ // Hardware wallet device cancel application event
+ $(hardwareWallet).one(HardwareWallet.DEVICE_CANCEL_EVENT + ".application", function(event) {
+
+ // Turn off hardware wallet unlock application event
+ $(hardwareWallet).off(HardwareWallet.UNLOCK_EVENT + ".application");
+
+ // Turn off hardware wallet disconnect application event
+ $(hardwareWallet).off(HardwareWallet.DISCONNECT_EVENT + ".application");
+
+ // Turn off hardware wallet before disconnect application event
+ $(hardwareWallet).off(HardwareWallet.BEFORE_DISCONNECT_EVENT + ".application");
+
+ // Clear external cancel check allowed
+ externalCancelCheckAllowed = false;
+
+ // Disable message
+ self.message.disable();
+
+ // Set prevent cancel on hide
+ preventCancelOnHide = true;
+
+ // Check if preventing messages
+ if(preventMessages === true) {
+
+ // Check if can't be canceled
+ if(cancelOccurred === Common.NO_CANCEL_OCCURRED) {
+
+ // Prevent showing messages
+ self.message.prevent();
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Check if unlock display is shown
+ if(self.isUnlockDisplayShown() === true)
+
+ // Enable tabbing to everything in unlock display and enable everything in unlock display
+ self.unlockDisplay.find("*").enableTab().enable();
+
+ // Otherwise check if unlocked display is shown
+ else if(self.isUnlockedDisplayShown() === true)
+
+ // Enable unlocked
+ self.unlocked.enable();
+
+ // Restore focus and don't blur
+ self.focus.restore(false);
+ }
+
+ // Check if preventing messages and it can be canceled
+ if(preventMessages === true && cancelOccurred !== Common.NO_CANCEL_OCCURRED) {
+
+ // Reject canceled error
+ reject(Common.CANCELED_ERROR);
+ }
+
+ // Otherwise
+ else {
+
+ // Return hiding message
+ return self.message.hide().then(function() {
+
+ // Reject canceled error
+ reject(Common.CANCELED_ERROR);
+ });
+ }
+ });
+
+ // Cancel if external canceled
+ var cancelIfExternalCanceled = function() {
+
+ // Check if external cancel check if allowed
+ if(externalCancelCheckAllowed === true) {
+
+ // Check if cancel occurred
+ if(cancelOccurred !== Common.NO_CANCEL_OCCURRED && cancelOccurred() === true) {
+
+ // Turn off hardware wallet unlock application event
+ $(hardwareWallet).off(HardwareWallet.UNLOCK_EVENT + ".application");
+
+ // Turn off hardware wallet disconnect application event
+ $(hardwareWallet).off(HardwareWallet.DISCONNECT_EVENT + ".application");
+
+ // Turn off hardware wallet before disconnect application event
+ $(hardwareWallet).off(HardwareWallet.BEFORE_DISCONNECT_EVENT + ".application");
+
+ // Turn off hardware wallet device cancel application event
+ $(hardwareWallet).off(HardwareWallet.DEVICE_CANCEL_EVENT + ".application");
+
+ // Disable message
+ self.message.disable();
+
+ // Set prevent cancel on hide
+ preventCancelOnHide = true;
+
+ // Check if preventing messages
+ if(preventMessages === true) {
+
+ // Check if can't be canceled
+ if(cancelOccurred === Common.NO_CANCEL_OCCURRED) {
+
+ // Prevent showing messages
+ self.message.prevent();
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Check if unlock display is shown
+ if(self.isUnlockDisplayShown() === true)
+
+ // Enable tabbing to everything in unlock display and enable everything in unlock display
+ self.unlockDisplay.find("*").enableTab().enable();
+
+ // Otherwise check if unlocked display is shown
+ else if(self.isUnlockedDisplayShown() === true)
+
+ // Enable unlocked
+ self.unlocked.enable();
+
+ // Restore focus and don't blur
+ self.focus.restore(false);
+ }
+
+ // Check if preventing messages and it can be canceled
+ if(preventMessages === true && cancelOccurred !== Common.NO_CANCEL_OCCURRED) {
+
+ // Reject canceled error
+ reject(Common.CANCELED_ERROR);
+ }
+
+ // Otherwise
+ else {
+
+ // Return hiding message
+ return self.message.hide().then(function() {
+
+ // Reject canceled error
+ reject(Common.CANCELED_ERROR);
+ });
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Set timeout
+ setTimeout(function() {
+
+ // Cancel if external canceled
+ cancelIfExternalCanceled();
+
+ }, Application.CANCELED_CHECK_INTERVAL_MILLISECONDS);
+ }
+ }
+ };
+
+ // Cancel if external canceled
+ cancelIfExternalCanceled();
+ }
+
+ // Otherwise
+ else {
+
+ // Set prevent cancel on hide
+ preventCancelOnHide = true;
+
+ // Resolve
+ resolve();
+
+ // Return false
+ return false;
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Return false
+ return false;
+ }
+
+ }, Language.getDefaultTranslation('Cancel'), secondButton, preventMessages === true, (allowUnlock === true) ? Message.VISIBLE_STATE_UNLOCK | Message.VISIBLE_STATE_UNLOCKED : Message.VISIBLE_STATE_UNLOCKED).then(function(messageResult) {
+
+ // Turn off hardware wallet unlock application event
+ $(hardwareWallet).off(HardwareWallet.UNLOCK_EVENT + ".application");
+
+ // Turn off hardware wallet disconnect application event
+ $(hardwareWallet).off(HardwareWallet.DISCONNECT_EVENT + ".application");
+
+ // Turn off hardware wallet before disconnect application event
+ $(hardwareWallet).off(HardwareWallet.BEFORE_DISCONNECT_EVENT + ".application");
+
+ // Turn off hardware wallet device cancel application event
+ $(hardwareWallet).off(HardwareWallet.DEVICE_CANCEL_EVENT + ".application");
+
+ // Clear external cancel check allowed
+ externalCancelCheckAllowed = false;
+
+ // Check if canceling
+ if(messageResult === Message.FIRST_BUTTON_CLICKED_RESULT) {
+
+ // Check if preventing messages
+ if(preventMessages === true) {
+
+ // Check if can't be canceled
+ if(cancelOccurred === Common.NO_CANCEL_OCCURRED) {
+
+ // Prevent showing messages
+ self.message.prevent();
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Check if unlock display is shown
+ if(self.isUnlockDisplayShown() === true)
+
+ // Enable tabbing to everything in unlock display and enable everything in unlock display
+ self.unlockDisplay.find("*").enableTab().enable();
+
+ // Otherwise check if unlocked display is shown
+ else if(self.isUnlockedDisplayShown() === true)
+
+ // Enable unlocked
+ self.unlocked.enable();
+
+ // Restore focus and don't blur
+ self.focus.restore(false);
+ }
+
+ // Check if preventing messages and it can be canceled
+ if(preventMessages === true && cancelOccurred !== Common.NO_CANCEL_OCCURRED) {
+
+ // Reject canceled error
+ reject(Common.CANCELED_ERROR);
+ }
+
+ // Otherwise
+ else {
+
+ // Return hiding message
+ return self.message.hide().then(function() {
+
+ // Reject canceled error
+ reject(Common.CANCELED_ERROR);
+ });
+ }
+ }
+
+ // Otherwise check if second button was clicked
+ else if(messageResult === Message.SECOND_BUTTON_CLICKED_RESULT) {
+
+ // Try
+ try {
+
+ // Get alphabetic pin
+ var alphabeticPin = self.message.getInputText().trim();
+ }
+
+ // Catch errors
+ catch(error) {
+
+ // Check if preventing messages
+ if(preventMessages === true) {
+
+ // Check if can't be canceled
+ if(cancelOccurred === Common.NO_CANCEL_OCCURRED) {
+
+ // Prevent showing messages
+ self.message.prevent();
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Check if unlock display is shown
+ if(self.isUnlockDisplayShown() === true)
+
+ // Enable tabbing to everything in unlock display and enable everything in unlock display
+ self.unlockDisplay.find("*").enableTab().enable();
+
+ // Otherwise check if unlocked display is shown
+ else if(self.isUnlockedDisplayShown() === true)
+
+ // Enable unlocked
+ self.unlocked.enable();
+
+ // Restore focus and don't blur
+ self.focus.restore(false);
+ }
+
+ // Check if preventing messages and it can be canceled
+ if(preventMessages === true && cancelOccurred !== Common.NO_CANCEL_OCCURRED) {
+
+ // Reject
+ reject();
+ }
+
+ // Otherwise
+ else {
+
+ // Return hiding message
+ return self.message.hide().then(function() {
+
+ // Reject
+ reject();
+ });
+ }
+
+ // Return
+ return;
+ }
+
+ // Show loading
+ self.showLoading();
+
+ // Set that message second button is loading
+ self.message.setButtonLoading(Message.SECOND_BUTTON);
+
+ // Disable message
+ self.message.disable();
+
+ // Resolve alphabetic pin
+ resolve(alphabeticPin);
+ }
+
+ // Otherwise check if not preventing cancel on hide
+ else if(preventCancelOnHide === false) {
+
+ // Reject canceled error
+ reject(Common.CANCELED_ERROR);
+ }
+ });
+ }
+
+ // Otherwise
+ else {
+
+ // Check if a high priority wallets exclusive transactions lock is waiting
+ if(self.transactions.isHighPriorityWalletsExclusiveTransactionsLockWaiting(hardwareWallet.getWalletKeyPath()) === true) {
+
+ // Reject canceled error
+ reject(Common.CANCELED_ERROR);
+ }
+
+ // Otherwise
+ else {
+
+ // Set timeout
+ setTimeout(function() {
+
+ // Prompt to unlock
+ promptToUnlock();
+
+ }, Application.CHECK_HARDWARE_WALLET_PRIORITY_INTERVAL_MILLISECONDS);
+ }
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Reject canceled error
+ reject(Common.CANCELED_ERROR);
+ }
+ };
+
+ // Prompt to unlock
+ promptToUnlock();
+ });
+ }
+
+ // Hardware wallet unlock message done
+ hardwareWalletUnlockMessageDone(preventMessages = false, cancelOccurred = Common.NO_CANCEL_OCCURRED, replaceMessage = true) {
+
+ // Set self
+ var self = this;
+
+ // Return promise
+ return new Promise(function(resolve, reject) {
+
+ // Check if preventing messages
+ if(preventMessages === true) {
+
+ // Check if can't be canceled
+ if(cancelOccurred === Common.NO_CANCEL_OCCURRED) {
+
+ // Show loading
+ self.showLoading();
+
+ // Prevent showing messages
+ self.message.prevent();
+ }
+
+ // Otherwise check if replacing message
+ else if(replaceMessage === true) {
+
+ // Hide loading
+ self.hideLoading();
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Check if replacing message
+ if(replaceMessage === true) {
+
+ // Hide loading
+ self.hideLoading();
+
+ // Check if unlock display is shown
+ if(self.isUnlockDisplayShown() === true)
+
+ // Enable tabbing to everything in unlock display and enable everything in unlock display
+ self.unlockDisplay.find("*").enableTab().enable();
+
+ // Otherwise check if unlocked display is shown
+ else if(self.isUnlockedDisplayShown() === true)
+
+ // Enable unlocked
+ self.unlocked.enable();
+
+ // Restore focus and don't blur
+ self.focus.restore(false);
+ }
+ }
+
+ // Check if preventing messages and it can be canceled
+ if(preventMessages === true && cancelOccurred !== Common.NO_CANCEL_OCCURRED) {
+
+ // Check if replacing message
+ if(replaceMessage === true) {
+
+ // Return replacing message
+ return self.message.replace(Application.HARDWARE_WALLET_UNLOCK_MESSAGE).then(function() {
+
+ // Resolve
+ resolve();
+ });
+ }
+
+ // Otherwise
+ else {
+
+ // Resolve
+ resolve();
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Check if replacing message
+ if(replaceMessage === true) {
+
+ // Return hiding message
+ return self.message.hide().then(function() {
+
+ // Resolve
+ resolve();
+ });
+ }
+
+ // Otherwise
+ else {
+
+ // Resolve
+ resolve();
+ }
+ }
+ });
+ }
+
+ // Show hardware wallet pending message
+ showHardwareWalletPendingMessage(hardwareWallet, text, allowUnlock = false, preventMessages = false, cancelOccurred = Common.NO_CANCEL_OCCURRED, recursivelyShown = false, rootCanceled = undefined) {
+
+ // Set self
+ var self = this;
+
+ // Return promise
+ return new Promise(function(resolve, reject) {
+
+ // Prompt to approve
+ var promptToApprove = function() {
+
+ // Check if cancel didn't occur or recursively shown
+ if(cancelOccurred === Common.NO_CANCEL_OCCURRED || cancelOccurred() === false || recursivelyShown === true) {
+
+ // Check if preventing messages, recursively shown, or messages are allowed and no message is shown
+ if(preventMessages === true || recursivelyShown === true || (self.message.getAllowed() === true && self.message.isShown() === false)) {
+
+ // Initialize canceled
+ var canceled = {
+
+ // Value
+ "Value": false
+ };
+
+ // Initialize sleep disabled
+ var sleepDisabled = false;
+
+ // Return showing message and do it immediately if preventing messages or recursively shown
+ return self.message.show(Language.getDefaultTranslation('Hardware Wallet Approval Requested'), text, preventMessages === true || recursivelyShown === true, function() {
+
+ // Check if cancel didn't occur or recursively shown
+ if(cancelOccurred === Common.NO_CANCEL_OCCURRED || cancelOccurred() === false || recursivelyShown === true) {
+
+ // Check if hardware wallet is connected
+ if(hardwareWallet.isConnected() === true) {
+
+ // Check if preventing messages
+ if(preventMessages === true) {
+
+ // Hide loading
+ self.hideLoading();
+ }
+
+ // Otherwise
+ else {
+
+ // Save focus and blur
+ self.focus.save(true);
+
+ // Check if unlock display is shown
+ if(self.isUnlockDisplayShown() === true)
+
+ // Disable tabbing to everything in unlock display and disable everything in unlock display
+ self.unlockDisplay.find("*").disableTab().disable();
+
+ // Otherwise check if unlocked display is shown
+ else if(self.isUnlockedDisplayShown() === true)
+
+ // Disable unlocked
+ self.unlocked.disable();
+
+ // Keep device awake and catch errors
+ self.wakeLock.preventLock().catch(function(error) {
+
+ });
+
+ // Set sleep disabled
+ sleepDisabled = true;
+ }
+
+ // Message before replace application hardware wallet approve event
+ $(self.message).on(Message.BEFORE_REPLACE_EVENT + ".applicationHardwareWalletApprove", function(event, messageType, messageData) {
+
+ // Check if message type is hardware wallet unlock message
+ if(messageType === Application.HARDWARE_WALLET_UNLOCK_MESSAGE) {
+
+ // Turn off message before replace application hardware wallet approve event
+ $(self.message).off(Message.BEFORE_REPLACE_EVENT + ".applicationHardwareWalletApprove");
+
+ // Show hardware wallet pending message and catch errors
+ self.showHardwareWalletPendingMessage(hardwareWallet, text, allowUnlock, preventMessages, cancelOccurred, true, (recursivelyShown === true) ? rootCanceled : canceled).catch(function(error) {
+
+ // Replace message
+ self.message.replace(Application.HARDWARE_WALLET_DISCONNECT_MESSAGE);
+ });
+
+ // Return false to stop other replace message
+ return false;
+ }
+ });
+
+ // Message show application hardware wallet approve event
+ $(self.message).one(Message.SHOW_EVENT + ".applicationHardwareWalletApprove", function() {
+
+ // Resolve canceled
+ resolve(function() {
+
+ // Return if canceled
+ return canceled["Value"] === true;
+ });
+ });
+ }
+
+ // Otherwise
+ else {
+
+ // Reject hardware wallet disconnected error
+ reject(HardwareWallet.DISCONNECTED_ERROR);
+
+ // Return false
+ return false;
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Return false
+ return false;
+ }
+
+ }, Language.getDefaultTranslation('Cancel'), Message.NO_BUTTON, preventMessages === true || recursivelyShown === true, (allowUnlock === true) ? Message.VISIBLE_STATE_UNLOCK | Message.VISIBLE_STATE_UNLOCKED : Message.VISIBLE_STATE_UNLOCKED).then(function(messageResult) {
+
+ // Turn off message show application hardware wallet approve event
+ $(self.message).off(Message.SHOW_EVENT + ".applicationHardwareWalletApprove");
+
+ // Check if message was displayed
+ if(messageResult !== Message.NOT_DISPLAYED_RESULT) {
+
+ // Turn off message before replace application hardware wallet approve event
+ $(self.message).off(Message.BEFORE_REPLACE_EVENT + ".applicationHardwareWalletApprove");
+
+ // Check if recursively shown
+ if(recursivelyShown === true) {
+
+ // Set root canceled
+ rootCanceled["Value"] = true;
+ }
+
+ // Otherwise
+ else {
+
+ // Set canceled
+ canceled["Value"] = true;
+ }
+
+ // Check if sleep is disabled
+ if(sleepDisabled === true) {
+
+ // Allow device to sleep and catch errors
+ self.wakeLock.allowLock().catch(function(error) {
+
+ // Finally
+ }).finally(function() {
+
+ // Check if unlock display is shown
+ if(self.isUnlockDisplayShown() === true)
+
+ // Enable tabbing to everything in unlock display and enable everything in unlock display
+ self.unlockDisplay.find("*").enableTab().enable();
+
+ // Otherwise check if unlocked display is shown
+ else if(self.isUnlockedDisplayShown() === true)
+
+ // Enable unlocked
+ self.unlocked.enable();
+
+ // Restore focus and don't blur
+ self.focus.restore(false);
+
+ // Hide message
+ self.message.hide().then(function() {
+
+ // Replace message
+ self.message.replace(Application.HARDWARE_WALLET_DISCONNECT_MESSAGE);
+ });
+ });
+ }
+
+ // Otherwise
+ else {
+
+ // Check if preventing messages
+ if(preventMessages === true) {
+
+ // Prevent showing messages
+ self.message.prevent();
+ }
+
+ // Otherwise
+ else {
+
+ // Check if unlock display is shown
+ if(self.isUnlockDisplayShown() === true)
+
+ // Enable tabbing to everything in unlock display and enable everything in unlock display
+ self.unlockDisplay.find("*").enableTab().enable();
+
+ // Otherwise check if unlocked display is shown
+ else if(self.isUnlockedDisplayShown() === true)
+
+ // Enable unlocked
+ self.unlocked.enable();
+
+ // Restore focus and don't blur
+ self.focus.restore(false);
+ }
+
+ // Hide message
+ self.message.hide().then(function() {
+
+ // Replace message
+ self.message.replace(Application.HARDWARE_WALLET_DISCONNECT_MESSAGE);
+ });
+ }
+ }
+
+ // Otherwise check if sleep is disabled
+ else if(sleepDisabled === true) {
+
+ // Allow device to sleep and catch errors
+ self.wakeLock.allowLock().catch(function(error) {
+
+ // Finally
+ }).finally(function() {
+
+ // Reject canceled error
+ reject(Common.CANCELED_ERROR);
+ });
+ }
+
+ // Otherwise
+ else {
+
+ // Reject canceled error
+ reject(Common.CANCELED_ERROR);
+ }
+ });
+ }
+
+ // Otherwise
+ else {
+
+ // Check if a high priority wallets exclusive transactions lock is waiting
+ if(self.transactions.isHighPriorityWalletsExclusiveTransactionsLockWaiting(hardwareWallet.getWalletKeyPath()) === true) {
+
+ // Reject canceled error
+ reject(Common.CANCELED_ERROR);
+ }
+
+ // Otherwise
+ else {
+
+ // Set timeout
+ setTimeout(function() {
+
+ // Prompt to approve
+ promptToApprove();
+
+ }, Application.CHECK_HARDWARE_WALLET_PRIORITY_INTERVAL_MILLISECONDS);
+ }
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Reject canceled error
+ reject(Common.CANCELED_ERROR);
+ }
+ };
+
+ // Prompt to approve
+ promptToApprove();
+ });
+ }
+
+ // Hardware wallet pending message done
+ hardwareWalletPendingMessageDone(preventMessages = false) {
+
+ // Turn off message before replace application hardware wallet approve event
+ $(this.message).off(Message.BEFORE_REPLACE_EVENT + ".applicationHardwareWalletApprove");
+
+ // Set self
+ var self = this;
+
+ // Return promise
+ return new Promise(function(resolve, reject) {
+
+ // Check if preventing messages
+ if(preventMessages === true) {
+
+ // Show loading
+ self.showLoading();
+
+ // Prevent showing messages
+ self.message.prevent();
+ }
+
+ // Otherwise
+ else {
+
+ // Hide loading
+ self.hideLoading();
+
+ // Check if unlock display is shown
+ if(self.isUnlockDisplayShown() === true)
+
+ // Enable tabbing to everything in unlock display and enable everything in unlock display
+ self.unlockDisplay.find("*").enableTab().enable();
+
+ // Otherwise check if unlocked display is shown
+ else if(self.isUnlockedDisplayShown() === true)
+
+ // Enable unlocked
+ self.unlocked.enable();
+
+ // Restore focus and don't blur
+ self.focus.restore(false);
+ }
+
+ // Return hiding message
+ return self.message.hide().then(function() {
+
+ // Resolve
+ resolve();
+ });
+ });
+ }
+
+ // Is disabled
+ isDisabled() {
+
+ // Check if create display is shown
+ if(this.isCreateDisplayShown() === true) {
+
+ // Return if create display is disabled
+ return this.createDisplay.children("div").first().attr("tabindex") === Common.NO_TAB_INDEX;
+ }
+
+ // Otherwise check if unlock display is shown
+ else if(this.isUnlockDisplayShown() === true) {
+
+ // Return if unlock display is disabled
+ return this.unlockDisplay.children("div").first().attr("tabindex") === Common.NO_TAB_INDEX;
+ }
+
+ // Otherwise
+ else {
+
+ // Return false
+ return false;
+ }
+ }
+
+ // Show loading delay milliseconds
+ static get SHOW_LOADING_DELAY_MILLISECONDS() {
+
+ // Return show loading delay milliseconds
+ return 100;
+ }
+
+ // Hardware wallet disconnect message
+ static get HARDWARE_WALLET_DISCONNECT_MESSAGE() {
+
+ // Return hardware wallet disconnect message
+ return "ApplicationHardwareWalletDisconnectMessage";
+ }
+
+ // Hardware wallet unlock message
+ static get HARDWARE_WALLET_UNLOCK_MESSAGE() {
+
+ // Return hardware wallet unlock message
+ return "ApplicationHardwareWalletUnlockMessage";
+ }
+
+ // Private
+
+ // Is not iframe
+ isNotIframe() {
+
+ // Set self
+ var self = this;
+
+ // Return promise
+ return new Promise(function(resolve, reject) {
+
+ // Check if not an iframe
+ if(Common.isIframe() === false)
+
+ // Resolve
+ resolve();
+
+ // Otherwise
+ else {
+
+ // Check if is an extension
+ if(Common.isExtension() === true) {
+
+ // Set message
+ var message = Message.createText(Language.getDefaultTranslation('This extension won\'t run when it\'s embedded in a site.'));
+ }
+
+ // Otherwise check if is an app
+ else if(Common.isApp() === true) {
+
+ // Set message
+ var message = Message.createText(Language.getDefaultTranslation('This app won\'t run when it\'s embedded in a site.'));
+ }
+
+ // Otherwise
+ else {
+
+ // Set message
+ var message = Message.createText(Language.getDefaultTranslation('This site won\'t run when it\'s embedded in a site. Visit %1$l to continue.'), [
+
+ [
+ // Text
+ Language.getDefaultTranslation('MWC Wallet'),
+
+ // URL
+ location["href"],
+
+ // Is external
+ true,
+
+ // Is blob
+ false
+ ]
+ ]);
+ }
+
+ // Show message and allow showing messages
+ self.message.show(Language.getDefaultTranslation('Error'), message, false, function() {
+
+ // Hide loading
+ self.hideLoading();
+
+ // Hide loading display spinner
+ self.loadingDisplay.children("div.spinner").addClass("hide");
+
+ }, Message.NO_BUTTON, Message.NO_BUTTON, true);
+ }
+ });
+ }
+
+ // Browser is compatible
+ browserIsCompatible() {
+
+ // Set self
+ var self = this;
+
+ // Return promise
+ return new Promise(function(resolve, reject) {
+
+ // Try
+ try {
+
+ // Check if math isn't supported
+ if(typeof Math !== "object" || Math === null)
+
+ // Throw error
+ throw "Math isn't supported.";
+
+ // Otherwise check if document isn't supported
+ else if(typeof document !== "object" || document === null)
+
+ // Throw error
+ throw "Document isn't supported.";
+
+ // Otherwise check if window isn't supported
+ else if(typeof window !== "object" || window === null)
+
+ // Throw error
+ throw "Window isn't supported.";
+
+ // Otherwise check if global this isn't supported
+ else if(typeof globalThis !== "object" || globalThis === null)
+
+ // Throw error
+ throw "Global this isn't supported.";
+
+ // Otherwise Check if promises aren't supported
+ else if(typeof Promise !== "function")
+
+ // Throw error
+ throw "Promises aren't supported.";
+
+ // Otherwise check if crypto isn't supported
+ else if(typeof crypto !== "object" || crypto === null)
+
+ // Throw error
+ throw "Crypto isn't supported.";
+
+ // Otherwise check if IndexedDB isn't supported
+ else if(typeof indexedDB !== "object" || indexedDB === null)
+
+ // Throw error
+ throw "IndexedDB isn't supported.";
+
+ // Otherwise check if IDBKeyRange isn't supported
+ else if(typeof IDBKeyRange !== "function")
+
+ // Throw error
+ throw "IDBKeyRange isn't supported.";
+
+ // Otherwise check if Number isn't supported
+ else if(typeof Number !== "function")
+
+ // Throw error
+ throw "Number isn't supported.";
+
+ // Otherwise check if String isn't supported
+ else if(typeof String !== "function")
+
+ // Throw error
+ throw "String isn't supported.";
+
+ // Otherwise check if local storage isn't supported
+ else if(typeof localStorage !== "object" || localStorage === null)
+
+ // Throw error
+ throw "Local storage isn't supported.";
+
+ // Otherwise check if 8-bit unsigned integer arrays aren't supported
+ else if(typeof Uint8Array !== "function")
+
+ // Throw error
+ throw "8-bit unsigned integer arrays aren't supported.";
+
+ // Otherwise check if 16-bit unsigned integer arrays aren't supported
+ else if(typeof Uint16Array !== "function")
+
+ // Throw error
+ throw "16-bit unsigned integer arrays aren't supported.";
+
+ // Otherwise check if 8-bit unsigned integer clamped arrays aren't supported
+ else if(typeof Uint8ClampedArray !== "function")
+
+ // Throw error
+ throw "8-bit unsigned integer clamped arrays aren't supported.";
+
+ // Otherwise check if text encoders aren't supported
+ else if(typeof TextEncoder !== "function")
+
+ // Throw error
+ throw "Text encoders aren't supported.";
+
+ // Otherwise check if text decoders aren't supported
+ else if(typeof TextDecoder !== "function")
+
+ // Throw error
+ throw "Text decoders aren't supported.";
+
+ // Otherwise check if URLs aren't supported
+ else if(typeof URL !== "function")
+
+ // Throw error
+ throw "URLs aren't supported.";
+
+ // Otherwise check if responses aren't supported
+ else if(typeof Response !== "function")
+
+ // Throw error
+ throw "Responses aren't supported.";
+
+ // Otherwise check if headers aren't supported
+ else if(typeof Headers !== "function")
+
+ // Throw error
+ throw "Headers aren't supported.";
+
+ // Otherwise check if data views aren't supported
+ else if(typeof DataView !== "function")
+
+ // Throw error
+ throw "Data views aren't supported.";
+
+ // Otherwise check if 32-bit unsigned integer arrays aren't supported
+ else if(typeof Uint32Array !== "function")
+
+ // Throw error
+ throw "32-bit unsigned integer arrays aren't supported.";
+
+ // Otherwise check if array buffers aren't supported
+ else if(typeof ArrayBuffer !== "function")
+
+ // Throw error
+ throw "Array buffers aren't supported.";
+
+ // Otherwise check if arrays aren't supported
+ else if(typeof Array !== "function")
+
+ // Throw error
+ throw "Arrays aren't supported.";
+
+ // Otherwise check if JSON isn't supported
+ else if(typeof JSON !== "object" || JSON === null)
+
+ // Throw error
+ throw "JSON isn't supported.";
+
+ // Otherwise check if WebSockets aren't supported
+ else if(typeof WebSocket !== "function")
+
+ // Throw error
+ throw "WebSockets aren't supported.";
+
+ // Otherwise check if Object isn't supported
+ else if(typeof Object !== "function")
+
+ // Throw error
+ throw "Object isn't supported.";
+
+ // Otherwise check if Date isn't supported
+ else if(typeof Date !== "function")
+
+ // Throw error
+ throw "Date isn't supported.";
+
+ // Otherwise check if Intl isn't supported
+ else if(typeof Intl !== "object" || Intl === null)
+
+ // Throw error
+ throw "Intl isn't supported.";
+
+ // Otherwise check if Sets aren't supported
+ else if(typeof Set !== "function")
+
+ // Throw error
+ throw "Sets aren't supported.";
+
+ // Otherwise check if WebAssembly isn't supported
+ else if(typeof WebAssembly !== "object" || WebAssembly === null)
+
+ // Throw error
+ throw "WebAssembly isn't supported.";
+
+ // Otherwise check if Web Workers aren't supported
+ else if(typeof Worker !== "function")
+
+ // Throw error
+ throw "Web Workers aren't supported.";
+
+ // Otherwise check if RegExp isn't supported
+ else if(typeof RegExp !== "function")
+
+ // Throw error
+ throw "RegExp isn't supported.";
+
+ // Otherwise check if 32-bit floating point arrays aren't supported
+ else if(typeof Float32Array !== "function")
+
+ // Throw error
+ throw "32-bit floating point arrays aren't supported.";
+
+ // Otherwise check if 64-bit floating point arrays aren't supported
+ else if(typeof Float64Array !== "function")
+
+ // Throw error
+ throw "64-bit floating point arrays aren't supported.";
+
+ // Otherwise check if Image isn't supported
+ else if(typeof Image !== "function")
+
+ // Throw error
+ throw "Image isn't supported.";
+
+ // Otherwise check if 16-bit signed integer arrays aren't supported
+ else if(typeof Int16Array !== "function")
+
+ // Throw error
+ throw "16-bit signed integer arrays aren't supported.";
+
+ // Otherwise check if 32-bit signed integer arrays aren't supported
+ else if(typeof Int32Array !== "function")
+
+ // Throw error
+ throw "32-bit signed integer arrays aren't supported.";
+
+ // Otherwise check if 8-bit signed integer arrays aren't supported
+ else if(typeof Int8Array !== "function")
+
+ // Throw error
+ throw "8-bit signed integer arrays aren't supported.";
+
+ // Otherwise check if RangeError isn't supported
+ else if(typeof RangeError !== "function")
+
+ // Throw error
+ throw "RangeError isn't supported.";
+
+ // Otherwise check if TypeError isn't supported
+ else if(typeof TypeError !== "function")
+
+ // Throw error
+ throw "TypeError isn't supported.";
+
+ // Otherwise check if Error isn't supported
+ else if(typeof Error !== "function")
+
+ // Throw error
+ throw "Error isn't supported.";
+
+ // Otherwise check if XMLHttpRequest isn't supported
+ else if(typeof XMLHttpRequest !== "function")
+
+ // Throw error
+ throw "XMLHttpRequest isn't supported.";
+
+ // Otherwise check if fetch isn't supported
+ else if(typeof fetch !== "function")
+
+ // Throw error
+ throw "Fetch isn't supported.";
+
+ // Otherwise check if set interval isn't supported
+ else if(typeof setInterval !== "function")
+
+ // Throw error
+ throw "Set interval isn't supported.";
+
+ // Otherwise check if clear interval isn't supported
+ else if(typeof clearInterval !== "function")
+
+ // Throw error
+ throw "Clear interval isn't supported.";
+
+ // Otherwise check if set timeout isn't supported
+ else if(typeof setTimeout !== "function")
+
+ // Throw error
+ throw "Set timeout isn't supported.";
+
+ // Otherwise check if clear timeout isn't supported
+ else if(typeof clearTimeout !== "function")
+
+ // Throw error
+ throw "Clear timeout isn't supported.";
+
+ // Otherwise check if request animation frame isn't supported
+ else if(typeof requestAnimationFrame !== "function")
+
+ // Throw error
+ throw "Request animation frame isn't supported.";
+
+ // Otherwise check if parse integer isn't supported
+ else if(typeof parseInt !== "function")
+
+ // Throw error
+ throw "Parse integer isn't supported.";
+
+ // Otherwise check if parse float isn't supported
+ else if(typeof parseFloat !== "function")
+
+ // Throw error
+ throw "Parse float isn't supported.";
+
+ // Otherwise check if decode URI component isn't supported
+ else if(typeof decodeURIComponent !== "function")
+
+ // Throw error
+ throw "Decode URI component isn't supported.";
+
+ // Otherwise check if encode URI component isn't supported
+ else if(typeof encodeURIComponent !== "function")
+
+ // Throw error
+ throw "Encode URI component isn't supported.";
+
+ // Otherwise check if Event isn't supported
+ else if(typeof Event !== "function")
+
+ // Throw error
+ throw "Event isn't supported.";
+
+ // Otherwise check if Function isn't supported
+ else if(typeof Function !== "function")
+
+ // Throw error
+ throw "Function isn't supported.";
+
+ // Otherwise check if DOM exception isn't supported
+ else if(typeof DOMException !== "function")
+
+ // Throw error
+ throw "DOM exception isn't supported.";
+
+ // Otherwise check if file reader isn't supported
+ else if(typeof FileReader !== "function")
+
+ // Throw error
+ throw "File reader isn't supported.";
+
+ // Otherwise check if Blobs aren't supported
+ else if(typeof Blob !== "function")
+
+ // Throw error
+ throw "Blobs aren't supported.";
+
+ // Otherwise
+ else
+
+ // Resolve
+ resolve();
+ }
+
+ // Catch errors
+ catch(error) {
+
+ // Show message and allow showing messages
+ self.message.show(Language.getDefaultTranslation('Error'), Message.createText(Language.getDefaultTranslation('Your browser isn\'t compatible. Update your browser to continue.')), false, function() {
+
+ // Hide loading
+ self.hideLoading();
+
+ // Hide loading display spinner
+ self.loadingDisplay.children("div.spinner").addClass("hide");
+
+ }, Message.NO_BUTTON, Message.NO_BUTTON, true);
+ }
+ });
+ }
+
+ // Install service worker
+ installServiceWorker() {
+
+ // Set self
+ var self = this;
+
+ // Return promise
+ return new Promise(function(resolve, reject) {
+
+ // Check service work installer's installation status
+ switch(self.serviceWorkerInstaller.getInstallationStatus()) {
+
+ // Installing
+ case ServiceWorkerInstaller.INSTALLING_STATUS:
+
+ // Service worker installer install succeeded application event
+ $(self.serviceWorkerInstaller).one(ServiceWorkerInstaller.INSTALL_SUCCEEDED_EVENT + ".application", function() {
+
+ // Turn off service worker installer install failed application event
+ $(self.serviceWorkerInstaller).off(ServiceWorkerInstaller.INSTALL_FAILED_EVENT + ".application");
+
+ // Resolve
+ resolve();
+
+ // Service worker installer install failed application event
+ }).one(ServiceWorkerInstaller.INSTALL_FAILED_EVENT + ".application", function() {
+
+ // Turn off service worker installer install succeeded application event
+ $(self.serviceWorkerInstaller).off(ServiceWorkerInstaller.INSTALL_SUCCEEDED_EVENT + ".application");
+
+ // Check if is an app
+ if(Common.isApp() === true) {
+
+ // Set message
+ var message = Message.createText(Language.getDefaultTranslation('Failed to install or update the service worker.')) + Message.createText(Language.getDefaultTranslation('(?<=.) ')) + Message.createText(Language.getDefaultTranslation('Restart this app to try again.'));
+ }
+
+ // Otherwise
+ else {
+
+ // Set message
+ var message = Message.createText(Language.getDefaultTranslation('Failed to install or update the service worker.')) + Message.createText(Language.getDefaultTranslation('(?<=.) ')) + Message.createText(Language.getDefaultTranslation('Refresh this site to try again.'));
+ }
+
+ // Show message and allow showing messages
+ self.message.show(Language.getDefaultTranslation('Error'), message, false, function() {
+
+ // Hide loading
+ self.hideLoading();
+
+ // Show language display
+ Language.showDisplay();
+
+ // Hide loading display spinner
+ self.loadingDisplay.children("div.spinner").addClass("hide");
+
+ }, Message.NO_BUTTON, Message.NO_BUTTON, true);
+ });
+
+ // Break
+ break;
+
+ // Unsupported or installed
+ case ServiceWorkerInstaller.UNSUPPORTED_STATUS:
+ case ServiceWorkerInstaller.INSTALLED_STATUS:
+
+ // Resolve
+ resolve();
+
+ // Break
+ break;
+
+ // Failed
+ case ServiceWorkerInstaller.FAILED_STATUS:
+
+ // Check if is an app
+ if(Common.isApp() === true) {
+
+ // Set message
+ var message = Message.createText(Language.getDefaultTranslation('Failed to install or update the service worker.')) + Message.createText(Language.getDefaultTranslation('(?<=.) ')) + Message.createText(Language.getDefaultTranslation('Restart this app to try again.'));
+ }
+
+ // Otherwise
+ else {
+
+ // Set message
+ var message = Message.createText(Language.getDefaultTranslation('Failed to install or update the service worker.')) + Message.createText(Language.getDefaultTranslation('(?<=.) ')) + Message.createText(Language.getDefaultTranslation('Refresh this site to try again.'));
+ }
+
+ // Show message and allow showing messages
+ self.message.show(Language.getDefaultTranslation('Error'), message, false, function() {
+
+ // Hide loading
+ self.hideLoading();
+
+ // Show language display
+ Language.showDisplay();
+
+ // Hide loading display spinner
+ self.loadingDisplay.children("div.spinner").addClass("hide");
+
+ }, Message.NO_BUTTON, Message.NO_BUTTON, true);
+
+ // Break
+ break;
+ }
+ });
+ }
+
+ // Show private browsing message
+ showPrivateBrowsingMessage() {
+
+ // Set self
+ var self = this;
+
+ // Return promise
+ return new Promise(function(resolve, reject) {
+
+ // Get if private browsing message has been shown
+ var privateBrowsingMessageShown = localStorage.getItem(Application.PRIVATE_BROWSING_MESSAGE_SHOWN_LOCAL_STORAGE_NAME);
+
+ // Check if private browsing message hasn't been shown and not an app or extension
+ if((privateBrowsingMessageShown === Common.INVALID_LOCAL_STORAGE_ITEM || privateBrowsingMessageShown !== Application.PRIVATE_BROWSING_MESSAGE_SHOWN_TRUE_VALUE) && Common.isApp() === false && Common.isExtension() === false) {
+
+ // Initialize message button clicked
+ var messageButtonClicked = false;
+
+ // Window storage application event
+ $(window).on("storage.application", function(event) {
+
+ // Check if private browsing message shown was changed
+ if(event["originalEvent"]["key"] === Application.PRIVATE_BROWSING_MESSAGE_SHOWN_LOCAL_STORAGE_NAME) {
+
+ // Turn off window storage application event
+ $(window).off("storage.application");
+
+ // Turn off window resize application event
+ $(window).off("resize.application");
+
+ // Set timeout
+ setTimeout(function() {
+
+ // Check if message button wasn't clicked
+ if(messageButtonClicked === false) {
+
+ // Show loading
+ self.showLoading();
+
+ // Prevent showing messages
+ self.message.prevent();
+
+ // Hide message
+ self.message.hide().then(function() {
+
+ // Resolve
+ resolve();
+ });
+ }
+
+ }, Application.HIDE_PRIVATE_BROWSING_MESSAGE_DELAY_MILLISECONDS);
+ }
+ });
+
+ // Windows resize application event
+ $(window).on("resize.application", function() {
+
+ // Check if is an app
+ if(Common.isApp() === true) {
+
+ // Turn off window storage application event
+ $(window).off("storage.application");
+
+ // Turn off window resize application event
+ $(window).off("resize.application");
+
+ // Set timeout
+ setTimeout(function() {
+
+ // Check if message button wasn't clicked
+ if(messageButtonClicked === false) {
+
+ // Show loading
+ self.showLoading();
+
+ // Prevent showing messages
+ self.message.prevent();
+
+ // Hide message
+ self.message.hide().then(function() {
+
+ // Resolve
+ resolve();
+ });
+ }
+
+ }, Application.HIDE_PRIVATE_BROWSING_MESSAGE_DELAY_MILLISECONDS);
+ }
+ });
+
+ // Show message and allow showing messages
+ self.message.show(Language.getDefaultTranslation('Private Browsing And Site Data Information'), Message.createText(Language.getDefaultTranslation('This site won\'t function correctly if you have private or incognito browsing modes enabled or if your browser is configured to automatically delete cookies and site data. Make sure that private and incognito browsing modes are disabled and that your browser is configured to retain cookies and site data before continuing.')), false, function() {
+
+ // Hide loading
+ self.hideLoading();
+
+ // Show language display
+ Language.showDisplay();
+
+ }, Language.getDefaultTranslation('OK'), Message.NO_BUTTON, true).then(function(messageResult) {
+
+ // Turn off window storage application event
+ $(window).off("storage.application");
+
+ // Turn off window resize application event
+ $(window).off("resize.application");
+
+ // Check if message was displayed
+ if(messageResult !== Message.NOT_DISPLAYED_RESULT) {
+
+ // Set message button clicked
+ messageButtonClicked = true;
+
+ // Try
+ try {
+
+ // Save that private browsing message has been shown
+ localStorage.setItem(Application.PRIVATE_BROWSING_MESSAGE_SHOWN_LOCAL_STORAGE_NAME, Application.PRIVATE_BROWSING_MESSAGE_SHOWN_TRUE_VALUE);
+ }
+
+ // Catch errors
+ catch(error) {
+
+ // Trigger a fatal error
+ new FatalError(FatalError.LOCAL_STORAGE_ERROR);
+
+ // Return
+ return;
+ }
+
+ // Show loading
+ self.showLoading();
+
+ // Prevent showing messages
+ self.message.prevent();
+
+ // Hide message
+ self.message.hide().then(function() {
+
+ // Resolve
+ resolve();
+ });
+ }
+ });
+ }
+
+ // Otherwise
+ else
+
+ // Resolve
+ resolve();
+ });
+ }
+
+ // Show third-party cookies message
+ showThirdPartyCookiesMessage() {
+
+ // Set self
+ var self = this;
+
+ // Return promise
+ return new Promise(function(resolve, reject) {
+
+ // Get if third-party cookies message has been shown
+ var thirdPartyCookiesMessageShown = localStorage.getItem(Application.THIRD_PARTY_COOKIES_MESSAGE_SHOWN_LOCAL_STORAGE_NAME);
+
+ // Check if third-party cookies message hasn't been shown and is an extension or loading from a file
+ if((thirdPartyCookiesMessageShown === Common.INVALID_LOCAL_STORAGE_ITEM || thirdPartyCookiesMessageShown !== Application.THIRD_PARTY_COOKIES_MESSAGE_SHOWN_TRUE_VALUE) && (Common.isExtension() === true || location["protocol"] === Common.FILE_PROTOCOL)) {
+
+ // Check if browser is Safari
+ if(typeof navigator === "object" && navigator !== null && "userAgent" in navigator === true && navigator["userAgent"].toLowerCase().indexOf("safari") !== Common.INDEX_NOT_FOUND && navigator["userAgent"].toLowerCase().indexOf("chrome") === Common.INDEX_NOT_FOUND) {
+
+ // Initialize message button clicked
+ var messageButtonClicked = false;
+
+ // Window storage application event
+ $(window).on("storage.application", function(event) {
+
+ // Check if third-party cookies message shown was changed
+ if(event["originalEvent"]["key"] === Application.THIRD_PARTY_COOKIES_MESSAGE_SHOWN_LOCAL_STORAGE_NAME) {
+
+ // Turn off window storage application event
+ $(window).off("storage.application");
+
+ // Set timeout
+ setTimeout(function() {
+
+ // Check if message button wasn't cicked
+ if(messageButtonClicked === false) {
+
+ // Show loading
+ self.showLoading();
+
+ // Prevent showing messages
+ self.message.prevent();
+
+ // Hide message
+ self.message.hide().then(function() {
+
+ // Resolve
+ resolve();
+ });
+ }
+
+ }, Application.HIDE_THIRD_PARTY_COOKIES_MESSAGE_DELAY_MILLISECONDS);
+ }
+ });
+
+ // Check if is an extesnion
+ if(Common.isExtension() === true) {
+
+ // Set message
+ var message = Message.createText(Language.getDefaultTranslation('This extension won\'t function correctly if your browser is configured to block third-party cookies. Make sure that your browser is configured to allow third-party cookies before continuing.'));
+ }
+
+ // Otherwise
+ else {
+
+ // Set message
+ var message = Message.createText(Language.getDefaultTranslation('This site won\'t function correctly if your browser is configured to block third-party cookies. Make sure that your browser is configured to allow third-party cookies before continuing.'));
+ }
+
+ // Show message and allow showing messages
+ self.message.show(Language.getDefaultTranslation('Third-Party Cookies Information'), message, false, function() {
+
+ // Hide loading
+ self.hideLoading();
+
+ // Show language display
+ Language.showDisplay();
+
+ }, Language.getDefaultTranslation('OK'), Message.NO_BUTTON, true).then(function(messageResult) {
+
+ // Turn off window storage application event
+ $(window).off("storage.application");
+
+ // Check if message was displayed
+ if(messageResult !== Message.NOT_DISPLAYED_RESULT) {
+
+ // Set message button clicked
+ messageButtonClicked = true;
+
+ // Try
+ try {
+
+ // Save that third-party cookies message has been shown
+ localStorage.setItem(Application.THIRD_PARTY_COOKIES_MESSAGE_SHOWN_LOCAL_STORAGE_NAME, Application.THIRD_PARTY_COOKIES_MESSAGE_SHOWN_TRUE_VALUE);
+ }
+
+ // Catch errors
+ catch(error) {
+
+ // Trigger a fatal error
+ new FatalError(FatalError.LOCAL_STORAGE_ERROR);
+
+ // Return
+ return;
+ }
+
+ // Show loading
+ self.showLoading();
+
+ // Prevent showing messages
+ self.message.prevent();
+
+ // Hide message
+ self.message.hide().then(function() {
+
+ // Resolve
+ resolve();
+ });
+ }
+ });
+ }
+
+ // Otherwise
+ else {
+
+ // Resolve
+ resolve();
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Resolve
+ resolve();
+ }
+ });
+ }
+
+ // Initialize dependencies
+ initializeDependencies() {
+
+ // Set self
+ var self = this;
+
+ // Return promise
+ return new Promise(function(resolve, reject) {
+
+ // Return performing dependencies initializations
+ return Promise.all(Application.DEPENDENCIES_INITIALIZATIONS.map(function(dependencyInitialization) {
+
+ // Return performing dependency initialization
+ return dependencyInitialization();
+
+ })).then(function() {
+
+ // Resolve
+ resolve();
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Check if is an extesnion
+ if(Common.isExtension() === true) {
+
+ // Set message
+ var message = Message.createText(Language.getDefaultTranslation('Failed to initialize dependencies.')) + Message.createText(Language.getDefaultTranslation('(?<=.) ')) + Message.createText(Language.getDefaultTranslation('Restart this extension to try again.'));
+ }
+
+ // Otherwise check if is an app
+ else if(Common.isApp() === true) {
+
+ // Set message
+ var message = Message.createText(Language.getDefaultTranslation('Failed to initialize dependencies.')) + Message.createText(Language.getDefaultTranslation('(?<=.) ')) + Message.createText(Language.getDefaultTranslation('Restart this app to try again.'));
+ }
+
+ // Otherwise
+ else {
+
+ // Set message
+ var message = Message.createText(Language.getDefaultTranslation('Failed to initialize dependencies.')) + Message.createText(Language.getDefaultTranslation('(?<=.) ')) + Message.createText(Language.getDefaultTranslation('Refresh this site to try again.'));
+ }
+
+ // Show message and allow showing messages
+ self.message.show(Language.getDefaultTranslation('Error'), message, false, function() {
+
+ // Hide loading
+ self.hideLoading();
+
+ // Show language display
+ Language.showDisplay();
+
+ // Hide loading display spinner
+ self.loadingDisplay.children("div.spinner").addClass("hide");
+
+ }, Message.NO_BUTTON, Message.NO_BUTTON, true);
+ });
+ });
+ }
+
+ // Initialize extension
+ initializeExtension() {
+
+ // Set self
+ var self = this;
+
+ // Return promise
+ return new Promise(function(resolve, reject) {
+
+ // Return initializing extension
+ return Extension.initialize().then(function() {
+
+ // Check if closing when done processing extension requests
+ if(Extension.getCloseWhenDone() === true) {
+
+ // Set ignore updates
+ self.ignoreUpdates = true;
+ }
+
+ // Otherwise
+ else {
+
+ // Set that body display shows that it's using listener
+ self.bodyDisplay.addClass("usingListener");
+ }
+
+ // Resolve
+ resolve();
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Show message and allow showing messages
+ self.message.show(Language.getDefaultTranslation('Error'), Message.createText(error), false, function() {
+
+ // Hide loading
+ self.hideLoading();
+
+ // Show language display
+ Language.showDisplay();
+
+ // Hide loading display spinner
+ self.loadingDisplay.children("div.spinner").addClass("hide");
+
+ }, Language.getDefaultTranslation('OK'), Message.NO_BUTTON, true).then(function(messageResult) {
+
+ // Check if message was displayed
+ if(messageResult !== Message.NOT_DISPLAYED_RESULT) {
+
+ // Prevent extension from interrupting on close
+ Extension.preventInterruptOnClose();
+
+ // Close
+ window.close();
+ }
+ });
+ });
+ });
+ }
+
+ // Uninitialize dependencies
+ uninitializeDependencies() {
+
+ // Go through all dependencies uninitializations
+ for(var i = 0; i < Application.DEPENDENCIES_UNINITIALIZATIONS["length"]; ++i) {
+
+ // Get dependency's uninitialization
+ var dependencyUninitialization = Application.DEPENDENCIES_UNINITIALIZATIONS[i];
+
+ // Perform dependency's uninitialization
+ dependencyUninitialization();
+ }
+ }
+
+ // Show reset settings
+ showResetSettings() {
+
+ // Set self
+ var self = this;
+
+ // Return promise
+ return new Promise(function(resolve, reject) {
+
+ // Get URL parameters
+ var urlParameters = Common.getUrlParameters();
+
+ // Check if resetting settings and URL parameters don't contain a request
+ if(self.resetSettings === true && "Request" in urlParameters === false) {
+
+ // Show message and allow showing messages
+ self.message.show(Language.getDefaultTranslation('Reset Settings'), Message.createText(Language.getDefaultTranslation('Are you sure you want to reset the settings to their default values?')), false, function() {
+
+ // Hide loading
+ self.hideLoading();
+
+ // Show language display
+ Language.showDisplay();
+
+ }, Language.getDefaultTranslation('No'), Language.getDefaultTranslation('Yes'), true).then(function(messageResult) {
+
+ // Check if message was displayed
+ if(messageResult !== Message.NOT_DISPLAYED_RESULT) {
+
+ // Check if resetting settings
+ if(messageResult === Message.SECOND_BUTTON_CLICKED_RESULT) {
+
+ // Show loading
+ self.showLoading();
+
+ // Set that message second button is loading
+ self.message.setButtonLoading(Message.SECOND_BUTTON);
+
+ // Prevent showing messages
+ self.message.prevent();
+
+ // Set timeout
+ setTimeout(function() {
+
+ // Once database is initialized
+ Database.onceInitialized(function() {
+
+ // Return promise
+ return new Promise(function(resolve, reject) {
+
+ // Return deleting settings
+ return self.settings.deleteValues(false).then(function() {
+
+ // Return hiding message
+ return self.message.hide().then(function() {
+
+ setTimeout(function() {
+
+ // Resolve
+ resolve();
+
+ }, Application.RESET_SETTINGS_AFTER_DELAY_MILLISECONDS);
+ });
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Return showing message and allow showing messages
+ return self.message.show(Language.getDefaultTranslation('Reset Settings Error'), Message.createText(error), true, function() {
+
+ // Hide loading
+ self.hideLoading();
+
+ }, Language.getDefaultTranslation('OK'), Message.NO_BUTTON, true).then(function(messageResult) {
+
+ // Check if message was displayed
+ if(messageResult !== Message.NOT_DISPLAYED_RESULT) {
+
+ // Show loading
+ self.showLoading();
+
+ // Prevent showing messages
+ self.message.prevent();
+
+ // Return hiding message
+ return self.message.hide().then(function() {
+
+ // Resolve
+ resolve();
+ });
+ }
+ });
+ });
+ });
+ }, true);
+
+ // Resolve
+ resolve();
+
+ }, Application.RESET_SETTINGS_BEFORE_DELAY_MILLISECONDS);
+ }
+
+ // Otherwise
+ else {
+
+ // Show loading
+ self.showLoading();
+
+ // Prevent showing messages
+ self.message.prevent();
+
+ // Hide message
+ self.message.hide().then(function() {
+
+ // Resolve
+ resolve();
+ });
+ }
+ }
+ });
+ }
+
+ // Otherwise
+ else {
+
+ // Resolve
+ resolve();
+ }
+ });
+ }
+
+ // Is primary instance
+ isPrimaryInstance() {
+
+ // Set self
+ var self = this;
+
+ // Return promise
+ return new Promise(function(resolve, reject) {
+
+ // Try
+ try {
+
+ // Create instance
+ var instance = new Instance();
+ }
+
+ // Catch errors
+ catch(error) {
+
+ // Check if is an extesnion
+ if(Common.isExtension() === true) {
+
+ // Set message
+ var message = Message.createText(Language.getDefaultTranslation('Failed to determine the primary instance.')) + Message.createText(Language.getDefaultTranslation('(?<=.) ')) + Message.createText(Language.getDefaultTranslation('Restart this extension to try again.'));
+ }
+
+ // Otherwise check if is an app
+ else if(Common.isApp() === true) {
+
+ // Set message
+ var message = Message.createText(Language.getDefaultTranslation('Failed to determine the primary instance.')) + Message.createText(Language.getDefaultTranslation('(?<=.) ')) + Message.createText(Language.getDefaultTranslation('Restart this app to try again.'));
+ }
+
+ // Otherwise
+ else {
+
+ // Set message
+ var message = Message.createText(Language.getDefaultTranslation('Failed to determine the primary instance.')) + Message.createText(Language.getDefaultTranslation('(?<=.) ')) + Message.createText(Language.getDefaultTranslation('Refresh this site to try again.'));
+ }
+
+ // Show message and allow showing messages
+ self.message.show(Language.getDefaultTranslation('Error'), message, false, function() {
+
+ // Hide loading
+ self.hideLoading();
+
+ // Show language display
+ Language.showDisplay();
+
+ // Hide loading display spinner
+ self.loadingDisplay.children("div.spinner").addClass("hide");
+
+ }, Message.NO_BUTTON, Message.NO_BUTTON, true);
+
+ // Return
+ return;
+ }
+
+ // Return getting if instance is the primary instance
+ return instance.isPrimaryInstance().then(function(isPrimaryInstance) {
+
+ // Check if instance is the primary instance
+ if(isPrimaryInstance === true)
+
+ // Resolve
+ resolve();
+
+ // Otherwise
+ else {
+
+ // Check if is an extension
+ if(Common.isExtension() === true) {
+
+ // Set message
+ var message = Language.getDefaultTranslation('Only one instance of this extension can be open at once. Close all other instances to continue.');
+ }
+
+ // Otherwise check if is an app
+ else if(Common.isApp() === true) {
+
+ // Set message
+ var message = Language.getDefaultTranslation('Only one instance of this app can be open at once. Close all other instances to continue.');
+ }
+
+ // Otherwise
+ else {
+
+ // Set message
+ var message = Language.getDefaultTranslation('Only one instance of this site can be open at once. Close all other instances to continue.');
+ }
+
+ // Show message and allow showing messages
+ self.message.show(Language.getDefaultTranslation('Error'), Message.createText(message), false, function() {
+
+ // Hide loading
+ self.hideLoading();
+
+ // Show language display
+ Language.showDisplay();
+
+ }, Message.NO_BUTTON, Message.NO_BUTTON, true);
+
+ // Instance is primary instance event
+ $(instance).one(Instance.IS_PRIMARY_INSTANCE_EVENT, function() {
+
+ // Set timeout
+ setTimeout(function() {
+
+ // Show loading
+ self.showLoading();
+
+ // Prevent showing messages
+ self.message.prevent();
+
+ // Hide message
+ self.message.hide().then(function() {
+
+ // Resolve
+ resolve();
+ });
+
+ }, Application.HIDE_PRIMARY_INSTANCE_MESSAGE_DELAY_MILLISECONDS);
+ });
+ }
+ });
+ });
+ }
+
+ // Set verify source
+ setVerifySource() {
+
+ // Check if is an extension
+ if(Common.isExtension() === true) {
+
+ // Check if using the Firefox extension
+ if(typeof browser !== "undefined" && browser["runtime"]["id"] === "{d783f67c-4dea-4d64-bfc2-1d4a6057babe}") {
+
+ // Set message
+ var message = Language.getDefaultTranslation('Make sure that you installed this extension for free from MWC Wallet\'s official browser extension listing on the Firefox Add-ons site at %1$m');
+
+ // Set message arguments
+ var messageArguments = [
+
+ [
+ // Text
+ "https://addons.mozilla.org/en-US/firefox/addon/mwc-wallet/",
+
+ // URL
+ "https://addons.mozilla.org/en-US/firefox/addon/mwc-wallet/",
+
+ // Is external
+ true,
+
+ // Is blob
+ false
+ ]
+ ];
+ }
+
+ // Otherwise check if using the Chrome extension
+ else if(typeof chrome !== "undefined" && chrome["runtime"]["id"] === "ahhdnimkkpkmclgcnbchlgijhmieongp") {
+
+ // Set message
+ var message = Language.getDefaultTranslation('Make sure that you installed this extension for free from MWC Wallet\'s official browser extension listing on the Chrome Web Store at %1$m');
+
+ // Set message arguments
+ var messageArguments = [
+
+ [
+ // Text
+ "https://chromewebstore.google.com/detail/mwc-wallet/ahhdnimkkpkmclgcnbchlgijhmieongp",
+
+ // URL
+ "https://chromewebstore.google.com/detail/mwc-wallet/ahhdnimkkpkmclgcnbchlgijhmieongp",
+
+ // Is external
+ true,
+
+ // Is blob
+ false
+ ]
+ ];
+ }
+
+ // Otherwise
+ else {
+
+ // Set message
+ var message = Language.getDefaultTranslation('Make sure that you downloaded this extension for free from MWC Wallet\'s official browser extension releases at %1$m');
+
+ // Set message arguments
+ var messageArguments = [
+
+ [
+ // Text
+ "https://github.com/NicolasFlamel1/MWC-Wallet-Browser-Extension/releases",
+
+ // URL
+ "https://github.com/NicolasFlamel1/MWC-Wallet-Browser-Extension/releases",
+
+ // Is external
+ true,
+
+ // Is blob
+ false
+ ]
+ ];
+ }
+ }
+
+ // Otherwise check if is an app
+ else if(Common.isApp() === true) {
+
+ // Set message
+ var message = Language.getDefaultTranslation('Make sure that you installed this app for free from MWC Wallet\'s official site at %1$m');
+
+ // Set message arguments
+ var messageArguments = [
+
+ [
+ // Text
+ "https://mwcwallet.com",
+
+ // URL
+ "https://mwcwallet.com",
+
+ // Is external
+ true,
+
+ // Is blob
+ false
+ ]
+ ];
+ }
+
+ // Otherwise check if loading from a file
+ else if(location["protocol"] === Common.FILE_PROTOCOL) {
+
+ // Set message
+ var message = Language.getDefaultTranslation('Make sure that you downloaded this file for free from MWC Wallet\'s official standalone releases at %1$m');
+
+ // Set message arguments
+ var messageArguments = [
+
+ [
+ // Text
+ "https://github.com/NicolasFlamel1/MWC-Wallet-Standalone/releases",
+
+ // URL
+ "https://github.com/NicolasFlamel1/MWC-Wallet-Standalone/releases",
+
+ // Is external
+ true,
+
+ // Is blob
+ false
+ ]
+ ];
+ }
+
+ // Otherwise
+ else {
+
+ // Set message
+ var message = Language.getDefaultTranslation('Make sure that you\'re accessing MWC Wallet for free from its official site at %1$m');
+
+ // Set message arguments
+ var messageArguments = [
+
+ [
+ // Text
+ (Tor.isOnionService() === true) ? "http://mwcwalletmiq3gdkmfbqlytxunvlxyli4m6zrqozk7xjc353ewqb6bad.onion" : "https://mwcwallet.com",
+
+ // URL
+ (Tor.isOnionService() === true) ? "http://mwcwalletmiq3gdkmfbqlytxunvlxyli4m6zrqozk7xjc353ewqb6bad.onion" : "https://mwcwallet.com",
+
+ // Is external
+ true,
+
+ // Is blob
+ false
+ ]
+ ];
+ }
+
+ // Create verify source from message
+ var verifySource = Language.createTranslatableContainer("", message, messageArguments, "verifySource");
+
+ // Set create display's verify source
+ this.createDisplay.find("p.verifySource").replaceWith(verifySource);
+
+ // Set unlock display's verify source
+ this.unlockDisplay.find("p.verifySource").replaceWith(verifySource);
+ }
+
+ // Show create or unlock
+ showCreateOrUnlock() {
+
+ // Check if wallets don't exist
+ if(this.wallets.exist() === false)
+
+ // Set display to show to create display
+ var displayToShow = this.createDisplay;
+
+ // Otherwise
+ else
+
+ // Set display to show to unlock display
+ var displayToShow = this.unlockDisplay;
+
+ // Hide loading display spinner
+ this.loadingDisplay.children("div.spinner").addClass("hide");
+
+ // Allow showing logo
+ this.logo.allowShowing();
+
+ // Show logo
+ this.logo.show();
+
+ // Set self
+ var self = this;
+
+ // Loading display spinner transition end or timeout event
+ this.loadingDisplay.children("div.spinner").transitionEndOrTimeout(function() {
+
+ // Hide loading display
+ self.loadingDisplay.addClass("hide");
+
+ // Hide loading
+ self.hideLoading();
+
+ // Disable tabbing to everything in display to show and disable everything in display to show
+ displayToShow.find("*").disableTab().disable();
+
+ // Show display to show
+ displayToShow.removeClass("hide");
+
+ // Set timeout
+ setTimeout(function() {
+
+ // Trigger input on display to show's inputs
+ displayToShow.find("input:not(.hide)").trigger("input");
+
+ // Show display to show children
+ displayToShow.children().removeClass("hide");
+
+ // Show language display
+ Language.showDisplay(true);
+
+ // Set info display
+ self.infoDisplay.append(Language.createTranslatableContainer("
", Language.getDefaultTranslation('%1$x/%2$x/v%3$v'), [Consensus.walletTypeToText(Consensus.getWalletType()), Consensus.networkTypeToText(Consensus.getNetworkType()), VERSION_NUMBER]));
+
+ // Show info display
+ self.infoDisplay.removeClass("hide");
+
+ // Initialize focus on input
+ var focusOnInput = true;
+
+ // Check if cookie acceptance was shown
+ if(self.cookieAcceptance.show() === true) {
+
+ // Clear focus on input
+ focusOnInput = false;
+ }
+
+ // Show maintenance notification
+ self.maintenanceNotification.show();
+
+ // Display to show form transition end or timeout event
+ displayToShow.children("form").transitionEndOrTimeout(function() {
+
+ // Set everything to transition at normal speed
+ self.mainDisplay.addClass("normalTransitionSpeed");
+
+ // Enable tabbing to everything in display to show and enable everything in display to show
+ displayToShow.find("*").enableTab().enable();
+
+ // Check if focusing on input
+ if(focusOnInput === true) {
+
+ // Get focus to display's first input
+ var firstInput = displayToShow.find("input:visible").first();
+
+ // Try
+ try {
+
+ // Check if first input isn't autofilled in
+ if(firstInput.is(":-webkit-autofill") === false && firstInput.is(":autofill") === false) {
+
+ // Focus on first input
+ firstInput.focus();
+ }
+ }
+
+ // Catch errors
+ catch(error) {
+
+ // Focus on first input
+ firstInput.focus();
+ }
+ }
+
+ // Allow showing messages
+ self.message.allow();
+
+ }, "opacity");
+ }, 0);
+
+ }, "opacity");
+ }
+
+ // Show display
+ showDisplay(displayToShow) {
+
+ // Delete all saved focus
+ this.focus.deleteAll();
+
+ // Allow scroll keys
+ this.scroll.allowKeys();
+
+ // Disable unlocked
+ this.unlocked.disable();
+
+ // Prevent automatic lock
+ this.automaticLock.prevent();
+
+ // Lock wallets
+ this.wallets.lock(this.unlockedAtLeastOnce === false);
+
+ // Check if message is shown
+ if(this.message.isShown() === true) {
+
+ // Check display to show
+ switch(displayToShow) {
+
+ // Create display
+ case this.createDisplay:
+
+ // Set hide message to if message visible state doesn't include create
+ var hideMessage = (this.message.visibleState() & Message.VISIBLE_STATE_CREATE) === 0;
+
+ // Break
+ break;
+
+ // Unlock display
+ case this.unlockDisplay:
+
+ // Set hide message to if message visible state doesn't include unlock
+ var hideMessage = (this.message.visibleState() & Message.VISIBLE_STATE_UNLOCK) === 0;
+
+ // Break
+ break;
+ }
+
+ // Check if hiding message
+ if(hideMessage === true) {
+
+ // Hide message
+ this.message.hide();
+ }
+ }
+
+ // Set self
+ var self = this;
+
+ // Message show application show display event
+ $(this.message).on(Message.SHOW_EVENT + ".applicationShowDisplay", function() {
+
+ // Check display to show
+ switch(displayToShow) {
+
+ // Create display
+ case self.createDisplay:
+
+ // Set hide message to if message visible state doesn't include create
+ var hideMessage = (self.message.visibleState() & Message.VISIBLE_STATE_CREATE) === 0;
+
+ // Break
+ break;
+
+ // Unlock display
+ case self.unlockDisplay:
+
+ // Set hide message to if message visible state doesn't include unlock
+ var hideMessage = (self.message.visibleState() & Message.VISIBLE_STATE_UNLOCK) === 0;
+
+ // Break
+ break;
+ }
+
+ // Check if hiding message
+ if(hideMessage === true) {
+
+ // Hide message
+ self.message.hide();
+ }
+
+ // Message before show application event
+ }).on(Message.BEFORE_SHOW_EVENT + ".application", function() {
+
+ // Check display to show
+ switch(displayToShow) {
+
+ // Create display
+ case self.createDisplay:
+
+ // Set hide message to if message visible state doesn't include create
+ var hideMessage = (self.message.visibleState() & Message.VISIBLE_STATE_CREATE) === 0;
+
+ // Break
+ break;
+
+ // Unlock display
+ case self.unlockDisplay:
+
+ // Set hide message to if message visible state doesn't include unlock
+ var hideMessage = (self.message.visibleState() & Message.VISIBLE_STATE_UNLOCK) === 0;
+
+ // Break
+ break;
+ }
+
+ // Check if hiding message
+ if(hideMessage === true) {
+
+ // Return false to cancel the message
+ return false;
+ }
+ });
+
+ // Hide unlocked display children
+ this.unlockedDisplay.children().addClass("hide");
+
+ // Hide display to show display logo
+ displayToShow.children("div.logo").addClass("hide");
+
+ // Unlocked display children transition end or timeout event
+ this.unlockedDisplay.children().transitionEndOrTimeout(function() {
+
+ // Hide unlocked display
+ self.unlockedDisplay.addClass("hide");
+
+ // Hide loading
+ self.hideLoading();
+
+ // Enable unlocked
+ self.unlocked.enable();
+
+ // Reset unlocked
+ self.unlocked.reset();
+
+ // Disable tabbing to everything in display to show and disable everything in display to show
+ displayToShow.find("*").disableTab().disable();
+
+ // Show display to show
+ displayToShow.removeClass("hide");
+
+ // Set timeout
+ setTimeout(function() {
+
+ // Turn off message show application show display event
+ $(self.message).off(Message.SHOW_EVENT + ".applicationShowDisplay");
+
+ // Turn off message before show application event
+ $(self.message).off(Message.BEFORE_SHOW_EVENT + ".application");
+
+ // Show display to show children
+ displayToShow.children().removeClass("hide");
+
+ // Show logo
+ self.logo.show();
+
+ // Show info display
+ self.infoDisplay.removeClass("hide");
+
+ // Check if message is not shown
+ if(self.message.isShown() === false) {
+
+ // Enable tabbing to everything in display to show and enable everything in display to show
+ displayToShow.find("*").enableTab().enable();
+
+ // Focus on display to show
+ displayToShow.focus();
+ }
+
+ // Otherwise
+ else {
+
+ // Display to show form transition end or timeout event
+ displayToShow.children("form").transitionEndOrTimeout(function() {
+
+ // Check if message is not shown
+ if(self.message.isShown() === false) {
+
+ // Enable tabbing to everything in display to show and enable everything in display to show
+ displayToShow.find("*").enableTab().enable();
+
+ // Focus on display to show
+ displayToShow.focus();
+ }
+ }, "opacity");
+ }
+
+ // Allow showing messages
+ self.message.allow();
+ }, 0);
+
+ }, "opacity");
+ }
+
+ // Reset
+ reset() {
+
+ // Set title
+ var title = Language.getDefaultTranslation('Show');
+
+ // Show create display password show icon, set its title, and change password input type
+ this.createDisplay.find("input[name=\"Password\"]").siblings("span.show").removeClass("conceal").attr(Common.DATA_ATTRIBUTE_PREFIX + "text", title).attr("title", Language.getTranslation(title)).siblings("input").attr("type", "password");
+
+ // Show create display confirm password show icon, set its title, and change password input type
+ this.createDisplay.find("input[name=\"Confirm Password\"]").siblings("span.show").removeClass("conceal").attr(Common.DATA_ATTRIBUTE_PREFIX + "text", title).attr("title", Language.getTranslation(title)).siblings("input").attr("type", "password");
+
+ // Show unlock display password show icon, set its title, and change password input type
+ this.unlockDisplay.find("input[name=\"Password\"]").siblings("span.show").removeClass("conceal").attr(Common.DATA_ATTRIBUTE_PREFIX + "text", title).attr("title", Language.getTranslation(title)).siblings("input").attr("type", "password");
+ }
+
+ // Private browsing message shown local storage name
+ static get PRIVATE_BROWSING_MESSAGE_SHOWN_LOCAL_STORAGE_NAME() {
+
+ // Return private browsing message shown local storage name
+ return "Private Browsing Message Shown";
+ }
+
+ // Private browsing message shown true value
+ static get PRIVATE_BROWSING_MESSAGE_SHOWN_TRUE_VALUE() {
+
+ // Return private browsing message shown true value
+ return "true";
+ }
+
+ // Third-party cookies message shown local storage name
+ static get THIRD_PARTY_COOKIES_MESSAGE_SHOWN_LOCAL_STORAGE_NAME() {
+
+ // Return third-party cookies message shown local storage name
+ return "Third-Party Cookies Message Shown";
+ }
+
+ // Third-party cookies message shown true value
+ static get THIRD_PARTY_COOKIES_MESSAGE_SHOWN_TRUE_VALUE() {
+
+ // Return Third-party cookies message shown true value
+ return "true";
+ }
+
+ // Show install app minimum delay seconds
+ static get SHOW_INSTALL_APP_MINIMUM_DELAY_SECONDS() {
+
+ // Return show install app minimum delay seconds
+ return 1 * Common.SECONDS_IN_A_MINUTE;
+ }
+
+ // Show install app maximum delay seconds
+ static get SHOW_INSTALL_APP_MAXIMUM_DELAY_SECONDS() {
+
+ // Return show install app maximum delay seconds
+ return 5 * Common.SECONDS_IN_A_MINUTE;
+ }
+
+ // Show unlocked display delay milliseconds
+ static get SHOW_UNLOCKED_DISPLAY_DELAY_MILLISECONDS() {
+
+ // Return show unlocked display delay milliseconds
+ return 300;
+ }
+
+ // Delete all wallets delay milliseconds
+ static get DELETE_ALL_WALLETS_DELAY_MILLISECONDS() {
+
+ // Return delete all wallets delay milliseconds
+ return 300;
+ }
+
+ // Hide primary instance message delay milliseconds
+ static get HIDE_PRIMARY_INSTANCE_MESSAGE_DELAY_MILLISECONDS() {
+
+ // Return hide primary instance message delay milliseconds
+ return 100;
+ }
+
+ // Hide private browsing message delay milliseconds
+ static get HIDE_PRIVATE_BROWSING_MESSAGE_DELAY_MILLISECONDS() {
+
+ // Return hide private browsing message delay milliseconds
+ return Application.HIDE_PRIMARY_INSTANCE_MESSAGE_DELAY_MILLISECONDS;
+ }
+
+ // Hide third-party cookies message delay milliseconds
+ static get HIDE_THIRD_PARTY_COOKIES_MESSAGE_DELAY_MILLISECONDS() {
+
+ // Return hide third-party cookies message delay milliseconds
+ return Application.HIDE_PRIMARY_INSTANCE_MESSAGE_DELAY_MILLISECONDS;
+ }
+
+ // Reset settings before delay milliseconds
+ static get RESET_SETTINGS_BEFORE_DELAY_MILLISECONDS() {
+
+ // Return reset settings before delay milliseconds
+ return 500;
+ }
+
+ // Reset settings after delay milliseconds
+ static get RESET_SETTINGS_AFTER_DELAY_MILLISECONDS() {
+
+ // Return reset settings after delay milliseconds
+ return 500;
+ }
+
+ // Install update delay milliseconds
+ static get INSTALL_UPDATE_DELAY_MILLISECONDS() {
+
+ // Return install update delay milliseconds
+ return 1 * Common.MILLISECONDS_IN_A_SECOND;
+ }
+
+ // Delete all wallets button maximum width
+ static get DELETE_ALL_WALLETS_BUTTON_MAXIMUM_WIDTH() {
+
+ // Return delete all wallets button maximum width
+ return parseFloat("10em");
+ }
+
+ // Dependencies initializations
+ static get DEPENDENCIES_INITIALIZATIONS() {
+
+ // Return dependencies initializations
+ return [
+
+ // BLAKE2b initialize
+ Blake2b.initialize,
+
+ // Ed25519 initialize
+ Ed25519.initialize,
+
+ // X25519 initialize
+ X25519.initialize,
+
+ // Secp256k1-zkp initialize
+ Secp256k1Zkp.initialize,
+
+ // SMAZ initialize
+ Smaz.initialize,
+
+ // Database initialize
+ Database.initialize,
+
+ // Output initialize
+ Output.initialize,
+
+ // Slate initialize
+ Slate.initialize,
+
+ // Wallet initialize
+ Wallet.initialize,
+
+ // Tor initialize
+ Tor.initialize,
+
+ // Camera initialize
+ Camera.initialize
+ ];
+ }
+
+ // Dependencies uninitializations
+ static get DEPENDENCIES_UNINITIALIZATIONS() {
+
+ // Return dependencies uninitializations
+ return [
+
+ // Secp256k1-zkp uninitialize
+ Secp256k1Zkp.uninitialize
+ ];
+ }
+
+ // Settings enable node connection error messages name
+ static get SETTINGS_ENABLE_NODE_CONNECTION_ERROR_MESSAGES_NAME() {
+
+ // Return settings enable node connection error messages name
+ return "Enable Node Connection Error Messages";
+ }
+
+ // Settings enable node connection error messages default value
+ static get SETTINGS_ENABLE_NODE_CONNECTION_ERROR_MESSAGES_DEFAULT_VALUE() {
+
+ // Return settings enable node connection error messages default value
+ return true;
+ }
+
+ // Settings enable listener connection error messages name
+ static get SETTINGS_ENABLE_LISTENER_CONNECTION_ERROR_MESSAGES_NAME() {
+
+ // Return settings enable listener connection error messages name
+ return "Enable Listener Connection Error Messages";
+ }
+
+ // Settings enable listener connection error messages default value
+ static get SETTINGS_ENABLE_LISTENER_CONNECTION_ERROR_MESSAGES_DEFAULT_VALUE() {
+
+ // Return settings enable listener connection error messages default value
+ return true;
+ }
+
+ // Shift key code
+ static get SHIFT_KEY_CODE() {
+
+ // Return shift key code
+ return 16;
+ }
+
+ // Prevent minimal interface delay milliseconds
+ static get PREVENT_MINIMAL_INTERFACE_DELAY_MILLISECONDS() {
+
+ // Return prevent minimal interface delay milliseconds
+ return 500;
+ }
+
+ // Canceled check interval milliseconds
+ static get CANCELED_CHECK_INTERVAL_MILLISECONDS() {
+
+ // Return canceled check interval milliseconds
+ return 50;
+ }
+
+ // Check extension request received interval milliseconds
+ static get CHECK_EXTENSION_REQUEST_RECEIVED_INTERVAL_MILLISECONDS() {
+
+ // Return check extension request received interval milliseconds
+ return 50;
+ }
+
+ // Check hardware wallet priority interval milliseconds
+ static get CHECK_HARDWARE_WALLET_PRIORITY_INTERVAL_MILLISECONDS() {
+
+ // Return check hardware wallet priority interval milliseconds
+ return 50;
+ }
+}
+
+
+// Main function
+
+// Set global object's application
+globalThis["Application"] = Application;
+
+// Ready event
+$(function() {
+
+ // Set timeout
+ setTimeout(function() {
+
+ // Check if a startup error didn't occur
+ if(startupErrorOccurred() === false) {
+
+ // Enable application error handler
+ enableApplicationErrorHandler();
+
+ // Create application
+ new Application();
+ }
+
+ }, Application.SHOW_LOADING_DELAY_MILLISECONDS);
+});
diff --git a/scripts/automatic_lock.js b/scripts/automatic_lock.js
new file mode 100755
index 0000000..cf7a8ca
--- /dev/null
+++ b/scripts/automatic_lock.js
@@ -0,0 +1,539 @@
+// Use strict
+"use strict";
+
+
+// Classes
+
+// Automatic lock class
+class AutomaticLock {
+
+ // Public
+
+ // Constructor
+ constructor(application, message, settings) {
+
+ // Set application
+ this.application = application;
+
+ // Set message
+ this.message = message;
+
+ // Set settings
+ this.settings = settings;
+
+ // Set lock timeout to no timeout
+ this.lockTimeout = AutomaticLock.NO_TIMEOUT;
+
+ // Set allow no interaction lock to false
+ this.allowNoInteractionLock = false;
+
+ // Set allow inactive lock to false
+ this.allowInactiveLock = false;
+
+ // Set locking to false
+ this.locking = false;
+
+ // Set enable automatic lock to setting's default value
+ this.enableAutomaticLock = AutomaticLock.SETTINGS_ENABLE_AUTOMATIC_LOCK_DEFAULT_VALUE;
+
+ // Set no interaction lock timeout minutes to setting's default value
+ this.noInteractionLockTimeoutMinutes = AutomaticLock.SETTINGS_NO_INTERACTION_LOCK_TIMEOUT_MINUTES_DEFAULT_VALUE;
+
+ // Set lock on inactive to setting's default value
+ this.enableLockOnInactive = AutomaticLock.SETTINGS_ENABLE_LOCK_ON_INACTIVE_DEFAULT_VALUE;
+
+ // Update last interaction timestamp
+ this.updateLastInteractionTimestamp();
+
+ // Set self
+ var self = this;
+
+ // Once database is initialized
+ Database.onceInitialized(function() {
+
+ // Return promise
+ return new Promise(function(resolve, reject) {
+
+ // Return creating settings
+ return Promise.all([
+
+ // Create enable automatic lock setting
+ self.settings.createValue(AutomaticLock.SETTINGS_ENABLE_AUTOMATIC_LOCK_NAME, AutomaticLock.SETTINGS_ENABLE_AUTOMATIC_LOCK_DEFAULT_VALUE),
+
+ // Create no interaction lock timeout minutes setting
+ self.settings.createValue(AutomaticLock.SETTINGS_NO_INTERACTION_LOCK_TIMEOUT_MINUTES_NAME, AutomaticLock.SETTINGS_NO_INTERACTION_LOCK_TIMEOUT_MINUTES_DEFAULT_VALUE),
+
+ // Create enable lock on inactive setting
+ self.settings.createValue(AutomaticLock.SETTINGS_ENABLE_LOCK_ON_INACTIVE_NAME, AutomaticLock.SETTINGS_ENABLE_LOCK_ON_INACTIVE_DEFAULT_VALUE)
+
+ ]).then(function() {
+
+ // Initialize settings
+ var settings = [
+
+ // Enable automatic lock setting
+ AutomaticLock.SETTINGS_ENABLE_AUTOMATIC_LOCK_NAME,
+
+ // No interaction lock timeout minutes setting
+ AutomaticLock.SETTINGS_NO_INTERACTION_LOCK_TIMEOUT_MINUTES_NAME,
+
+ // Enable lock on inactive setting
+ AutomaticLock.SETTINGS_ENABLE_LOCK_ON_INACTIVE_NAME
+ ];
+
+ // Return getting settings' values
+ return Promise.all(settings.map(function(setting) {
+
+ // Return getting setting's value
+ return self.settings.getValue(setting);
+
+ })).then(function(settingValues) {
+
+ // Set enable automatic lock to setting's value
+ self.enableAutomaticLock = settingValues[settings.indexOf(AutomaticLock.SETTINGS_ENABLE_AUTOMATIC_LOCK_NAME)];
+
+ // Set no interaction lock timeout minutes to setting's value
+ self.noInteractionLockTimeoutMinutes = settingValues[settings.indexOf(AutomaticLock.SETTINGS_NO_INTERACTION_LOCK_TIMEOUT_MINUTES_NAME)];
+
+ // Set enable lock on inactive to setting's value
+ self.enableLockOnInactive = settingValues[settings.indexOf(AutomaticLock.SETTINGS_ENABLE_LOCK_ON_INACTIVE_NAME)];
+
+ // Update last interaction timestamp
+ self.updateLastInteractionTimestamp();
+
+ // Resolve
+ resolve();
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Reject
+ reject();
+ });
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Reject
+ reject();
+ });
+ });
+ });
+
+ // Settings change event
+ $(this.settings).on(Settings.CHANGE_EVENT, function(event, setting) {
+
+ // Check what setting was changes
+ switch(setting[Settings.DATABASE_SETTING_NAME]) {
+
+ // Enable automatic lock setting
+ case AutomaticLock.SETTINGS_ENABLE_AUTOMATIC_LOCK_NAME:
+
+ // Set enable automatic lock to setting's value
+ self.enableAutomaticLock = setting[Settings.DATABASE_VALUE_NAME];
+
+ // Update last interaction timestamp
+ self.updateLastInteractionTimestamp();
+
+ // Break
+ break;
+
+ // No interaction lock timeout minutes setting
+ case AutomaticLock.SETTINGS_NO_INTERACTION_LOCK_TIMEOUT_MINUTES_NAME:
+
+ // Set no interaction lock timeout minutes to setting's value
+ self.noInteractionLockTimeoutMinutes = setting[Settings.DATABASE_VALUE_NAME];
+
+ // Update last interaction timestamp
+ self.updateLastInteractionTimestamp();
+
+ // Break
+ break;
+
+ // Enable lock on inactive setting
+ case AutomaticLock.SETTINGS_ENABLE_LOCK_ON_INACTIVE_NAME:
+
+ // Set enable lock on inactive to setting's value
+ self.enableLockOnInactive = setting[Settings.DATABASE_VALUE_NAME];
+
+ // Break
+ break;
+ }
+ });
+
+ // Window blur event
+ $(window).on("blur", function() {
+
+ // Update last interaction timestamp
+ self.updateLastInteractionTimestamp();
+
+ // Check if lock on inactive is enabled and allowed
+ if(self.enableLockOnInactive === true && self.allowInactiveLock === true) {
+
+ // Reset lock timeout
+ self.resetLockTimeout();
+
+ // Check if application's unlocked display is shown
+ if(self.application.isUnlockedDisplayShown() === true) {
+
+ // Log message
+ Log.logMessage(Language.getDefaultTranslation('Inactive lock activated.'));
+ }
+
+ // Lock
+ self.lock();
+ }
+
+ // Window focus event
+ }).on("focus", function() {
+
+ // Check if no interaction lock timeout minutes exist
+ if(self.noInteractionLockTimeoutMinutes !== AutomaticLock.NO_NO_INTERACTION_LOCK_TIMEOUT_MINUTES) {
+
+ // Check if application hasn't been interacted with in too long
+ if(self.lastInteractionTimestamp <= Common.getCurrentTimestamp() - self.noInteractionLockTimeoutMinutes * Common.SECONDS_IN_A_MINUTE) {
+
+ // Reset lock timeout
+ self.resetLockTimeout();
+
+ // Check if automatic lock is enabled and allowed
+ if(self.enableAutomaticLock === true && self.allowNoInteractionLock === true) {
+
+ // Check if application's unlocked display is shown
+ if(self.application.isUnlockedDisplayShown() === true) {
+
+ // Log message
+ Log.logMessage(Language.getDefaultTranslation('Automatic lock activated.'));
+ }
+
+ // Lock
+ self.lock();
+ }
+ }
+ }
+
+ // Update last interaction timestamp
+ self.updateLastInteractionTimestamp();
+
+ // Window page hide event
+ }).on("pagehide", function() {
+
+ // Check if lock on inactive is enabled and allowed
+ if(self.enableLockOnInactive === true && self.allowInactiveLock === true) {
+
+ // Reset lock timeout
+ self.resetLockTimeout();
+
+ // Check if application's unlocked display is shown
+ if(self.application.isUnlockedDisplayShown() === true) {
+
+ // Log message
+ Log.logMessage(Language.getDefaultTranslation('Inactive lock activated.'));
+ }
+
+ // Lock
+ self.lock();
+ }
+ });
+
+ // Document mouse move, mouse down, mouse up, key down, key up, touch start, touch move, touch end, and focus change event
+ $(document).on("mousemove mousedown mouseup keydown keyup touchstart touchmove touchend" + Common.FOCUS_CHANGE_EVENT, function(event) {
+
+ // Check if has focus
+ if(document.hasFocus() === true)
+
+ // Update last interaction timestamp
+ self.updateLastInteractionTimestamp();
+
+ // Document visibility change event
+ }).on("visibilitychange", function() {
+
+ // Check if page is hidden
+ if(document["visibilityState"] === Common.VISIBILITY_STATE_HIDDEN) {
+
+ // Check if lock on inactive is enabled and allowed
+ if(self.enableLockOnInactive === true && self.allowInactiveLock === true) {
+
+ // Reset lock timeout
+ self.resetLockTimeout();
+
+ // Check if application's unlocked display is shown
+ if(self.application.isUnlockedDisplayShown() === true) {
+
+ // Log message
+ Log.logMessage(Language.getDefaultTranslation('Inactive lock activated.'));
+ }
+
+ // Lock
+ self.lock();
+ }
+ }
+ });
+ }
+
+ // Allow
+ allow(lockType = AutomaticLock.NO_INTERACTION_LOCK_TYPE | AutomaticLock.INACTIVE_LOCK_TYPE) {
+
+ // Check if allowing the no interaction lock
+ if((lockType & AutomaticLock.NO_INTERACTION_LOCK_TYPE) !== 0)
+
+ // Set allow no interaction lock
+ this.allowNoInteractionLock = true;
+
+ // Check if allowing the inactive lock
+ if((lockType & AutomaticLock.INACTIVE_LOCK_TYPE) !== 0)
+
+ // Set allow inactive lock
+ this.allowInactiveLock = true;
+
+ // Check if no interaction lock timeout minutes exist
+ if(this.noInteractionLockTimeoutMinutes !== AutomaticLock.NO_NO_INTERACTION_LOCK_TIMEOUT_MINUTES) {
+
+ // Check if application hasn't been interacted with in too long
+ if(this.lastInteractionTimestamp <= Common.getCurrentTimestamp() - this.noInteractionLockTimeoutMinutes * Common.SECONDS_IN_A_MINUTE) {
+
+ // Reset lock timeout
+ this.resetLockTimeout();
+
+ // Check if automatic lock is enabled and allowed
+ if(this.enableAutomaticLock === true && this.allowNoInteractionLock === true) {
+
+ // Check if application's unlocked display is shown
+ if(this.application.isUnlockedDisplayShown() === true) {
+
+ // Log message
+ Log.logMessage(Language.getDefaultTranslation('Automatic lock activated.'));
+ }
+
+ // Lock
+ this.lock();
+ }
+ }
+ }
+
+ // Check if lock on inactive is enabled and allowed and the document doesn't have focus or is hidden
+ if(this.enableLockOnInactive === true && this.allowInactiveLock === true && (document.hasFocus() === false || document["visibilityState"] === Common.VISIBILITY_STATE_HIDDEN)) {
+
+ // Reset lock timeout
+ this.resetLockTimeout();
+
+ // Check if application's unlocked display is shown
+ if(this.application.isUnlockedDisplayShown() === true) {
+
+ // Log message
+ Log.logMessage(Language.getDefaultTranslation('Inactive lock activated.'));
+ }
+
+ // Lock
+ this.lock();
+ }
+ }
+
+ // Get allowed
+ getAllowed() {
+
+ // Initialize result
+ var result = 0;
+
+ // Check if allow no interaction lock
+ if(this.allowNoInteractionLock === true) {
+
+ // Set no interaction lock type in the result
+ result |= AutomaticLock.NO_INTERACTION_LOCK_TYPE;
+ }
+
+ // Check if allow inactive lock
+ if(this.allowInactiveLock === true) {
+
+ // Set inactive lock type in the result
+ result |= AutomaticLock.INACTIVE_LOCK_TYPE;
+ }
+
+ // Return result
+ return result;
+ }
+
+ // Prevent
+ prevent(lockType = AutomaticLock.NO_INTERACTION_LOCK_TYPE | AutomaticLock.INACTIVE_LOCK_TYPE) {
+
+ // Check if preventing the no interaction lock
+ if((lockType & AutomaticLock.NO_INTERACTION_LOCK_TYPE) !== 0)
+
+ // Clear allow no interaction lock
+ this.allowNoInteractionLock = false;
+
+ // Check if preventing the inactive lock
+ if((lockType & AutomaticLock.INACTIVE_LOCK_TYPE) !== 0)
+
+ // Clear allow inactive lock
+ this.allowInactiveLock = false;
+ }
+
+ // Is locking
+ isLocking() {
+
+ // Return if locking
+ return this.locking === true;
+ }
+
+ // No interaction lock type
+ static get NO_INTERACTION_LOCK_TYPE() {
+
+ // Return no interaction lock type
+ return 1 << 0;
+ }
+
+ // Inactive lock type
+ static get INACTIVE_LOCK_TYPE() {
+
+ // Return inactive lock type
+ return 1 << 1;
+ }
+
+ // Private
+
+ // Update last interaction timestamp
+ updateLastInteractionTimestamp() {
+
+ // Set last interaction timestamp to current timestamp
+ this.lastInteractionTimestamp = Common.getCurrentTimestamp();
+
+ // Reset lock timeout
+ this.resetLockTimeout();
+ }
+
+ // Reset lock timeout
+ resetLockTimeout() {
+
+ // Check if lock timeout exists
+ if(this.lockTimeout !== AutomaticLock.NO_TIMEOUT) {
+
+ // Clear lock timeout
+ clearTimeout(this.lockTimeout);
+
+ // Set lock timeout to no timeout
+ this.lockTimeout = AutomaticLock.NO_TIMEOUT;
+ }
+
+ // Check if no interaction lock timeout minutes exist
+ if(this.noInteractionLockTimeoutMinutes !== AutomaticLock.NO_NO_INTERACTION_LOCK_TIMEOUT_MINUTES) {
+
+ // Set self
+ var self = this;
+
+ // Set lock timeout
+ this.lockTimeout = setTimeout(function() {
+
+ // Reset lock timeout
+ self.resetLockTimeout();
+
+ // Check if automatic lock is enabled and allowed
+ if(self.enableAutomaticLock === true && self.allowNoInteractionLock === true) {
+
+ // Check if application's unlocked display is shown
+ if(self.application.isUnlockedDisplayShown() === true) {
+
+ // Log message
+ Log.logMessage(Language.getDefaultTranslation('Automatic lock activated.'));
+ }
+
+ // Lock
+ self.lock();
+ }
+
+ }, this.noInteractionLockTimeoutMinutes * Common.SECONDS_IN_A_MINUTE * Common.MILLISECONDS_IN_A_SECOND);
+ }
+ }
+
+ // Lock
+ lock() {
+
+ // Check if application's unlocked display is shown, not already locking, and not currently showing a minimal display
+ if(this.application.isUnlockedDisplayShown() === true && this.isLocking() === false && $("div.unlocked").hasClass("minimal") === false) {
+
+ // Set locking
+ this.locking = true;
+
+ // Prevent showing messages
+ this.message.prevent();
+
+ // Show loading
+ this.application.showLoading();
+
+ // Show lock display
+ this.application.showLockDisplay();
+
+ // Set self
+ var self = this;
+
+ // Set timeout
+ setTimeout(function() {
+
+ // Clear locking
+ self.locking = false;
+ }, 0);
+ }
+ }
+
+ // No timeout
+ static get NO_TIMEOUT() {
+
+ // Return no timeout
+ return null;
+ }
+
+ // No no interaction lock timeout minutes
+ static get NO_NO_INTERACTION_LOCK_TIMEOUT_MINUTES() {
+
+ // Return no no interaction lock timeout minutes
+ return null;
+ }
+
+ // Settings enable automatic lock name
+ static get SETTINGS_ENABLE_AUTOMATIC_LOCK_NAME() {
+
+ // Return settings enable automatic lock name
+ return "Enable Automatic Lock";
+ }
+
+ // Settings enable automatic lock default value
+ static get SETTINGS_ENABLE_AUTOMATIC_LOCK_DEFAULT_VALUE() {
+
+ // Return settings enable automatic lock default value
+ return true;
+ }
+
+ // Settings no interaction lock timeout minutes name
+ static get SETTINGS_NO_INTERACTION_LOCK_TIMEOUT_MINUTES_NAME() {
+
+ // Return settings no interaction lock timeout minutes name
+ return "No Interaction Lock Timeout Minutes";
+ }
+
+ // Settings no interaction lock timeout minutes default value
+ static get SETTINGS_NO_INTERACTION_LOCK_TIMEOUT_MINUTES_DEFAULT_VALUE() {
+
+ // Return settings no interaction lock timeout minutes default value
+ return 5;
+ }
+
+ // Settings enable lock on inactive name
+ static get SETTINGS_ENABLE_LOCK_ON_INACTIVE_NAME() {
+
+ // Return settings enable lock on inactive name
+ return "Enable Lock On Inactive";
+ }
+
+ // Settings enable lock on inactive default value
+ static get SETTINGS_ENABLE_LOCK_ON_INACTIVE_DEFAULT_VALUE() {
+
+ // Return settings enable lock on inactive default value
+ return false;
+ }
+}
+
+
+// Main function
+
+// Set global object's automatic lock
+globalThis["AutomaticLock"] = AutomaticLock;
diff --git a/scripts/base58.js b/scripts/base58.js
new file mode 100755
index 0000000..756789c
--- /dev/null
+++ b/scripts/base58.js
@@ -0,0 +1,266 @@
+// Use strict
+"use strict";
+
+
+// Classes
+
+// Base58 class
+class Base58 {
+
+ // Public
+
+ // Encode
+ static encode(byteArray) {
+
+ // Go through all leading zeros in the byte array
+ var numberOfLeadingZeros = 0;
+ while(numberOfLeadingZeros < byteArray["length"] && byteArray[numberOfLeadingZeros] === 0) {
+
+ // Increment number of leading zeros
+ ++numberOfLeadingZeros;
+ }
+
+ // Get buffer size
+ var bufferSize = Math.ceil((byteArray["length"] - numberOfLeadingZeros) * Base58.BYTES_PER_LENGTH_IN_NEW_BASE);
+
+ // Create buffer
+ var buffer = (new Uint8Array(bufferSize)).fill(0);
+
+ // Go through all bytes in the byte array after the leading zeros
+ var length = 0;
+ for(var i = numberOfLeadingZeros; i < byteArray["length"]; ++i) {
+
+ // Get byte
+ var byte = byteArray[i];
+
+ // Go through all base 58 components of the byte
+ for(var j = 0, k = bufferSize - 1; (byte !== 0 || j < length) && k >= 0; ++j, --k) {
+
+ // Include the current buffer value in the byte
+ byte += (Common.BYTE_MAX_VALUE + 1) * buffer[k];
+
+ // Set value in the buffer
+ buffer[k] = byte % Base58.NUMBER_BASE;
+
+ // Update the byte
+ byte = Math.floor(byte / Base58.NUMBER_BASE);
+ }
+
+ // Update length
+ length = j;
+ }
+
+ // Go through all leading zeros in the buffer
+ var bufferIndex = bufferSize - length;
+ while(bufferIndex < buffer["length"] && buffer[bufferIndex] === 0) {
+
+ // Increment buffer index
+ ++bufferIndex;
+ }
+
+ // Set result to start with the number of leading zeros in base58
+ var result = Base58.CHARACTERS[0].repeat(numberOfLeadingZeros);
+
+ // Go through all bytes in the buffer after the leading zeros
+ for(; bufferIndex < buffer["length"]; ++bufferIndex) {
+
+ // Append buffer's value in base58 to the result
+ result += Base58.CHARACTERS[buffer[bufferIndex]];
+ }
+
+ // Return result
+ return result;
+ }
+
+ // Encode with checksum
+ static encodeWithChecksum(byteArray) {
+
+ // Get the checksum of the byte array
+ var checksum = Base58.getChecksum(byteArray);
+
+ // Return encoding the byte array with the checksum
+ return Base58.encode(Common.mergeArrays([
+
+ // Byte array
+ byteArray,
+
+ // Checksum
+ checksum
+ ]));
+ }
+
+ // Decode
+ static decode(base58String) {
+
+ // Go through all leading zeros in base58 in the string
+ var numberOfLeadingZeros = 0;
+ while(numberOfLeadingZeros < base58String["length"] && base58String[numberOfLeadingZeros] === Base58.CHARACTERS[0]) {
+
+ // Increment number of leading zeros
+ ++numberOfLeadingZeros;
+ }
+
+ // Get buffer size
+ var bufferSize = Math.ceil((base58String["length"] - numberOfLeadingZeros) * Base58.BYTES_PER_LENGTH_IN_OLD_BASE);
+
+ // Crete buffer
+ var buffer = (new Uint8Array(bufferSize)).fill(0);
+
+ // Go through all characters in the string after the leading zeros in base58
+ var length = 0;
+ for(var i = numberOfLeadingZeros; i < base58String["length"]; ++i) {
+
+ // Get character as byte
+ var byte = Base58.CHARACTERS.indexOf(base58String[i]);
+
+ // Check if byte is invalid
+ if(byte === Common.INDEX_NOT_FOUND) {
+
+ // Throw error
+ throw "Invalid base58 string.";
+ }
+
+ // Go through all base 58 components of the byte
+ for(var j = 0, k = bufferSize - 1; (byte !== 0 || j < length) && k >= 0; ++j, --k) {
+
+ // Include the current buffer value in the byte
+ byte += Base58.NUMBER_BASE * buffer[k];
+
+ // Set value in the buffer
+ buffer[k] = byte % (Common.BYTE_MAX_VALUE + 1);
+
+ // Update the byte
+ byte = Math.floor(byte / (Common.BYTE_MAX_VALUE + 1));
+ }
+
+ // Update length
+ length = j;
+ }
+
+ // Go through all leading zeros in the buffer
+ var bufferIndex = bufferSize - length;
+ while(bufferIndex < buffer["length"] && buffer[bufferIndex] === 0) {
+
+ // Increment buffer index
+ ++bufferIndex;
+ }
+
+ // Set result to start with the number of leading zeros
+ var result = (new Uint8Array(numberOfLeadingZeros + bufferSize - bufferIndex)).fill(0);
+
+ // Go through all bytes in the buffer after the leading zeros
+ for(var i = 0; bufferIndex < buffer["length"]; ++i, ++bufferIndex) {
+
+ // Append buffer's value to the result
+ result[i + numberOfLeadingZeros] += buffer[bufferIndex];
+ }
+
+ // Return result
+ return result;
+ }
+
+ // Decode with checksum
+ static decodeWithChecksum(base58String) {
+
+ // Try
+ try {
+
+ // Decode the string
+ var byteArray = Base58.decode(base58String);
+ }
+
+ // Catch errors
+ catch(error) {
+
+ // Throw error
+ throw error;
+ }
+
+ // Check if the byte array doesn't include a checksum
+ if(byteArray["length"] < Base58.CHECKSUM_LENGTH) {
+
+ // Throw error
+ throw "No checksum exists.";
+ }
+
+ // Otherwise
+ else {
+
+ // Get the checksum of the byte array without its checksum
+ var checksum = Base58.getChecksum(byteArray.subarray(0, byteArray["length"] - Base58.CHECKSUM_LENGTH));
+
+ // Get the provided checksum from the byte array
+ var providedChecksum = byteArray.subarray(byteArray["length"] - Base58.CHECKSUM_LENGTH);
+
+ // Check if checksums don't match
+ if(Common.arraysAreEqual(checksum, providedChecksum) === false) {
+
+ // Throw error
+ throw "Invalid checksum.";
+ }
+
+ // Otherwise
+ else {
+
+ // Return byte array without the checksum
+ return byteArray.subarray(0, byteArray["length"] - Base58.CHECKSUM_LENGTH);
+ }
+ }
+ }
+
+ // Private
+
+ // Get checksum
+ static getChecksum(byteArray) {
+
+ // Get a hash of the hash of the byte array
+ var hash = new Uint8Array(sha256.arrayBuffer(new Uint8Array(sha256.arrayBuffer(byteArray))));
+
+ // Get the checksum from the hash
+ var checksum = hash.subarray(0, Base58.CHECKSUM_LENGTH);
+
+ // Return checksum
+ return checksum;
+ }
+
+ // Characters
+ static get CHARACTERS() {
+
+ // Return characters
+ return "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
+ }
+
+ // Number base
+ static get NUMBER_BASE() {
+
+ // Return number base
+ return 58;
+ }
+
+ // Bytes per length in new base
+ static get BYTES_PER_LENGTH_IN_NEW_BASE() {
+
+ // Return bytes per length in new base
+ return Math.log(Common.BYTE_MAX_VALUE + 1) / Math.log(Base58.NUMBER_BASE);
+ }
+
+ // Bytes per length in old base
+ static get BYTES_PER_LENGTH_IN_OLD_BASE() {
+
+ // Return bytes per length in old base
+ return Math.log(Base58.NUMBER_BASE) / Math.log(Common.BYTE_MAX_VALUE + 1);
+ }
+
+ // Checksum length
+ static get CHECKSUM_LENGTH() {
+
+ // Return checksum length
+ return 4;
+ }
+}
+
+
+// Main function
+
+// Set global object's base58
+globalThis["Base58"] = Base58;
diff --git a/scripts/base64.js license.txt b/scripts/base64.js license.txt
new file mode 100755
index 0000000..fd579a4
--- /dev/null
+++ b/scripts/base64.js license.txt
@@ -0,0 +1,27 @@
+Copyright (c) 2014, Dan Kogai
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+* Neither the name of {{{project}}} nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/scripts/base64.js-3.7.5.js b/scripts/base64.js-3.7.5.js
new file mode 100755
index 0000000..c44a6c8
--- /dev/null
+++ b/scripts/base64.js-3.7.5.js
@@ -0,0 +1,316 @@
+//
+// THIS FILE IS AUTOMATICALLY GENERATED! DO NOT EDIT BY HAND!
+//
+;
+(function (global, factory) {
+ typeof exports === 'object' && typeof module !== 'undefined'
+ ? module.exports = factory()
+ : typeof define === 'function' && define.amd
+ ? define(factory) :
+ // cf. https://github.com/dankogai/js-base64/issues/119
+ (function () {
+ // existing version for noConflict()
+ var _Base64 = global.Base64;
+ var gBase64 = factory();
+ gBase64.noConflict = function () {
+ global.Base64 = _Base64;
+ return gBase64;
+ };
+ if (global.Meteor) { // Meteor.js
+ Base64 = gBase64;
+ }
+ global.Base64 = gBase64;
+ })();
+}((typeof self !== 'undefined' ? self
+ : typeof window !== 'undefined' ? window
+ : typeof global !== 'undefined' ? global
+ : this), function () {
+ 'use strict';
+ /**
+ * base64.ts
+ *
+ * Licensed under the BSD 3-Clause License.
+ * http://opensource.org/licenses/BSD-3-Clause
+ *
+ * References:
+ * http://en.wikipedia.org/wiki/Base64
+ *
+ * @author Dan Kogai (https://github.com/dankogai)
+ */
+ var version = '3.7.5';
+ /**
+ * @deprecated use lowercase `version`.
+ */
+ var VERSION = version;
+ var _hasatob = typeof atob === 'function';
+ var _hasbtoa = typeof btoa === 'function';
+ var _hasBuffer = typeof Buffer === 'function';
+ var _TD = typeof TextDecoder === 'function' ? new TextDecoder() : undefined;
+ var _TE = typeof TextEncoder === 'function' ? new TextEncoder() : undefined;
+ var b64ch = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
+ var b64chs = Array.prototype.slice.call(b64ch);
+ var b64tab = (function (a) {
+ var tab = {};
+ a.forEach(function (c, i) { return tab[c] = i; });
+ return tab;
+ })(b64chs);
+ var b64re = /^(?:[A-Za-z\d+\/]{4})*?(?:[A-Za-z\d+\/]{2}(?:==)?|[A-Za-z\d+\/]{3}=?)?$/;
+ var _fromCC = String.fromCharCode.bind(String);
+ var _U8Afrom = typeof Uint8Array.from === 'function'
+ ? Uint8Array.from.bind(Uint8Array)
+ : function (it) { return new Uint8Array(Array.prototype.slice.call(it, 0)); };
+ var _mkUriSafe = function (src) { return src
+ .replace(/=/g, '').replace(/[+\/]/g, function (m0) { return m0 == '+' ? '-' : '_'; }); };
+ var _tidyB64 = function (s) { return s.replace(/[^A-Za-z0-9\+\/]/g, ''); };
+ /**
+ * polyfill version of `btoa`
+ */
+ var btoaPolyfill = function (bin) {
+ // console.log('polyfilled');
+ var u32, c0, c1, c2, asc = '';
+ var pad = bin.length % 3;
+ for (var i = 0; i < bin.length;) {
+ if ((c0 = bin.charCodeAt(i++)) > 255 ||
+ (c1 = bin.charCodeAt(i++)) > 255 ||
+ (c2 = bin.charCodeAt(i++)) > 255)
+ throw new TypeError('invalid character found');
+ u32 = (c0 << 16) | (c1 << 8) | c2;
+ asc += b64chs[u32 >> 18 & 63]
+ + b64chs[u32 >> 12 & 63]
+ + b64chs[u32 >> 6 & 63]
+ + b64chs[u32 & 63];
+ }
+ return pad ? asc.slice(0, pad - 3) + "===".substring(pad) : asc;
+ };
+ /**
+ * does what `window.btoa` of web browsers do.
+ * @param {String} bin binary string
+ * @returns {string} Base64-encoded string
+ */
+ var _btoa = _hasbtoa ? function (bin) { return btoa(bin); }
+ : _hasBuffer ? function (bin) { return Buffer.from(bin, 'binary').toString('base64'); }
+ : btoaPolyfill;
+ var _fromUint8Array = _hasBuffer
+ ? function (u8a) { return Buffer.from(u8a).toString('base64'); }
+ : function (u8a) {
+ // cf. https://stackoverflow.com/questions/12710001/how-to-convert-uint8-array-to-base64-encoded-string/12713326#12713326
+ var maxargs = 0x1000;
+ var strs = [];
+ for (var i = 0, l = u8a.length; i < l; i += maxargs) {
+ strs.push(_fromCC.apply(null, u8a.subarray(i, i + maxargs)));
+ }
+ return _btoa(strs.join(''));
+ };
+ /**
+ * converts a Uint8Array to a Base64 string.
+ * @param {boolean} [urlsafe] URL-and-filename-safe a la RFC4648 §5
+ * @returns {string} Base64 string
+ */
+ var fromUint8Array = function (u8a, urlsafe) {
+ if (urlsafe === void 0) { urlsafe = false; }
+ return urlsafe ? _mkUriSafe(_fromUint8Array(u8a)) : _fromUint8Array(u8a);
+ };
+ // This trick is found broken https://github.com/dankogai/js-base64/issues/130
+ // const utob = (src: string) => unescape(encodeURIComponent(src));
+ // reverting good old fationed regexp
+ var cb_utob = function (c) {
+ if (c.length < 2) {
+ var cc = c.charCodeAt(0);
+ return cc < 0x80 ? c
+ : cc < 0x800 ? (_fromCC(0xc0 | (cc >>> 6))
+ + _fromCC(0x80 | (cc & 0x3f)))
+ : (_fromCC(0xe0 | ((cc >>> 12) & 0x0f))
+ + _fromCC(0x80 | ((cc >>> 6) & 0x3f))
+ + _fromCC(0x80 | (cc & 0x3f)));
+ }
+ else {
+ var cc = 0x10000
+ + (c.charCodeAt(0) - 0xD800) * 0x400
+ + (c.charCodeAt(1) - 0xDC00);
+ return (_fromCC(0xf0 | ((cc >>> 18) & 0x07))
+ + _fromCC(0x80 | ((cc >>> 12) & 0x3f))
+ + _fromCC(0x80 | ((cc >>> 6) & 0x3f))
+ + _fromCC(0x80 | (cc & 0x3f)));
+ }
+ };
+ var re_utob = /[\uD800-\uDBFF][\uDC00-\uDFFFF]|[^\x00-\x7F]/g;
+ /**
+ * @deprecated should have been internal use only.
+ * @param {string} src UTF-8 string
+ * @returns {string} UTF-16 string
+ */
+ var utob = function (u) { return u.replace(re_utob, cb_utob); };
+ //
+ var _encode = _hasBuffer
+ ? function (s) { return Buffer.from(s, 'utf8').toString('base64'); }
+ : _TE
+ ? function (s) { return _fromUint8Array(_TE.encode(s)); }
+ : function (s) { return _btoa(utob(s)); };
+ /**
+ * converts a UTF-8-encoded string to a Base64 string.
+ * @param {boolean} [urlsafe] if `true` make the result URL-safe
+ * @returns {string} Base64 string
+ */
+ var encode = function (src, urlsafe) {
+ if (urlsafe === void 0) { urlsafe = false; }
+ return urlsafe
+ ? _mkUriSafe(_encode(src))
+ : _encode(src);
+ };
+ /**
+ * converts a UTF-8-encoded string to URL-safe Base64 RFC4648 §5.
+ * @returns {string} Base64 string
+ */
+ var encodeURI = function (src) { return encode(src, true); };
+ // This trick is found broken https://github.com/dankogai/js-base64/issues/130
+ // const btou = (src: string) => decodeURIComponent(escape(src));
+ // reverting good old fationed regexp
+ var re_btou = /[\xC0-\xDF][\x80-\xBF]|[\xE0-\xEF][\x80-\xBF]{2}|[\xF0-\xF7][\x80-\xBF]{3}/g;
+ var cb_btou = function (cccc) {
+ switch (cccc.length) {
+ case 4:
+ var cp = ((0x07 & cccc.charCodeAt(0)) << 18)
+ | ((0x3f & cccc.charCodeAt(1)) << 12)
+ | ((0x3f & cccc.charCodeAt(2)) << 6)
+ | (0x3f & cccc.charCodeAt(3)), offset = cp - 0x10000;
+ return (_fromCC((offset >>> 10) + 0xD800)
+ + _fromCC((offset & 0x3FF) + 0xDC00));
+ case 3:
+ return _fromCC(((0x0f & cccc.charCodeAt(0)) << 12)
+ | ((0x3f & cccc.charCodeAt(1)) << 6)
+ | (0x3f & cccc.charCodeAt(2)));
+ default:
+ return _fromCC(((0x1f & cccc.charCodeAt(0)) << 6)
+ | (0x3f & cccc.charCodeAt(1)));
+ }
+ };
+ /**
+ * @deprecated should have been internal use only.
+ * @param {string} src UTF-16 string
+ * @returns {string} UTF-8 string
+ */
+ var btou = function (b) { return b.replace(re_btou, cb_btou); };
+ /**
+ * polyfill version of `atob`
+ */
+ var atobPolyfill = function (asc) {
+ // console.log('polyfilled');
+ asc = asc.replace(/\s+/g, '');
+ if (!b64re.test(asc))
+ throw new TypeError('malformed base64.');
+ asc += '=='.slice(2 - (asc.length & 3));
+ var u24, bin = '', r1, r2;
+ for (var i = 0; i < asc.length;) {
+ u24 = b64tab[asc.charAt(i++)] << 18
+ | b64tab[asc.charAt(i++)] << 12
+ | (r1 = b64tab[asc.charAt(i++)]) << 6
+ | (r2 = b64tab[asc.charAt(i++)]);
+ bin += r1 === 64 ? _fromCC(u24 >> 16 & 255)
+ : r2 === 64 ? _fromCC(u24 >> 16 & 255, u24 >> 8 & 255)
+ : _fromCC(u24 >> 16 & 255, u24 >> 8 & 255, u24 & 255);
+ }
+ return bin;
+ };
+ /**
+ * does what `window.atob` of web browsers do.
+ * @param {String} asc Base64-encoded string
+ * @returns {string} binary string
+ */
+ var _atob = _hasatob ? function (asc) { return atob(_tidyB64(asc)); }
+ : _hasBuffer ? function (asc) { return Buffer.from(asc, 'base64').toString('binary'); }
+ : atobPolyfill;
+ //
+ var _toUint8Array = _hasBuffer
+ ? function (a) { return _U8Afrom(Buffer.from(a, 'base64')); }
+ : function (a) { return _U8Afrom(_atob(a).split('').map(function (c) { return c.charCodeAt(0); })); };
+ /**
+ * converts a Base64 string to a Uint8Array.
+ */
+ var toUint8Array = function (a) { return _toUint8Array(_unURI(a)); };
+ //
+ var _decode = _hasBuffer
+ ? function (a) { return Buffer.from(a, 'base64').toString('utf8'); }
+ : _TD
+ ? function (a) { return _TD.decode(_toUint8Array(a)); }
+ : function (a) { return btou(_atob(a)); };
+ var _unURI = function (a) { return _tidyB64(a.replace(/[-_]/g, function (m0) { return m0 == '-' ? '+' : '/'; })); };
+ /**
+ * converts a Base64 string to a UTF-8 string.
+ * @param {String} src Base64 string. Both normal and URL-safe are supported
+ * @returns {string} UTF-8 string
+ */
+ var decode = function (src) { return _decode(_unURI(src)); };
+ /**
+ * check if a value is a valid Base64 string
+ * @param {String} src a value to check
+ */
+ var isValid = function (src) {
+ if (typeof src !== 'string')
+ return false;
+ var s = src.replace(/\s+/g, '').replace(/={0,2}$/, '');
+ return !/[^\s0-9a-zA-Z\+/]/.test(s) || !/[^\s0-9a-zA-Z\-_]/.test(s);
+ };
+ //
+ var _noEnum = function (v) {
+ return {
+ value: v, enumerable: false, writable: true, configurable: true
+ };
+ };
+ /**
+ * extend String.prototype with relevant methods
+ */
+ var extendString = function () {
+ var _add = function (name, body) { return Object.defineProperty(String.prototype, name, _noEnum(body)); };
+ _add('fromBase64', function () { return decode(this); });
+ _add('toBase64', function (urlsafe) { return encode(this, urlsafe); });
+ _add('toBase64URI', function () { return encode(this, true); });
+ _add('toBase64URL', function () { return encode(this, true); });
+ _add('toUint8Array', function () { return toUint8Array(this); });
+ };
+ /**
+ * extend Uint8Array.prototype with relevant methods
+ */
+ var extendUint8Array = function () {
+ var _add = function (name, body) { return Object.defineProperty(Uint8Array.prototype, name, _noEnum(body)); };
+ _add('toBase64', function (urlsafe) { return fromUint8Array(this, urlsafe); });
+ _add('toBase64URI', function () { return fromUint8Array(this, true); });
+ _add('toBase64URL', function () { return fromUint8Array(this, true); });
+ };
+ /**
+ * extend Builtin prototypes with relevant methods
+ */
+ var extendBuiltins = function () {
+ extendString();
+ extendUint8Array();
+ };
+ var gBase64 = {
+ version: version,
+ VERSION: VERSION,
+ atob: _atob,
+ atobPolyfill: atobPolyfill,
+ btoa: _btoa,
+ btoaPolyfill: btoaPolyfill,
+ fromBase64: decode,
+ toBase64: encode,
+ encode: encode,
+ encodeURI: encodeURI,
+ encodeURL: encodeURI,
+ utob: utob,
+ btou: btou,
+ decode: decode,
+ isValid: isValid,
+ fromUint8Array: fromUint8Array,
+ toUint8Array: toUint8Array,
+ extendString: extendString,
+ extendUint8Array: extendUint8Array,
+ extendBuiltins: extendBuiltins
+ };
+ //
+ // export Base64 to the namespace
+ //
+ // ES5 is yet to have Object.assign() that may make transpilers unhappy.
+ // gBase64.Base64 = Object.assign({}, gBase64);
+ gBase64.Base64 = {};
+ Object.keys(gBase64).forEach(function (k) { return gBase64.Base64[k] = gBase64[k]; });
+ return gBase64;
+}));
diff --git a/scripts/bech32 license.txt b/scripts/bech32 license.txt
new file mode 100755
index 0000000..8ae7d39
--- /dev/null
+++ b/scripts/bech32 license.txt
@@ -0,0 +1,22 @@
+MIT License
+
+Copyright (c) 2017 Pieter Wuille
+Copyright (c) 2018 bitcoinjs contributors
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/scripts/bech32-2.0.0.js b/scripts/bech32-2.0.0.js
new file mode 100755
index 0000000..268eef0
--- /dev/null
+++ b/scripts/bech32-2.0.0.js
@@ -0,0 +1,170 @@
+'use strict';
+//Object.defineProperty(globalThis, "__esModule", { value: true });
+globalThis.bech32m = globalThis.bech32 = void 0;
+const ALPHABET = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l';
+const ALPHABET_MAP = {};
+for (let z = 0; z < ALPHABET.length; z++) {
+ const x = ALPHABET.charAt(z);
+ ALPHABET_MAP[x] = z;
+}
+function polymodStep(pre) {
+ const b = pre >> 25;
+ return (((pre & 0x1ffffff) << 5) ^
+ (-((b >> 0) & 1) & 0x3b6a57b2) ^
+ (-((b >> 1) & 1) & 0x26508e6d) ^
+ (-((b >> 2) & 1) & 0x1ea119fa) ^
+ (-((b >> 3) & 1) & 0x3d4233dd) ^
+ (-((b >> 4) & 1) & 0x2a1462b3));
+}
+function prefixChk(prefix) {
+ let chk = 1;
+ for (let i = 0; i < prefix.length; ++i) {
+ const c = prefix.charCodeAt(i);
+ if (c < 33 || c > 126)
+ return 'Invalid prefix (' + prefix + ')';
+ chk = polymodStep(chk) ^ (c >> 5);
+ }
+ chk = polymodStep(chk);
+ for (let i = 0; i < prefix.length; ++i) {
+ const v = prefix.charCodeAt(i);
+ chk = polymodStep(chk) ^ (v & 0x1f);
+ }
+ return chk;
+}
+function convert(data, inBits, outBits, pad) {
+ let value = 0;
+ let bits = 0;
+ const maxV = (1 << outBits) - 1;
+ const result = [];
+ for (let i = 0; i < data.length; ++i) {
+ value = (value << inBits) | data[i];
+ bits += inBits;
+ while (bits >= outBits) {
+ bits -= outBits;
+ result.push((value >> bits) & maxV);
+ }
+ }
+ if (pad) {
+ if (bits > 0) {
+ result.push((value << (outBits - bits)) & maxV);
+ }
+ }
+ else {
+ if (bits >= inBits)
+ return 'Excess padding';
+ if ((value << (outBits - bits)) & maxV)
+ return 'Non-zero padding';
+ }
+ return result;
+}
+function toWords(bytes) {
+ return convert(bytes, 8, 5, true);
+}
+function fromWordsUnsafe(words) {
+ const res = convert(words, 5, 8, false);
+ if (Array.isArray(res))
+ return res;
+}
+function fromWords(words) {
+ const res = convert(words, 5, 8, false);
+ if (Array.isArray(res))
+ return res;
+ throw new Error(res);
+}
+function getLibraryFromEncoding(encoding) {
+ let ENCODING_CONST;
+ if (encoding === 'bech32') {
+ ENCODING_CONST = 1;
+ }
+ else {
+ ENCODING_CONST = 0x2bc830a3;
+ }
+ function encode(prefix, words, LIMIT) {
+ LIMIT = LIMIT || 90;
+ if (prefix.length + 7 + words.length > LIMIT)
+ throw new TypeError('Exceeds length limit');
+ prefix = prefix.toLowerCase();
+ // determine chk mod
+ let chk = prefixChk(prefix);
+ if (typeof chk === 'string')
+ throw new Error(chk);
+ let result = prefix + '1';
+ for (let i = 0; i < words.length; ++i) {
+ const x = words[i];
+ if (x >> 5 !== 0)
+ throw new Error('Non 5-bit word');
+ chk = polymodStep(chk) ^ x;
+ result += ALPHABET.charAt(x);
+ }
+ for (let i = 0; i < 6; ++i) {
+ chk = polymodStep(chk);
+ }
+ chk ^= ENCODING_CONST;
+ for (let i = 0; i < 6; ++i) {
+ const v = (chk >> ((5 - i) * 5)) & 0x1f;
+ result += ALPHABET.charAt(v);
+ }
+ return result;
+ }
+ function __decode(str, LIMIT) {
+ LIMIT = LIMIT || 90;
+ if (str.length < 8)
+ return str + ' too short';
+ if (str.length > LIMIT)
+ return 'Exceeds length limit';
+ // don't allow mixed case
+ const lowered = str.toLowerCase();
+ const uppered = str.toUpperCase();
+ if (str !== lowered && str !== uppered)
+ return 'Mixed-case string ' + str;
+ str = lowered;
+ const split = str.lastIndexOf('1');
+ if (split === -1)
+ return 'No separator character for ' + str;
+ if (split === 0)
+ return 'Missing prefix for ' + str;
+ const prefix = str.slice(0, split);
+ const wordChars = str.slice(split + 1);
+ if (wordChars.length < 6)
+ return 'Data too short';
+ let chk = prefixChk(prefix);
+ if (typeof chk === 'string')
+ return chk;
+ const words = [];
+ for (let i = 0; i < wordChars.length; ++i) {
+ const c = wordChars.charAt(i);
+ const v = ALPHABET_MAP[c];
+ if (v === undefined)
+ return 'Unknown character ' + c;
+ chk = polymodStep(chk) ^ v;
+ // not in the checksum?
+ if (i + 6 >= wordChars.length)
+ continue;
+ words.push(v);
+ }
+ if (chk !== ENCODING_CONST)
+ return 'Invalid checksum for ' + str;
+ return { prefix, words };
+ }
+ function decodeUnsafe(str, LIMIT) {
+ const res = __decode(str, LIMIT);
+ if (typeof res === 'object')
+ return res;
+ }
+ function decode(str, LIMIT) {
+ const res = __decode(str, LIMIT);
+ if (typeof res === 'object')
+ return res;
+ throw new Error(res);
+ }
+ return {
+ decodeUnsafe,
+ decode,
+ encode,
+ toWords,
+ fromWordsUnsafe,
+ fromWords,
+ };
+}
+globalThis.bech32 = getLibraryFromEncoding('bech32');
+globalThis.bech32m = getLibraryFromEncoding('bech32m');
diff --git a/scripts/bignumber.js license.txt b/scripts/bignumber.js license.txt
new file mode 100755
index 0000000..83b6c7c
--- /dev/null
+++ b/scripts/bignumber.js license.txt
@@ -0,0 +1,26 @@
+The MIT License (MIT)
+=====================
+
+Copyright © `<2022>` `Michael Mclaughlin`
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the “Software”), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+
diff --git a/scripts/bignumber.js-9.1.1.js b/scripts/bignumber.js-9.1.1.js
new file mode 100755
index 0000000..da6d84b
--- /dev/null
+++ b/scripts/bignumber.js-9.1.1.js
@@ -0,0 +1,2926 @@
+;(function (globalObject) {
+ 'use strict';
+
+/*
+ * bignumber.js v9.1.1
+ * A JavaScript library for arbitrary-precision arithmetic.
+ * https://github.com/MikeMcl/bignumber.js
+ * Copyright (c) 2022 Michael Mclaughlin
+ * MIT Licensed.
+ *
+ * BigNumber.prototype methods | BigNumber methods
+ * |
+ * absoluteValue abs | clone
+ * comparedTo | config set
+ * decimalPlaces dp | DECIMAL_PLACES
+ * dividedBy div | ROUNDING_MODE
+ * dividedToIntegerBy idiv | EXPONENTIAL_AT
+ * exponentiatedBy pow | RANGE
+ * integerValue | CRYPTO
+ * isEqualTo eq | MODULO_MODE
+ * isFinite | POW_PRECISION
+ * isGreaterThan gt | FORMAT
+ * isGreaterThanOrEqualTo gte | ALPHABET
+ * isInteger | isBigNumber
+ * isLessThan lt | maximum max
+ * isLessThanOrEqualTo lte | minimum min
+ * isNaN | random
+ * isNegative | sum
+ * isPositive |
+ * isZero |
+ * minus |
+ * modulo mod |
+ * multipliedBy times |
+ * negated |
+ * plus |
+ * precision sd |
+ * shiftedBy |
+ * squareRoot sqrt |
+ * toExponential |
+ * toFixed |
+ * toFormat |
+ * toFraction |
+ * toJSON |
+ * toNumber |
+ * toPrecision |
+ * toString |
+ * valueOf |
+ *
+ */
+
+
+ var BigNumber,
+ isNumeric = /^-?(?:\d+(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?$/i,
+ mathceil = Math.ceil,
+ mathfloor = Math.floor,
+
+ bignumberError = '[BigNumber Error] ',
+ tooManyDigits = bignumberError + 'Number primitive has more than 15 significant digits: ',
+
+ BASE = 1e14,
+ LOG_BASE = 14,
+ MAX_SAFE_INTEGER = 0x1fffffffffffff, // 2^53 - 1
+ // MAX_INT32 = 0x7fffffff, // 2^31 - 1
+ POWS_TEN = [1, 10, 100, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11, 1e12, 1e13],
+ SQRT_BASE = 1e7,
+
+ // EDITABLE
+ // The limit on the value of DECIMAL_PLACES, TO_EXP_NEG, TO_EXP_POS, MIN_EXP, MAX_EXP, and
+ // the arguments to toExponential, toFixed, toFormat, and toPrecision.
+ MAX = 1E9; // 0 to MAX_INT32
+
+
+ /*
+ * Create and return a BigNumber constructor.
+ */
+ function clone(configObject) {
+ var div, convertBase, parseNumeric,
+ P = BigNumber.prototype = { constructor: BigNumber, toString: null, valueOf: null },
+ ONE = new BigNumber(1),
+
+
+ //----------------------------- EDITABLE CONFIG DEFAULTS -------------------------------
+
+
+ // The default values below must be integers within the inclusive ranges stated.
+ // The values can also be changed at run-time using BigNumber.set.
+
+ // The maximum number of decimal places for operations involving division.
+ DECIMAL_PLACES = 20, // 0 to MAX
+
+ // The rounding mode used when rounding to the above decimal places, and when using
+ // toExponential, toFixed, toFormat and toPrecision, and round (default value).
+ // UP 0 Away from zero.
+ // DOWN 1 Towards zero.
+ // CEIL 2 Towards +Infinity.
+ // FLOOR 3 Towards -Infinity.
+ // HALF_UP 4 Towards nearest neighbour. If equidistant, up.
+ // HALF_DOWN 5 Towards nearest neighbour. If equidistant, down.
+ // HALF_EVEN 6 Towards nearest neighbour. If equidistant, towards even neighbour.
+ // HALF_CEIL 7 Towards nearest neighbour. If equidistant, towards +Infinity.
+ // HALF_FLOOR 8 Towards nearest neighbour. If equidistant, towards -Infinity.
+ ROUNDING_MODE = 4, // 0 to 8
+
+ // EXPONENTIAL_AT : [TO_EXP_NEG , TO_EXP_POS]
+
+ // The exponent value at and beneath which toString returns exponential notation.
+ // Number type: -7
+ TO_EXP_NEG = -7, // 0 to -MAX
+
+ // The exponent value at and above which toString returns exponential notation.
+ // Number type: 21
+ TO_EXP_POS = 21, // 0 to MAX
+
+ // RANGE : [MIN_EXP, MAX_EXP]
+
+ // The minimum exponent value, beneath which underflow to zero occurs.
+ // Number type: -324 (5e-324)
+ MIN_EXP = -1e7, // -1 to -MAX
+
+ // The maximum exponent value, above which overflow to Infinity occurs.
+ // Number type: 308 (1.7976931348623157e+308)
+ // For MAX_EXP > 1e7, e.g. new BigNumber('1e100000000').plus(1) may be slow.
+ MAX_EXP = 1e7, // 1 to MAX
+
+ // Whether to use cryptographically-secure random number generation, if available.
+ CRYPTO = false, // true or false
+
+ // The modulo mode used when calculating the modulus: a mod n.
+ // The quotient (q = a / n) is calculated according to the corresponding rounding mode.
+ // The remainder (r) is calculated as: r = a - n * q.
+ //
+ // UP 0 The remainder is positive if the dividend is negative, else is negative.
+ // DOWN 1 The remainder has the same sign as the dividend.
+ // This modulo mode is commonly known as 'truncated division' and is
+ // equivalent to (a % n) in JavaScript.
+ // FLOOR 3 The remainder has the same sign as the divisor (Python %).
+ // HALF_EVEN 6 This modulo mode implements the IEEE 754 remainder function.
+ // EUCLID 9 Euclidian division. q = sign(n) * floor(a / abs(n)).
+ // The remainder is always positive.
+ //
+ // The truncated division, floored division, Euclidian division and IEEE 754 remainder
+ // modes are commonly used for the modulus operation.
+ // Although the other rounding modes can also be used, they may not give useful results.
+ MODULO_MODE = 1, // 0 to 9
+
+ // The maximum number of significant digits of the result of the exponentiatedBy operation.
+ // If POW_PRECISION is 0, there will be unlimited significant digits.
+ POW_PRECISION = 0, // 0 to MAX
+
+ // The format specification used by the BigNumber.prototype.toFormat method.
+ FORMAT = {
+ prefix: '',
+ groupSize: 3,
+ secondaryGroupSize: 0,
+ groupSeparator: ',',
+ decimalSeparator: '.',
+ fractionGroupSize: 0,
+ fractionGroupSeparator: '\xA0', // non-breaking space
+ suffix: ''
+ },
+
+ // The alphabet used for base conversion. It must be at least 2 characters long, with no '+',
+ // '-', '.', whitespace, or repeated character.
+ // '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ$_'
+ ALPHABET = '0123456789abcdefghijklmnopqrstuvwxyz',
+ alphabetHasNormalDecimalDigits = true;
+
+
+ //------------------------------------------------------------------------------------------
+
+
+ // CONSTRUCTOR
+
+
+ /*
+ * The BigNumber constructor and exported function.
+ * Create and return a new instance of a BigNumber object.
+ *
+ * v {number|string|BigNumber} A numeric value.
+ * [b] {number} The base of v. Integer, 2 to ALPHABET.length inclusive.
+ */
+ function BigNumber(v, b) {
+ var alphabet, c, caseChanged, e, i, isNum, len, str,
+ x = this;
+
+ // Enable constructor call without `new`.
+ if (!(x instanceof BigNumber)) return new BigNumber(v, b);
+
+ if (b == null) {
+
+ if (v && v._isBigNumber === true) {
+ x.s = v.s;
+
+ if (!v.c || v.e > MAX_EXP) {
+ x.c = x.e = null;
+ } else if (v.e < MIN_EXP) {
+ x.c = [x.e = 0];
+ } else {
+ x.e = v.e;
+ x.c = v.c.slice();
+ }
+
+ return;
+ }
+
+ if ((isNum = typeof v == 'number') && v * 0 == 0) {
+
+ // Use `1 / n` to handle minus zero also.
+ x.s = 1 / v < 0 ? (v = -v, -1) : 1;
+
+ // Fast path for integers, where n < 2147483648 (2**31).
+ if (v === ~~v) {
+ for (e = 0, i = v; i >= 10; i /= 10, e++);
+
+ if (e > MAX_EXP) {
+ x.c = x.e = null;
+ } else {
+ x.e = e;
+ x.c = [v];
+ }
+
+ return;
+ }
+
+ str = String(v);
+ } else {
+
+ if (!isNumeric.test(str = String(v))) return parseNumeric(x, str, isNum);
+
+ x.s = str.charCodeAt(0) == 45 ? (str = str.slice(1), -1) : 1;
+ }
+
+ // Decimal point?
+ if ((e = str.indexOf('.')) > -1) str = str.replace('.', '');
+
+ // Exponential form?
+ if ((i = str.search(/e/i)) > 0) {
+
+ // Determine exponent.
+ if (e < 0) e = i;
+ e += +str.slice(i + 1);
+ str = str.substring(0, i);
+ } else if (e < 0) {
+
+ // Integer.
+ e = str.length;
+ }
+
+ } else {
+
+ // '[BigNumber Error] Base {not a primitive number|not an integer|out of range}: {b}'
+ intCheck(b, 2, ALPHABET.length, 'Base');
+
+ // Allow exponential notation to be used with base 10 argument, while
+ // also rounding to DECIMAL_PLACES as with other bases.
+ if (b == 10 && alphabetHasNormalDecimalDigits) {
+ x = new BigNumber(v);
+ return round(x, DECIMAL_PLACES + x.e + 1, ROUNDING_MODE);
+ }
+
+ str = String(v);
+
+ if (isNum = typeof v == 'number') {
+
+ // Avoid potential interpretation of Infinity and NaN as base 44+ values.
+ if (v * 0 != 0) return parseNumeric(x, str, isNum, b);
+
+ x.s = 1 / v < 0 ? (str = str.slice(1), -1) : 1;
+
+ // '[BigNumber Error] Number primitive has more than 15 significant digits: {n}'
+ if (BigNumber.DEBUG && str.replace(/^0\.0*|\./, '').length > 15) {
+ throw Error
+ (tooManyDigits + v);
+ }
+ } else {
+ x.s = str.charCodeAt(0) === 45 ? (str = str.slice(1), -1) : 1;
+ }
+
+ alphabet = ALPHABET.slice(0, b);
+ e = i = 0;
+
+ // Check that str is a valid base b number.
+ // Don't use RegExp, so alphabet can contain special characters.
+ for (len = str.length; i < len; i++) {
+ if (alphabet.indexOf(c = str.charAt(i)) < 0) {
+ if (c == '.') {
+
+ // If '.' is not the first character and it has not be found before.
+ if (i > e) {
+ e = len;
+ continue;
+ }
+ } else if (!caseChanged) {
+
+ // Allow e.g. hexadecimal 'FF' as well as 'ff'.
+ if (str == str.toUpperCase() && (str = str.toLowerCase()) ||
+ str == str.toLowerCase() && (str = str.toUpperCase())) {
+ caseChanged = true;
+ i = -1;
+ e = 0;
+ continue;
+ }
+ }
+
+ return parseNumeric(x, String(v), isNum, b);
+ }
+ }
+
+ // Prevent later check for length on converted number.
+ isNum = false;
+ str = convertBase(str, b, 10, x.s);
+
+ // Decimal point?
+ if ((e = str.indexOf('.')) > -1) str = str.replace('.', '');
+ else e = str.length;
+ }
+
+ // Determine leading zeros.
+ for (i = 0; str.charCodeAt(i) === 48; i++);
+
+ // Determine trailing zeros.
+ for (len = str.length; str.charCodeAt(--len) === 48;);
+
+ if (str = str.slice(i, ++len)) {
+ len -= i;
+
+ // '[BigNumber Error] Number primitive has more than 15 significant digits: {n}'
+ if (isNum && BigNumber.DEBUG &&
+ len > 15 && (v > MAX_SAFE_INTEGER || v !== mathfloor(v))) {
+ throw Error
+ (tooManyDigits + (x.s * v));
+ }
+
+ // Overflow?
+ if ((e = e - i - 1) > MAX_EXP) {
+
+ // Infinity.
+ x.c = x.e = null;
+
+ // Underflow?
+ } else if (e < MIN_EXP) {
+
+ // Zero.
+ x.c = [x.e = 0];
+ } else {
+ x.e = e;
+ x.c = [];
+
+ // Transform base
+
+ // e is the base 10 exponent.
+ // i is where to slice str to get the first element of the coefficient array.
+ i = (e + 1) % LOG_BASE;
+ if (e < 0) i += LOG_BASE; // i < 1
+
+ if (i < len) {
+ if (i) x.c.push(+str.slice(0, i));
+
+ for (len -= LOG_BASE; i < len;) {
+ x.c.push(+str.slice(i, i += LOG_BASE));
+ }
+
+ i = LOG_BASE - (str = str.slice(i)).length;
+ } else {
+ i -= len;
+ }
+
+ for (; i--; str += '0');
+ x.c.push(+str);
+ }
+ } else {
+
+ // Zero.
+ x.c = [x.e = 0];
+ }
+ }
+
+
+ // CONSTRUCTOR PROPERTIES
+
+
+ BigNumber.clone = clone;
+
+ BigNumber.ROUND_UP = 0;
+ BigNumber.ROUND_DOWN = 1;
+ BigNumber.ROUND_CEIL = 2;
+ BigNumber.ROUND_FLOOR = 3;
+ BigNumber.ROUND_HALF_UP = 4;
+ BigNumber.ROUND_HALF_DOWN = 5;
+ BigNumber.ROUND_HALF_EVEN = 6;
+ BigNumber.ROUND_HALF_CEIL = 7;
+ BigNumber.ROUND_HALF_FLOOR = 8;
+ BigNumber.EUCLID = 9;
+
+
+ /*
+ * Configure infrequently-changing library-wide settings.
+ *
+ * Accept an object with the following optional properties (if the value of a property is
+ * a number, it must be an integer within the inclusive range stated):
+ *
+ * DECIMAL_PLACES {number} 0 to MAX
+ * ROUNDING_MODE {number} 0 to 8
+ * EXPONENTIAL_AT {number|number[]} -MAX to MAX or [-MAX to 0, 0 to MAX]
+ * RANGE {number|number[]} -MAX to MAX (not zero) or [-MAX to -1, 1 to MAX]
+ * CRYPTO {boolean} true or false
+ * MODULO_MODE {number} 0 to 9
+ * POW_PRECISION {number} 0 to MAX
+ * ALPHABET {string} A string of two or more unique characters which does
+ * not contain '.'.
+ * FORMAT {object} An object with some of the following properties:
+ * prefix {string}
+ * groupSize {number}
+ * secondaryGroupSize {number}
+ * groupSeparator {string}
+ * decimalSeparator {string}
+ * fractionGroupSize {number}
+ * fractionGroupSeparator {string}
+ * suffix {string}
+ *
+ * (The values assigned to the above FORMAT object properties are not checked for validity.)
+ *
+ * E.g.
+ * BigNumber.config({ DECIMAL_PLACES : 20, ROUNDING_MODE : 4 })
+ *
+ * Ignore properties/parameters set to null or undefined, except for ALPHABET.
+ *
+ * Return an object with the properties current values.
+ */
+ BigNumber.config = BigNumber.set = function (obj) {
+ var p, v;
+
+ if (obj != null) {
+
+ if (typeof obj == 'object') {
+
+ // DECIMAL_PLACES {number} Integer, 0 to MAX inclusive.
+ // '[BigNumber Error] DECIMAL_PLACES {not a primitive number|not an integer|out of range}: {v}'
+ if (obj.hasOwnProperty(p = 'DECIMAL_PLACES')) {
+ v = obj[p];
+ intCheck(v, 0, MAX, p);
+ DECIMAL_PLACES = v;
+ }
+
+ // ROUNDING_MODE {number} Integer, 0 to 8 inclusive.
+ // '[BigNumber Error] ROUNDING_MODE {not a primitive number|not an integer|out of range}: {v}'
+ if (obj.hasOwnProperty(p = 'ROUNDING_MODE')) {
+ v = obj[p];
+ intCheck(v, 0, 8, p);
+ ROUNDING_MODE = v;
+ }
+
+ // EXPONENTIAL_AT {number|number[]}
+ // Integer, -MAX to MAX inclusive or
+ // [integer -MAX to 0 inclusive, 0 to MAX inclusive].
+ // '[BigNumber Error] EXPONENTIAL_AT {not a primitive number|not an integer|out of range}: {v}'
+ if (obj.hasOwnProperty(p = 'EXPONENTIAL_AT')) {
+ v = obj[p];
+ if (v && v.pop) {
+ intCheck(v[0], -MAX, 0, p);
+ intCheck(v[1], 0, MAX, p);
+ TO_EXP_NEG = v[0];
+ TO_EXP_POS = v[1];
+ } else {
+ intCheck(v, -MAX, MAX, p);
+ TO_EXP_NEG = -(TO_EXP_POS = v < 0 ? -v : v);
+ }
+ }
+
+ // RANGE {number|number[]} Non-zero integer, -MAX to MAX inclusive or
+ // [integer -MAX to -1 inclusive, integer 1 to MAX inclusive].
+ // '[BigNumber Error] RANGE {not a primitive number|not an integer|out of range|cannot be zero}: {v}'
+ if (obj.hasOwnProperty(p = 'RANGE')) {
+ v = obj[p];
+ if (v && v.pop) {
+ intCheck(v[0], -MAX, -1, p);
+ intCheck(v[1], 1, MAX, p);
+ MIN_EXP = v[0];
+ MAX_EXP = v[1];
+ } else {
+ intCheck(v, -MAX, MAX, p);
+ if (v) {
+ MIN_EXP = -(MAX_EXP = v < 0 ? -v : v);
+ } else {
+ throw Error
+ (bignumberError + p + ' cannot be zero: ' + v);
+ }
+ }
+ }
+
+ // CRYPTO {boolean} true or false.
+ // '[BigNumber Error] CRYPTO not true or false: {v}'
+ // '[BigNumber Error] crypto unavailable'
+ if (obj.hasOwnProperty(p = 'CRYPTO')) {
+ v = obj[p];
+ if (v === !!v) {
+ if (v) {
+ if (typeof crypto != 'undefined' && crypto &&
+ (crypto.getRandomValues || crypto.randomBytes)) {
+ CRYPTO = v;
+ } else {
+ CRYPTO = !v;
+ throw Error
+ (bignumberError + 'crypto unavailable');
+ }
+ } else {
+ CRYPTO = v;
+ }
+ } else {
+ throw Error
+ (bignumberError + p + ' not true or false: ' + v);
+ }
+ }
+
+ // MODULO_MODE {number} Integer, 0 to 9 inclusive.
+ // '[BigNumber Error] MODULO_MODE {not a primitive number|not an integer|out of range}: {v}'
+ if (obj.hasOwnProperty(p = 'MODULO_MODE')) {
+ v = obj[p];
+ intCheck(v, 0, 9, p);
+ MODULO_MODE = v;
+ }
+
+ // POW_PRECISION {number} Integer, 0 to MAX inclusive.
+ // '[BigNumber Error] POW_PRECISION {not a primitive number|not an integer|out of range}: {v}'
+ if (obj.hasOwnProperty(p = 'POW_PRECISION')) {
+ v = obj[p];
+ intCheck(v, 0, MAX, p);
+ POW_PRECISION = v;
+ }
+
+ // FORMAT {object}
+ // '[BigNumber Error] FORMAT not an object: {v}'
+ if (obj.hasOwnProperty(p = 'FORMAT')) {
+ v = obj[p];
+ if (typeof v == 'object') FORMAT = v;
+ else throw Error
+ (bignumberError + p + ' not an object: ' + v);
+ }
+
+ // ALPHABET {string}
+ // '[BigNumber Error] ALPHABET invalid: {v}'
+ if (obj.hasOwnProperty(p = 'ALPHABET')) {
+ v = obj[p];
+
+ // Disallow if less than two characters,
+ // or if it contains '+', '-', '.', whitespace, or a repeated character.
+ if (typeof v == 'string' && !/^.?$|[+\-.\s]|(.).*\1/.test(v)) {
+ alphabetHasNormalDecimalDigits = v.slice(0, 10) == '0123456789';
+ ALPHABET = v;
+ } else {
+ throw Error
+ (bignumberError + p + ' invalid: ' + v);
+ }
+ }
+
+ } else {
+
+ // '[BigNumber Error] Object expected: {v}'
+ throw Error
+ (bignumberError + 'Object expected: ' + obj);
+ }
+ }
+
+ return {
+ DECIMAL_PLACES: DECIMAL_PLACES,
+ ROUNDING_MODE: ROUNDING_MODE,
+ EXPONENTIAL_AT: [TO_EXP_NEG, TO_EXP_POS],
+ RANGE: [MIN_EXP, MAX_EXP],
+ CRYPTO: CRYPTO,
+ MODULO_MODE: MODULO_MODE,
+ POW_PRECISION: POW_PRECISION,
+ FORMAT: FORMAT,
+ ALPHABET: ALPHABET
+ };
+ };
+
+
+ /*
+ * Return true if v is a BigNumber instance, otherwise return false.
+ *
+ * If BigNumber.DEBUG is true, throw if a BigNumber instance is not well-formed.
+ *
+ * v {any}
+ *
+ * '[BigNumber Error] Invalid BigNumber: {v}'
+ */
+ BigNumber.isBigNumber = function (v) {
+ if (!v || v._isBigNumber !== true) return false;
+ if (!BigNumber.DEBUG) return true;
+
+ var i, n,
+ c = v.c,
+ e = v.e,
+ s = v.s;
+
+ out: if ({}.toString.call(c) == '[object Array]') {
+
+ if ((s === 1 || s === -1) && e >= -MAX && e <= MAX && e === mathfloor(e)) {
+
+ // If the first element is zero, the BigNumber value must be zero.
+ if (c[0] === 0) {
+ if (e === 0 && c.length === 1) return true;
+ break out;
+ }
+
+ // Calculate number of digits that c[0] should have, based on the exponent.
+ i = (e + 1) % LOG_BASE;
+ if (i < 1) i += LOG_BASE;
+
+ // Calculate number of digits of c[0].
+ //if (Math.ceil(Math.log(c[0] + 1) / Math.LN10) == i) {
+ if (String(c[0]).length == i) {
+
+ for (i = 0; i < c.length; i++) {
+ n = c[i];
+ if (n < 0 || n >= BASE || n !== mathfloor(n)) break out;
+ }
+
+ // Last element cannot be zero, unless it is the only element.
+ if (n !== 0) return true;
+ }
+ }
+
+ // Infinity/NaN
+ } else if (c === null && e === null && (s === null || s === 1 || s === -1)) {
+ return true;
+ }
+
+ throw Error
+ (bignumberError + 'Invalid BigNumber: ' + v);
+ };
+
+
+ /*
+ * Return a new BigNumber whose value is the maximum of the arguments.
+ *
+ * arguments {number|string|BigNumber}
+ */
+ BigNumber.maximum = BigNumber.max = function () {
+ return maxOrMin(arguments, P.lt);
+ };
+
+
+ /*
+ * Return a new BigNumber whose value is the minimum of the arguments.
+ *
+ * arguments {number|string|BigNumber}
+ */
+ BigNumber.minimum = BigNumber.min = function () {
+ return maxOrMin(arguments, P.gt);
+ };
+
+
+ /*
+ * Return a new BigNumber with a random value equal to or greater than 0 and less than 1,
+ * and with dp, or DECIMAL_PLACES if dp is omitted, decimal places (or less if trailing
+ * zeros are produced).
+ *
+ * [dp] {number} Decimal places. Integer, 0 to MAX inclusive.
+ *
+ * '[BigNumber Error] Argument {not a primitive number|not an integer|out of range}: {dp}'
+ * '[BigNumber Error] crypto unavailable'
+ */
+ BigNumber.random = (function () {
+ var pow2_53 = 0x20000000000000;
+
+ // Return a 53 bit integer n, where 0 <= n < 9007199254740992.
+ // Check if Math.random() produces more than 32 bits of randomness.
+ // If it does, assume at least 53 bits are produced, otherwise assume at least 30 bits.
+ // 0x40000000 is 2^30, 0x800000 is 2^23, 0x1fffff is 2^21 - 1.
+ var random53bitInt = (Math.random() * pow2_53) & 0x1fffff
+ ? function () { return mathfloor(Math.random() * pow2_53); }
+ : function () { return ((Math.random() * 0x40000000 | 0) * 0x800000) +
+ (Math.random() * 0x800000 | 0); };
+
+ return function (dp) {
+ var a, b, e, k, v,
+ i = 0,
+ c = [],
+ rand = new BigNumber(ONE);
+
+ if (dp == null) dp = DECIMAL_PLACES;
+ else intCheck(dp, 0, MAX);
+
+ k = mathceil(dp / LOG_BASE);
+
+ if (CRYPTO) {
+
+ // Browsers supporting crypto.getRandomValues.
+ if (crypto.getRandomValues) {
+
+ a = crypto.getRandomValues(new Uint32Array(k *= 2));
+
+ for (; i < k;) {
+
+ // 53 bits:
+ // ((Math.pow(2, 32) - 1) * Math.pow(2, 21)).toString(2)
+ // 11111 11111111 11111111 11111111 11100000 00000000 00000000
+ // ((Math.pow(2, 32) - 1) >>> 11).toString(2)
+ // 11111 11111111 11111111
+ // 0x20000 is 2^21.
+ v = a[i] * 0x20000 + (a[i + 1] >>> 11);
+
+ // Rejection sampling:
+ // 0 <= v < 9007199254740992
+ // Probability that v >= 9e15, is
+ // 7199254740992 / 9007199254740992 ~= 0.0008, i.e. 1 in 1251
+ if (v >= 9e15) {
+ b = crypto.getRandomValues(new Uint32Array(2));
+ a[i] = b[0];
+ a[i + 1] = b[1];
+ } else {
+
+ // 0 <= v <= 8999999999999999
+ // 0 <= (v % 1e14) <= 99999999999999
+ c.push(v % 1e14);
+ i += 2;
+ }
+ }
+ i = k / 2;
+
+ // Node.js supporting crypto.randomBytes.
+ } else if (crypto.randomBytes) {
+
+ // buffer
+ a = crypto.randomBytes(k *= 7);
+
+ for (; i < k;) {
+
+ // 0x1000000000000 is 2^48, 0x10000000000 is 2^40
+ // 0x100000000 is 2^32, 0x1000000 is 2^24
+ // 11111 11111111 11111111 11111111 11111111 11111111 11111111
+ // 0 <= v < 9007199254740992
+ v = ((a[i] & 31) * 0x1000000000000) + (a[i + 1] * 0x10000000000) +
+ (a[i + 2] * 0x100000000) + (a[i + 3] * 0x1000000) +
+ (a[i + 4] << 16) + (a[i + 5] << 8) + a[i + 6];
+
+ if (v >= 9e15) {
+ crypto.randomBytes(7).copy(a, i);
+ } else {
+
+ // 0 <= (v % 1e14) <= 99999999999999
+ c.push(v % 1e14);
+ i += 7;
+ }
+ }
+ i = k / 7;
+ } else {
+ CRYPTO = false;
+ throw Error
+ (bignumberError + 'crypto unavailable');
+ }
+ }
+
+ // Use Math.random.
+ if (!CRYPTO) {
+
+ for (; i < k;) {
+ v = random53bitInt();
+ if (v < 9e15) c[i++] = v % 1e14;
+ }
+ }
+
+ k = c[--i];
+ dp %= LOG_BASE;
+
+ // Convert trailing digits to zeros according to dp.
+ if (k && dp) {
+ v = POWS_TEN[LOG_BASE - dp];
+ c[i] = mathfloor(k / v) * v;
+ }
+
+ // Remove trailing elements which are zero.
+ for (; c[i] === 0; c.pop(), i--);
+
+ // Zero?
+ if (i < 0) {
+ c = [e = 0];
+ } else {
+
+ // Remove leading elements which are zero and adjust exponent accordingly.
+ for (e = -1 ; c[0] === 0; c.splice(0, 1), e -= LOG_BASE);
+
+ // Count the digits of the first element of c to determine leading zeros, and...
+ for (i = 1, v = c[0]; v >= 10; v /= 10, i++);
+
+ // adjust the exponent accordingly.
+ if (i < LOG_BASE) e -= LOG_BASE - i;
+ }
+
+ rand.e = e;
+ rand.c = c;
+ return rand;
+ };
+ })();
+
+
+ /*
+ * Return a BigNumber whose value is the sum of the arguments.
+ *
+ * arguments {number|string|BigNumber}
+ */
+ BigNumber.sum = function () {
+ var i = 1,
+ args = arguments,
+ sum = new BigNumber(args[0]);
+ for (; i < args.length;) sum = sum.plus(args[i++]);
+ return sum;
+ };
+
+
+ // PRIVATE FUNCTIONS
+
+
+ // Called by BigNumber and BigNumber.prototype.toString.
+ convertBase = (function () {
+ var decimal = '0123456789';
+
+ /*
+ * Convert string of baseIn to an array of numbers of baseOut.
+ * Eg. toBaseOut('255', 10, 16) returns [15, 15].
+ * Eg. toBaseOut('ff', 16, 10) returns [2, 5, 5].
+ */
+ function toBaseOut(str, baseIn, baseOut, alphabet) {
+ var j,
+ arr = [0],
+ arrL,
+ i = 0,
+ len = str.length;
+
+ for (; i < len;) {
+ for (arrL = arr.length; arrL--; arr[arrL] *= baseIn);
+
+ arr[0] += alphabet.indexOf(str.charAt(i++));
+
+ for (j = 0; j < arr.length; j++) {
+
+ if (arr[j] > baseOut - 1) {
+ if (arr[j + 1] == null) arr[j + 1] = 0;
+ arr[j + 1] += arr[j] / baseOut | 0;
+ arr[j] %= baseOut;
+ }
+ }
+ }
+
+ return arr.reverse();
+ }
+
+ // Convert a numeric string of baseIn to a numeric string of baseOut.
+ // If the caller is toString, we are converting from base 10 to baseOut.
+ // If the caller is BigNumber, we are converting from baseIn to base 10.
+ return function (str, baseIn, baseOut, sign, callerIsToString) {
+ var alphabet, d, e, k, r, x, xc, y,
+ i = str.indexOf('.'),
+ dp = DECIMAL_PLACES,
+ rm = ROUNDING_MODE;
+
+ // Non-integer.
+ if (i >= 0) {
+ k = POW_PRECISION;
+
+ // Unlimited precision.
+ POW_PRECISION = 0;
+ str = str.replace('.', '');
+ y = new BigNumber(baseIn);
+ x = y.pow(str.length - i);
+ POW_PRECISION = k;
+
+ // Convert str as if an integer, then restore the fraction part by dividing the
+ // result by its base raised to a power.
+
+ y.c = toBaseOut(toFixedPoint(coeffToString(x.c), x.e, '0'),
+ 10, baseOut, decimal);
+ y.e = y.c.length;
+ }
+
+ // Convert the number as integer.
+
+ xc = toBaseOut(str, baseIn, baseOut, callerIsToString
+ ? (alphabet = ALPHABET, decimal)
+ : (alphabet = decimal, ALPHABET));
+
+ // xc now represents str as an integer and converted to baseOut. e is the exponent.
+ e = k = xc.length;
+
+ // Remove trailing zeros.
+ for (; xc[--k] == 0; xc.pop());
+
+ // Zero?
+ if (!xc[0]) return alphabet.charAt(0);
+
+ // Does str represent an integer? If so, no need for the division.
+ if (i < 0) {
+ --e;
+ } else {
+ x.c = xc;
+ x.e = e;
+
+ // The sign is needed for correct rounding.
+ x.s = sign;
+ x = div(x, y, dp, rm, baseOut);
+ xc = x.c;
+ r = x.r;
+ e = x.e;
+ }
+
+ // xc now represents str converted to baseOut.
+
+ // THe index of the rounding digit.
+ d = e + dp + 1;
+
+ // The rounding digit: the digit to the right of the digit that may be rounded up.
+ i = xc[d];
+
+ // Look at the rounding digits and mode to determine whether to round up.
+
+ k = baseOut / 2;
+ r = r || d < 0 || xc[d + 1] != null;
+
+ r = rm < 4 ? (i != null || r) && (rm == 0 || rm == (x.s < 0 ? 3 : 2))
+ : i > k || i == k &&(rm == 4 || r || rm == 6 && xc[d - 1] & 1 ||
+ rm == (x.s < 0 ? 8 : 7));
+
+ // If the index of the rounding digit is not greater than zero, or xc represents
+ // zero, then the result of the base conversion is zero or, if rounding up, a value
+ // such as 0.00001.
+ if (d < 1 || !xc[0]) {
+
+ // 1^-dp or 0
+ str = r ? toFixedPoint(alphabet.charAt(1), -dp, alphabet.charAt(0)) : alphabet.charAt(0);
+ } else {
+
+ // Truncate xc to the required number of decimal places.
+ xc.length = d;
+
+ // Round up?
+ if (r) {
+
+ // Rounding up may mean the previous digit has to be rounded up and so on.
+ for (--baseOut; ++xc[--d] > baseOut;) {
+ xc[d] = 0;
+
+ if (!d) {
+ ++e;
+ xc = [1].concat(xc);
+ }
+ }
+ }
+
+ // Determine trailing zeros.
+ for (k = xc.length; !xc[--k];);
+
+ // E.g. [4, 11, 15] becomes 4bf.
+ for (i = 0, str = ''; i <= k; str += alphabet.charAt(xc[i++]));
+
+ // Add leading zeros, decimal point and trailing zeros as required.
+ str = toFixedPoint(str, e, alphabet.charAt(0));
+ }
+
+ // The caller will add the sign.
+ return str;
+ };
+ })();
+
+
+ // Perform division in the specified base. Called by div and convertBase.
+ div = (function () {
+
+ // Assume non-zero x and k.
+ function multiply(x, k, base) {
+ var m, temp, xlo, xhi,
+ carry = 0,
+ i = x.length,
+ klo = k % SQRT_BASE,
+ khi = k / SQRT_BASE | 0;
+
+ for (x = x.slice(); i--;) {
+ xlo = x[i] % SQRT_BASE;
+ xhi = x[i] / SQRT_BASE | 0;
+ m = khi * xlo + xhi * klo;
+ temp = klo * xlo + ((m % SQRT_BASE) * SQRT_BASE) + carry;
+ carry = (temp / base | 0) + (m / SQRT_BASE | 0) + khi * xhi;
+ x[i] = temp % base;
+ }
+
+ if (carry) x = [carry].concat(x);
+
+ return x;
+ }
+
+ function compare(a, b, aL, bL) {
+ var i, cmp;
+
+ if (aL != bL) {
+ cmp = aL > bL ? 1 : -1;
+ } else {
+
+ for (i = cmp = 0; i < aL; i++) {
+
+ if (a[i] != b[i]) {
+ cmp = a[i] > b[i] ? 1 : -1;
+ break;
+ }
+ }
+ }
+
+ return cmp;
+ }
+
+ function subtract(a, b, aL, base) {
+ var i = 0;
+
+ // Subtract b from a.
+ for (; aL--;) {
+ a[aL] -= i;
+ i = a[aL] < b[aL] ? 1 : 0;
+ a[aL] = i * base + a[aL] - b[aL];
+ }
+
+ // Remove leading zeros.
+ for (; !a[0] && a.length > 1; a.splice(0, 1));
+ }
+
+ // x: dividend, y: divisor.
+ return function (x, y, dp, rm, base) {
+ var cmp, e, i, more, n, prod, prodL, q, qc, rem, remL, rem0, xi, xL, yc0,
+ yL, yz,
+ s = x.s == y.s ? 1 : -1,
+ xc = x.c,
+ yc = y.c;
+
+ // Either NaN, Infinity or 0?
+ if (!xc || !xc[0] || !yc || !yc[0]) {
+
+ return new BigNumber(
+
+ // Return NaN if either NaN, or both Infinity or 0.
+ !x.s || !y.s || (xc ? yc && xc[0] == yc[0] : !yc) ? NaN :
+
+ // Return ±0 if x is ±0 or y is ±Infinity, or return ±Infinity as y is ±0.
+ xc && xc[0] == 0 || !yc ? s * 0 : s / 0
+ );
+ }
+
+ q = new BigNumber(s);
+ qc = q.c = [];
+ e = x.e - y.e;
+ s = dp + e + 1;
+
+ if (!base) {
+ base = BASE;
+ e = bitFloor(x.e / LOG_BASE) - bitFloor(y.e / LOG_BASE);
+ s = s / LOG_BASE | 0;
+ }
+
+ // Result exponent may be one less then the current value of e.
+ // The coefficients of the BigNumbers from convertBase may have trailing zeros.
+ for (i = 0; yc[i] == (xc[i] || 0); i++);
+
+ if (yc[i] > (xc[i] || 0)) e--;
+
+ if (s < 0) {
+ qc.push(1);
+ more = true;
+ } else {
+ xL = xc.length;
+ yL = yc.length;
+ i = 0;
+ s += 2;
+
+ // Normalise xc and yc so highest order digit of yc is >= base / 2.
+
+ n = mathfloor(base / (yc[0] + 1));
+
+ // Not necessary, but to handle odd bases where yc[0] == (base / 2) - 1.
+ // if (n > 1 || n++ == 1 && yc[0] < base / 2) {
+ if (n > 1) {
+ yc = multiply(yc, n, base);
+ xc = multiply(xc, n, base);
+ yL = yc.length;
+ xL = xc.length;
+ }
+
+ xi = yL;
+ rem = xc.slice(0, yL);
+ remL = rem.length;
+
+ // Add zeros to make remainder as long as divisor.
+ for (; remL < yL; rem[remL++] = 0);
+ yz = yc.slice();
+ yz = [0].concat(yz);
+ yc0 = yc[0];
+ if (yc[1] >= base / 2) yc0++;
+ // Not necessary, but to prevent trial digit n > base, when using base 3.
+ // else if (base == 3 && yc0 == 1) yc0 = 1 + 1e-15;
+
+ do {
+ n = 0;
+
+ // Compare divisor and remainder.
+ cmp = compare(yc, rem, yL, remL);
+
+ // If divisor < remainder.
+ if (cmp < 0) {
+
+ // Calculate trial digit, n.
+
+ rem0 = rem[0];
+ if (yL != remL) rem0 = rem0 * base + (rem[1] || 0);
+
+ // n is how many times the divisor goes into the current remainder.
+ n = mathfloor(rem0 / yc0);
+
+ // Algorithm:
+ // product = divisor multiplied by trial digit (n).
+ // Compare product and remainder.
+ // If product is greater than remainder:
+ // Subtract divisor from product, decrement trial digit.
+ // Subtract product from remainder.
+ // If product was less than remainder at the last compare:
+ // Compare new remainder and divisor.
+ // If remainder is greater than divisor:
+ // Subtract divisor from remainder, increment trial digit.
+
+ if (n > 1) {
+
+ // n may be > base only when base is 3.
+ if (n >= base) n = base - 1;
+
+ // product = divisor * trial digit.
+ prod = multiply(yc, n, base);
+ prodL = prod.length;
+ remL = rem.length;
+
+ // Compare product and remainder.
+ // If product > remainder then trial digit n too high.
+ // n is 1 too high about 5% of the time, and is not known to have
+ // ever been more than 1 too high.
+ while (compare(prod, rem, prodL, remL) == 1) {
+ n--;
+
+ // Subtract divisor from product.
+ subtract(prod, yL < prodL ? yz : yc, prodL, base);
+ prodL = prod.length;
+ cmp = 1;
+ }
+ } else {
+
+ // n is 0 or 1, cmp is -1.
+ // If n is 0, there is no need to compare yc and rem again below,
+ // so change cmp to 1 to avoid it.
+ // If n is 1, leave cmp as -1, so yc and rem are compared again.
+ if (n == 0) {
+
+ // divisor < remainder, so n must be at least 1.
+ cmp = n = 1;
+ }
+
+ // product = divisor
+ prod = yc.slice();
+ prodL = prod.length;
+ }
+
+ if (prodL < remL) prod = [0].concat(prod);
+
+ // Subtract product from remainder.
+ subtract(rem, prod, remL, base);
+ remL = rem.length;
+
+ // If product was < remainder.
+ if (cmp == -1) {
+
+ // Compare divisor and new remainder.
+ // If divisor < new remainder, subtract divisor from remainder.
+ // Trial digit n too low.
+ // n is 1 too low about 5% of the time, and very rarely 2 too low.
+ while (compare(yc, rem, yL, remL) < 1) {
+ n++;
+
+ // Subtract divisor from remainder.
+ subtract(rem, yL < remL ? yz : yc, remL, base);
+ remL = rem.length;
+ }
+ }
+ } else if (cmp === 0) {
+ n++;
+ rem = [0];
+ } // else cmp === 1 and n will be 0
+
+ // Add the next digit, n, to the result array.
+ qc[i++] = n;
+
+ // Update the remainder.
+ if (rem[0]) {
+ rem[remL++] = xc[xi] || 0;
+ } else {
+ rem = [xc[xi]];
+ remL = 1;
+ }
+ } while ((xi++ < xL || rem[0] != null) && s--);
+
+ more = rem[0] != null;
+
+ // Leading zero?
+ if (!qc[0]) qc.splice(0, 1);
+ }
+
+ if (base == BASE) {
+
+ // To calculate q.e, first get the number of digits of qc[0].
+ for (i = 1, s = qc[0]; s >= 10; s /= 10, i++);
+
+ round(q, dp + (q.e = i + e * LOG_BASE - 1) + 1, rm, more);
+
+ // Caller is convertBase.
+ } else {
+ q.e = e;
+ q.r = +more;
+ }
+
+ return q;
+ };
+ })();
+
+
+ /*
+ * Return a string representing the value of BigNumber n in fixed-point or exponential
+ * notation rounded to the specified decimal places or significant digits.
+ *
+ * n: a BigNumber.
+ * i: the index of the last digit required (i.e. the digit that may be rounded up).
+ * rm: the rounding mode.
+ * id: 1 (toExponential) or 2 (toPrecision).
+ */
+ function format(n, i, rm, id) {
+ var c0, e, ne, len, str;
+
+ if (rm == null) rm = ROUNDING_MODE;
+ else intCheck(rm, 0, 8);
+
+ if (!n.c) return n.toString();
+
+ c0 = n.c[0];
+ ne = n.e;
+
+ if (i == null) {
+ str = coeffToString(n.c);
+ str = id == 1 || id == 2 && (ne <= TO_EXP_NEG || ne >= TO_EXP_POS)
+ ? toExponential(str, ne)
+ : toFixedPoint(str, ne, '0');
+ } else {
+ n = round(new BigNumber(n), i, rm);
+
+ // n.e may have changed if the value was rounded up.
+ e = n.e;
+
+ str = coeffToString(n.c);
+ len = str.length;
+
+ // toPrecision returns exponential notation if the number of significant digits
+ // specified is less than the number of digits necessary to represent the integer
+ // part of the value in fixed-point notation.
+
+ // Exponential notation.
+ if (id == 1 || id == 2 && (i <= e || e <= TO_EXP_NEG)) {
+
+ // Append zeros?
+ for (; len < i; str += '0', len++);
+ str = toExponential(str, e);
+
+ // Fixed-point notation.
+ } else {
+ i -= ne;
+ str = toFixedPoint(str, e, '0');
+
+ // Append zeros?
+ if (e + 1 > len) {
+ if (--i > 0) for (str += '.'; i--; str += '0');
+ } else {
+ i += e - len;
+ if (i > 0) {
+ if (e + 1 == len) str += '.';
+ for (; i--; str += '0');
+ }
+ }
+ }
+ }
+
+ return n.s < 0 && c0 ? '-' + str : str;
+ }
+
+
+ // Handle BigNumber.max and BigNumber.min.
+ function maxOrMin(args, method) {
+ var n,
+ i = 1,
+ m = new BigNumber(args[0]);
+
+ for (; i < args.length; i++) {
+ n = new BigNumber(args[i]);
+
+ // If any number is NaN, return NaN.
+ if (!n.s) {
+ m = n;
+ break;
+ } else if (method.call(m, n)) {
+ m = n;
+ }
+ }
+
+ return m;
+ }
+
+
+ /*
+ * Strip trailing zeros, calculate base 10 exponent and check against MIN_EXP and MAX_EXP.
+ * Called by minus, plus and times.
+ */
+ function normalise(n, c, e) {
+ var i = 1,
+ j = c.length;
+
+ // Remove trailing zeros.
+ for (; !c[--j]; c.pop());
+
+ // Calculate the base 10 exponent. First get the number of digits of c[0].
+ for (j = c[0]; j >= 10; j /= 10, i++);
+
+ // Overflow?
+ if ((e = i + e * LOG_BASE - 1) > MAX_EXP) {
+
+ // Infinity.
+ n.c = n.e = null;
+
+ // Underflow?
+ } else if (e < MIN_EXP) {
+
+ // Zero.
+ n.c = [n.e = 0];
+ } else {
+ n.e = e;
+ n.c = c;
+ }
+
+ return n;
+ }
+
+
+ // Handle values that fail the validity test in BigNumber.
+ parseNumeric = (function () {
+ var basePrefix = /^(-?)0([xbo])(?=\w[\w.]*$)/i,
+ dotAfter = /^([^.]+)\.$/,
+ dotBefore = /^\.([^.]+)$/,
+ isInfinityOrNaN = /^-?(Infinity|NaN)$/,
+ whitespaceOrPlus = /^\s*\+(?=[\w.])|^\s+|\s+$/g;
+
+ return function (x, str, isNum, b) {
+ var base,
+ s = isNum ? str : str.replace(whitespaceOrPlus, '');
+
+ // No exception on ±Infinity or NaN.
+ if (isInfinityOrNaN.test(s)) {
+ x.s = isNaN(s) ? null : s < 0 ? -1 : 1;
+ } else {
+ if (!isNum) {
+
+ // basePrefix = /^(-?)0([xbo])(?=\w[\w.]*$)/i
+ s = s.replace(basePrefix, function (m, p1, p2) {
+ base = (p2 = p2.toLowerCase()) == 'x' ? 16 : p2 == 'b' ? 2 : 8;
+ return !b || b == base ? p1 : m;
+ });
+
+ if (b) {
+ base = b;
+
+ // E.g. '1.' to '1', '.1' to '0.1'
+ s = s.replace(dotAfter, '$1').replace(dotBefore, '0.$1');
+ }
+
+ if (str != s) return new BigNumber(s, base);
+ }
+
+ // '[BigNumber Error] Not a number: {n}'
+ // '[BigNumber Error] Not a base {b} number: {n}'
+ if (BigNumber.DEBUG) {
+ throw Error
+ (bignumberError + 'Not a' + (b ? ' base ' + b : '') + ' number: ' + str);
+ }
+
+ // NaN
+ x.s = null;
+ }
+
+ x.c = x.e = null;
+ }
+ })();
+
+
+ /*
+ * Round x to sd significant digits using rounding mode rm. Check for over/under-flow.
+ * If r is truthy, it is known that there are more digits after the rounding digit.
+ */
+ function round(x, sd, rm, r) {
+ var d, i, j, k, n, ni, rd,
+ xc = x.c,
+ pows10 = POWS_TEN;
+
+ // if x is not Infinity or NaN...
+ if (xc) {
+
+ // rd is the rounding digit, i.e. the digit after the digit that may be rounded up.
+ // n is a base 1e14 number, the value of the element of array x.c containing rd.
+ // ni is the index of n within x.c.
+ // d is the number of digits of n.
+ // i is the index of rd within n including leading zeros.
+ // j is the actual index of rd within n (if < 0, rd is a leading zero).
+ out: {
+
+ // Get the number of digits of the first element of xc.
+ for (d = 1, k = xc[0]; k >= 10; k /= 10, d++);
+ i = sd - d;
+
+ // If the rounding digit is in the first element of xc...
+ if (i < 0) {
+ i += LOG_BASE;
+ j = sd;
+ n = xc[ni = 0];
+
+ // Get the rounding digit at index j of n.
+ rd = n / pows10[d - j - 1] % 10 | 0;
+ } else {
+ ni = mathceil((i + 1) / LOG_BASE);
+
+ if (ni >= xc.length) {
+
+ if (r) {
+
+ // Needed by sqrt.
+ for (; xc.length <= ni; xc.push(0));
+ n = rd = 0;
+ d = 1;
+ i %= LOG_BASE;
+ j = i - LOG_BASE + 1;
+ } else {
+ break out;
+ }
+ } else {
+ n = k = xc[ni];
+
+ // Get the number of digits of n.
+ for (d = 1; k >= 10; k /= 10, d++);
+
+ // Get the index of rd within n.
+ i %= LOG_BASE;
+
+ // Get the index of rd within n, adjusted for leading zeros.
+ // The number of leading zeros of n is given by LOG_BASE - d.
+ j = i - LOG_BASE + d;
+
+ // Get the rounding digit at index j of n.
+ rd = j < 0 ? 0 : n / pows10[d - j - 1] % 10 | 0;
+ }
+ }
+
+ r = r || sd < 0 ||
+
+ // Are there any non-zero digits after the rounding digit?
+ // The expression n % pows10[d - j - 1] returns all digits of n to the right
+ // of the digit at j, e.g. if n is 908714 and j is 2, the expression gives 714.
+ xc[ni + 1] != null || (j < 0 ? n : n % pows10[d - j - 1]);
+
+ r = rm < 4
+ ? (rd || r) && (rm == 0 || rm == (x.s < 0 ? 3 : 2))
+ : rd > 5 || rd == 5 && (rm == 4 || r || rm == 6 &&
+
+ // Check whether the digit to the left of the rounding digit is odd.
+ ((i > 0 ? j > 0 ? n / pows10[d - j] : 0 : xc[ni - 1]) % 10) & 1 ||
+ rm == (x.s < 0 ? 8 : 7));
+
+ if (sd < 1 || !xc[0]) {
+ xc.length = 0;
+
+ if (r) {
+
+ // Convert sd to decimal places.
+ sd -= x.e + 1;
+
+ // 1, 0.1, 0.01, 0.001, 0.0001 etc.
+ xc[0] = pows10[(LOG_BASE - sd % LOG_BASE) % LOG_BASE];
+ x.e = -sd || 0;
+ } else {
+
+ // Zero.
+ xc[0] = x.e = 0;
+ }
+
+ return x;
+ }
+
+ // Remove excess digits.
+ if (i == 0) {
+ xc.length = ni;
+ k = 1;
+ ni--;
+ } else {
+ xc.length = ni + 1;
+ k = pows10[LOG_BASE - i];
+
+ // E.g. 56700 becomes 56000 if 7 is the rounding digit.
+ // j > 0 means i > number of leading zeros of n.
+ xc[ni] = j > 0 ? mathfloor(n / pows10[d - j] % pows10[j]) * k : 0;
+ }
+
+ // Round up?
+ if (r) {
+
+ for (; ;) {
+
+ // If the digit to be rounded up is in the first element of xc...
+ if (ni == 0) {
+
+ // i will be the length of xc[0] before k is added.
+ for (i = 1, j = xc[0]; j >= 10; j /= 10, i++);
+ j = xc[0] += k;
+ for (k = 1; j >= 10; j /= 10, k++);
+
+ // if i != k the length has increased.
+ if (i != k) {
+ x.e++;
+ if (xc[0] == BASE) xc[0] = 1;
+ }
+
+ break;
+ } else {
+ xc[ni] += k;
+ if (xc[ni] != BASE) break;
+ xc[ni--] = 0;
+ k = 1;
+ }
+ }
+ }
+
+ // Remove trailing zeros.
+ for (i = xc.length; xc[--i] === 0; xc.pop());
+ }
+
+ // Overflow? Infinity.
+ if (x.e > MAX_EXP) {
+ x.c = x.e = null;
+
+ // Underflow? Zero.
+ } else if (x.e < MIN_EXP) {
+ x.c = [x.e = 0];
+ }
+ }
+
+ return x;
+ }
+
+
+ function valueOf(n) {
+ var str,
+ e = n.e;
+
+ if (e === null) return n.toString();
+
+ str = coeffToString(n.c);
+
+ str = e <= TO_EXP_NEG || e >= TO_EXP_POS
+ ? toExponential(str, e)
+ : toFixedPoint(str, e, '0');
+
+ return n.s < 0 ? '-' + str : str;
+ }
+
+
+ // PROTOTYPE/INSTANCE METHODS
+
+
+ /*
+ * Return a new BigNumber whose value is the absolute value of this BigNumber.
+ */
+ P.absoluteValue = P.abs = function () {
+ var x = new BigNumber(this);
+ if (x.s < 0) x.s = 1;
+ return x;
+ };
+
+
+ /*
+ * Return
+ * 1 if the value of this BigNumber is greater than the value of BigNumber(y, b),
+ * -1 if the value of this BigNumber is less than the value of BigNumber(y, b),
+ * 0 if they have the same value,
+ * or null if the value of either is NaN.
+ */
+ P.comparedTo = function (y, b) {
+ return compare(this, new BigNumber(y, b));
+ };
+
+
+ /*
+ * If dp is undefined or null or true or false, return the number of decimal places of the
+ * value of this BigNumber, or null if the value of this BigNumber is ±Infinity or NaN.
+ *
+ * Otherwise, if dp is a number, return a new BigNumber whose value is the value of this
+ * BigNumber rounded to a maximum of dp decimal places using rounding mode rm, or
+ * ROUNDING_MODE if rm is omitted.
+ *
+ * [dp] {number} Decimal places: integer, 0 to MAX inclusive.
+ * [rm] {number} Rounding mode. Integer, 0 to 8 inclusive.
+ *
+ * '[BigNumber Error] Argument {not a primitive number|not an integer|out of range}: {dp|rm}'
+ */
+ P.decimalPlaces = P.dp = function (dp, rm) {
+ var c, n, v,
+ x = this;
+
+ if (dp != null) {
+ intCheck(dp, 0, MAX);
+ if (rm == null) rm = ROUNDING_MODE;
+ else intCheck(rm, 0, 8);
+
+ return round(new BigNumber(x), dp + x.e + 1, rm);
+ }
+
+ if (!(c = x.c)) return null;
+ n = ((v = c.length - 1) - bitFloor(this.e / LOG_BASE)) * LOG_BASE;
+
+ // Subtract the number of trailing zeros of the last number.
+ if (v = c[v]) for (; v % 10 == 0; v /= 10, n--);
+ if (n < 0) n = 0;
+
+ return n;
+ };
+
+
+ /*
+ * n / 0 = I
+ * n / N = N
+ * n / I = 0
+ * 0 / n = 0
+ * 0 / 0 = N
+ * 0 / N = N
+ * 0 / I = 0
+ * N / n = N
+ * N / 0 = N
+ * N / N = N
+ * N / I = N
+ * I / n = I
+ * I / 0 = I
+ * I / N = N
+ * I / I = N
+ *
+ * Return a new BigNumber whose value is the value of this BigNumber divided by the value of
+ * BigNumber(y, b), rounded according to DECIMAL_PLACES and ROUNDING_MODE.
+ */
+ P.dividedBy = P.div = function (y, b) {
+ return div(this, new BigNumber(y, b), DECIMAL_PLACES, ROUNDING_MODE);
+ };
+
+
+ /*
+ * Return a new BigNumber whose value is the integer part of dividing the value of this
+ * BigNumber by the value of BigNumber(y, b).
+ */
+ P.dividedToIntegerBy = P.idiv = function (y, b) {
+ return div(this, new BigNumber(y, b), 0, 1);
+ };
+
+
+ /*
+ * Return a BigNumber whose value is the value of this BigNumber exponentiated by n.
+ *
+ * If m is present, return the result modulo m.
+ * If n is negative round according to DECIMAL_PLACES and ROUNDING_MODE.
+ * If POW_PRECISION is non-zero and m is not present, round to POW_PRECISION using ROUNDING_MODE.
+ *
+ * The modular power operation works efficiently when x, n, and m are integers, otherwise it
+ * is equivalent to calculating x.exponentiatedBy(n).modulo(m) with a POW_PRECISION of 0.
+ *
+ * n {number|string|BigNumber} The exponent. An integer.
+ * [m] {number|string|BigNumber} The modulus.
+ *
+ * '[BigNumber Error] Exponent not an integer: {n}'
+ */
+ P.exponentiatedBy = P.pow = function (n, m) {
+ var half, isModExp, i, k, more, nIsBig, nIsNeg, nIsOdd, y,
+ x = this;
+
+ n = new BigNumber(n);
+
+ // Allow NaN and ±Infinity, but not other non-integers.
+ if (n.c && !n.isInteger()) {
+ throw Error
+ (bignumberError + 'Exponent not an integer: ' + valueOf(n));
+ }
+
+ if (m != null) m = new BigNumber(m);
+
+ // Exponent of MAX_SAFE_INTEGER is 15.
+ nIsBig = n.e > 14;
+
+ // If x is NaN, ±Infinity, ±0 or ±1, or n is ±Infinity, NaN or ±0.
+ if (!x.c || !x.c[0] || x.c[0] == 1 && !x.e && x.c.length == 1 || !n.c || !n.c[0]) {
+
+ // The sign of the result of pow when x is negative depends on the evenness of n.
+ // If +n overflows to ±Infinity, the evenness of n would be not be known.
+ y = new BigNumber(Math.pow(+valueOf(x), nIsBig ? n.s * (2 - isOdd(n)) : +valueOf(n)));
+ return m ? y.mod(m) : y;
+ }
+
+ nIsNeg = n.s < 0;
+
+ if (m) {
+
+ // x % m returns NaN if abs(m) is zero, or m is NaN.
+ if (m.c ? !m.c[0] : !m.s) return new BigNumber(NaN);
+
+ isModExp = !nIsNeg && x.isInteger() && m.isInteger();
+
+ if (isModExp) x = x.mod(m);
+
+ // Overflow to ±Infinity: >=2**1e10 or >=1.0000024**1e15.
+ // Underflow to ±0: <=0.79**1e10 or <=0.9999975**1e15.
+ } else if (n.e > 9 && (x.e > 0 || x.e < -1 || (x.e == 0
+ // [1, 240000000]
+ ? x.c[0] > 1 || nIsBig && x.c[1] >= 24e7
+ // [80000000000000] [99999750000000]
+ : x.c[0] < 8e13 || nIsBig && x.c[0] <= 9999975e7))) {
+
+ // If x is negative and n is odd, k = -0, else k = 0.
+ k = x.s < 0 && isOdd(n) ? -0 : 0;
+
+ // If x >= 1, k = ±Infinity.
+ if (x.e > -1) k = 1 / k;
+
+ // If n is negative return ±0, else return ±Infinity.
+ return new BigNumber(nIsNeg ? 1 / k : k);
+
+ } else if (POW_PRECISION) {
+
+ // Truncating each coefficient array to a length of k after each multiplication
+ // equates to truncating significant digits to POW_PRECISION + [28, 41],
+ // i.e. there will be a minimum of 28 guard digits retained.
+ k = mathceil(POW_PRECISION / LOG_BASE + 2);
+ }
+
+ if (nIsBig) {
+ half = new BigNumber(0.5);
+ if (nIsNeg) n.s = 1;
+ nIsOdd = isOdd(n);
+ } else {
+ i = Math.abs(+valueOf(n));
+ nIsOdd = i % 2;
+ }
+
+ y = new BigNumber(ONE);
+
+ // Performs 54 loop iterations for n of 9007199254740991.
+ for (; ;) {
+
+ if (nIsOdd) {
+ y = y.times(x);
+ if (!y.c) break;
+
+ if (k) {
+ if (y.c.length > k) y.c.length = k;
+ } else if (isModExp) {
+ y = y.mod(m); //y = y.minus(div(y, m, 0, MODULO_MODE).times(m));
+ }
+ }
+
+ if (i) {
+ i = mathfloor(i / 2);
+ if (i === 0) break;
+ nIsOdd = i % 2;
+ } else {
+ n = n.times(half);
+ round(n, n.e + 1, 1);
+
+ if (n.e > 14) {
+ nIsOdd = isOdd(n);
+ } else {
+ i = +valueOf(n);
+ if (i === 0) break;
+ nIsOdd = i % 2;
+ }
+ }
+
+ x = x.times(x);
+
+ if (k) {
+ if (x.c && x.c.length > k) x.c.length = k;
+ } else if (isModExp) {
+ x = x.mod(m); //x = x.minus(div(x, m, 0, MODULO_MODE).times(m));
+ }
+ }
+
+ if (isModExp) return y;
+ if (nIsNeg) y = ONE.div(y);
+
+ return m ? y.mod(m) : k ? round(y, POW_PRECISION, ROUNDING_MODE, more) : y;
+ };
+
+
+ /*
+ * Return a new BigNumber whose value is the value of this BigNumber rounded to an integer
+ * using rounding mode rm, or ROUNDING_MODE if rm is omitted.
+ *
+ * [rm] {number} Rounding mode. Integer, 0 to 8 inclusive.
+ *
+ * '[BigNumber Error] Argument {not a primitive number|not an integer|out of range}: {rm}'
+ */
+ P.integerValue = function (rm) {
+ var n = new BigNumber(this);
+ if (rm == null) rm = ROUNDING_MODE;
+ else intCheck(rm, 0, 8);
+ return round(n, n.e + 1, rm);
+ };
+
+
+ /*
+ * Return true if the value of this BigNumber is equal to the value of BigNumber(y, b),
+ * otherwise return false.
+ */
+ P.isEqualTo = P.eq = function (y, b) {
+ return compare(this, new BigNumber(y, b)) === 0;
+ };
+
+
+ /*
+ * Return true if the value of this BigNumber is a finite number, otherwise return false.
+ */
+ P.isFinite = function () {
+ return !!this.c;
+ };
+
+
+ /*
+ * Return true if the value of this BigNumber is greater than the value of BigNumber(y, b),
+ * otherwise return false.
+ */
+ P.isGreaterThan = P.gt = function (y, b) {
+ return compare(this, new BigNumber(y, b)) > 0;
+ };
+
+
+ /*
+ * Return true if the value of this BigNumber is greater than or equal to the value of
+ * BigNumber(y, b), otherwise return false.
+ */
+ P.isGreaterThanOrEqualTo = P.gte = function (y, b) {
+ return (b = compare(this, new BigNumber(y, b))) === 1 || b === 0;
+
+ };
+
+
+ /*
+ * Return true if the value of this BigNumber is an integer, otherwise return false.
+ */
+ P.isInteger = function () {
+ return !!this.c && bitFloor(this.e / LOG_BASE) > this.c.length - 2;
+ };
+
+
+ /*
+ * Return true if the value of this BigNumber is less than the value of BigNumber(y, b),
+ * otherwise return false.
+ */
+ P.isLessThan = P.lt = function (y, b) {
+ return compare(this, new BigNumber(y, b)) < 0;
+ };
+
+
+ /*
+ * Return true if the value of this BigNumber is less than or equal to the value of
+ * BigNumber(y, b), otherwise return false.
+ */
+ P.isLessThanOrEqualTo = P.lte = function (y, b) {
+ return (b = compare(this, new BigNumber(y, b))) === -1 || b === 0;
+ };
+
+
+ /*
+ * Return true if the value of this BigNumber is NaN, otherwise return false.
+ */
+ P.isNaN = function () {
+ return !this.s;
+ };
+
+
+ /*
+ * Return true if the value of this BigNumber is negative, otherwise return false.
+ */
+ P.isNegative = function () {
+ return this.s < 0;
+ };
+
+
+ /*
+ * Return true if the value of this BigNumber is positive, otherwise return false.
+ */
+ P.isPositive = function () {
+ return this.s > 0;
+ };
+
+
+ /*
+ * Return true if the value of this BigNumber is 0 or -0, otherwise return false.
+ */
+ P.isZero = function () {
+ return !!this.c && this.c[0] == 0;
+ };
+
+
+ /*
+ * n - 0 = n
+ * n - N = N
+ * n - I = -I
+ * 0 - n = -n
+ * 0 - 0 = 0
+ * 0 - N = N
+ * 0 - I = -I
+ * N - n = N
+ * N - 0 = N
+ * N - N = N
+ * N - I = N
+ * I - n = I
+ * I - 0 = I
+ * I - N = N
+ * I - I = N
+ *
+ * Return a new BigNumber whose value is the value of this BigNumber minus the value of
+ * BigNumber(y, b).
+ */
+ P.minus = function (y, b) {
+ var i, j, t, xLTy,
+ x = this,
+ a = x.s;
+
+ y = new BigNumber(y, b);
+ b = y.s;
+
+ // Either NaN?
+ if (!a || !b) return new BigNumber(NaN);
+
+ // Signs differ?
+ if (a != b) {
+ y.s = -b;
+ return x.plus(y);
+ }
+
+ var xe = x.e / LOG_BASE,
+ ye = y.e / LOG_BASE,
+ xc = x.c,
+ yc = y.c;
+
+ if (!xe || !ye) {
+
+ // Either Infinity?
+ if (!xc || !yc) return xc ? (y.s = -b, y) : new BigNumber(yc ? x : NaN);
+
+ // Either zero?
+ if (!xc[0] || !yc[0]) {
+
+ // Return y if y is non-zero, x if x is non-zero, or zero if both are zero.
+ return yc[0] ? (y.s = -b, y) : new BigNumber(xc[0] ? x :
+
+ // IEEE 754 (2008) 6.3: n - n = -0 when rounding to -Infinity
+ ROUNDING_MODE == 3 ? -0 : 0);
+ }
+ }
+
+ xe = bitFloor(xe);
+ ye = bitFloor(ye);
+ xc = xc.slice();
+
+ // Determine which is the bigger number.
+ if (a = xe - ye) {
+
+ if (xLTy = a < 0) {
+ a = -a;
+ t = xc;
+ } else {
+ ye = xe;
+ t = yc;
+ }
+
+ t.reverse();
+
+ // Prepend zeros to equalise exponents.
+ for (b = a; b--; t.push(0));
+ t.reverse();
+ } else {
+
+ // Exponents equal. Check digit by digit.
+ j = (xLTy = (a = xc.length) < (b = yc.length)) ? a : b;
+
+ for (a = b = 0; b < j; b++) {
+
+ if (xc[b] != yc[b]) {
+ xLTy = xc[b] < yc[b];
+ break;
+ }
+ }
+ }
+
+ // x < y? Point xc to the array of the bigger number.
+ if (xLTy) {
+ t = xc;
+ xc = yc;
+ yc = t;
+ y.s = -y.s;
+ }
+
+ b = (j = yc.length) - (i = xc.length);
+
+ // Append zeros to xc if shorter.
+ // No need to add zeros to yc if shorter as subtract only needs to start at yc.length.
+ if (b > 0) for (; b--; xc[i++] = 0);
+ b = BASE - 1;
+
+ // Subtract yc from xc.
+ for (; j > a;) {
+
+ if (xc[--j] < yc[j]) {
+ for (i = j; i && !xc[--i]; xc[i] = b);
+ --xc[i];
+ xc[j] += BASE;
+ }
+
+ xc[j] -= yc[j];
+ }
+
+ // Remove leading zeros and adjust exponent accordingly.
+ for (; xc[0] == 0; xc.splice(0, 1), --ye);
+
+ // Zero?
+ if (!xc[0]) {
+
+ // Following IEEE 754 (2008) 6.3,
+ // n - n = +0 but n - n = -0 when rounding towards -Infinity.
+ y.s = ROUNDING_MODE == 3 ? -1 : 1;
+ y.c = [y.e = 0];
+ return y;
+ }
+
+ // No need to check for Infinity as +x - +y != Infinity && -x - -y != Infinity
+ // for finite x and y.
+ return normalise(y, xc, ye);
+ };
+
+
+ /*
+ * n % 0 = N
+ * n % N = N
+ * n % I = n
+ * 0 % n = 0
+ * -0 % n = -0
+ * 0 % 0 = N
+ * 0 % N = N
+ * 0 % I = 0
+ * N % n = N
+ * N % 0 = N
+ * N % N = N
+ * N % I = N
+ * I % n = N
+ * I % 0 = N
+ * I % N = N
+ * I % I = N
+ *
+ * Return a new BigNumber whose value is the value of this BigNumber modulo the value of
+ * BigNumber(y, b). The result depends on the value of MODULO_MODE.
+ */
+ P.modulo = P.mod = function (y, b) {
+ var q, s,
+ x = this;
+
+ y = new BigNumber(y, b);
+
+ // Return NaN if x is Infinity or NaN, or y is NaN or zero.
+ if (!x.c || !y.s || y.c && !y.c[0]) {
+ return new BigNumber(NaN);
+
+ // Return x if y is Infinity or x is zero.
+ } else if (!y.c || x.c && !x.c[0]) {
+ return new BigNumber(x);
+ }
+
+ if (MODULO_MODE == 9) {
+
+ // Euclidian division: q = sign(y) * floor(x / abs(y))
+ // r = x - qy where 0 <= r < abs(y)
+ s = y.s;
+ y.s = 1;
+ q = div(x, y, 0, 3);
+ y.s = s;
+ q.s *= s;
+ } else {
+ q = div(x, y, 0, MODULO_MODE);
+ }
+
+ y = x.minus(q.times(y));
+
+ // To match JavaScript %, ensure sign of zero is sign of dividend.
+ if (!y.c[0] && MODULO_MODE == 1) y.s = x.s;
+
+ return y;
+ };
+
+
+ /*
+ * n * 0 = 0
+ * n * N = N
+ * n * I = I
+ * 0 * n = 0
+ * 0 * 0 = 0
+ * 0 * N = N
+ * 0 * I = N
+ * N * n = N
+ * N * 0 = N
+ * N * N = N
+ * N * I = N
+ * I * n = I
+ * I * 0 = N
+ * I * N = N
+ * I * I = I
+ *
+ * Return a new BigNumber whose value is the value of this BigNumber multiplied by the value
+ * of BigNumber(y, b).
+ */
+ P.multipliedBy = P.times = function (y, b) {
+ var c, e, i, j, k, m, xcL, xlo, xhi, ycL, ylo, yhi, zc,
+ base, sqrtBase,
+ x = this,
+ xc = x.c,
+ yc = (y = new BigNumber(y, b)).c;
+
+ // Either NaN, ±Infinity or ±0?
+ if (!xc || !yc || !xc[0] || !yc[0]) {
+
+ // Return NaN if either is NaN, or one is 0 and the other is Infinity.
+ if (!x.s || !y.s || xc && !xc[0] && !yc || yc && !yc[0] && !xc) {
+ y.c = y.e = y.s = null;
+ } else {
+ y.s *= x.s;
+
+ // Return ±Infinity if either is ±Infinity.
+ if (!xc || !yc) {
+ y.c = y.e = null;
+
+ // Return ±0 if either is ±0.
+ } else {
+ y.c = [0];
+ y.e = 0;
+ }
+ }
+
+ return y;
+ }
+
+ e = bitFloor(x.e / LOG_BASE) + bitFloor(y.e / LOG_BASE);
+ y.s *= x.s;
+ xcL = xc.length;
+ ycL = yc.length;
+
+ // Ensure xc points to longer array and xcL to its length.
+ if (xcL < ycL) {
+ zc = xc;
+ xc = yc;
+ yc = zc;
+ i = xcL;
+ xcL = ycL;
+ ycL = i;
+ }
+
+ // Initialise the result array with zeros.
+ for (i = xcL + ycL, zc = []; i--; zc.push(0));
+
+ base = BASE;
+ sqrtBase = SQRT_BASE;
+
+ for (i = ycL; --i >= 0;) {
+ c = 0;
+ ylo = yc[i] % sqrtBase;
+ yhi = yc[i] / sqrtBase | 0;
+
+ for (k = xcL, j = i + k; j > i;) {
+ xlo = xc[--k] % sqrtBase;
+ xhi = xc[k] / sqrtBase | 0;
+ m = yhi * xlo + xhi * ylo;
+ xlo = ylo * xlo + ((m % sqrtBase) * sqrtBase) + zc[j] + c;
+ c = (xlo / base | 0) + (m / sqrtBase | 0) + yhi * xhi;
+ zc[j--] = xlo % base;
+ }
+
+ zc[j] = c;
+ }
+
+ if (c) {
+ ++e;
+ } else {
+ zc.splice(0, 1);
+ }
+
+ return normalise(y, zc, e);
+ };
+
+
+ /*
+ * Return a new BigNumber whose value is the value of this BigNumber negated,
+ * i.e. multiplied by -1.
+ */
+ P.negated = function () {
+ var x = new BigNumber(this);
+ x.s = -x.s || null;
+ return x;
+ };
+
+
+ /*
+ * n + 0 = n
+ * n + N = N
+ * n + I = I
+ * 0 + n = n
+ * 0 + 0 = 0
+ * 0 + N = N
+ * 0 + I = I
+ * N + n = N
+ * N + 0 = N
+ * N + N = N
+ * N + I = N
+ * I + n = I
+ * I + 0 = I
+ * I + N = N
+ * I + I = I
+ *
+ * Return a new BigNumber whose value is the value of this BigNumber plus the value of
+ * BigNumber(y, b).
+ */
+ P.plus = function (y, b) {
+ var t,
+ x = this,
+ a = x.s;
+
+ y = new BigNumber(y, b);
+ b = y.s;
+
+ // Either NaN?
+ if (!a || !b) return new BigNumber(NaN);
+
+ // Signs differ?
+ if (a != b) {
+ y.s = -b;
+ return x.minus(y);
+ }
+
+ var xe = x.e / LOG_BASE,
+ ye = y.e / LOG_BASE,
+ xc = x.c,
+ yc = y.c;
+
+ if (!xe || !ye) {
+
+ // Return ±Infinity if either ±Infinity.
+ if (!xc || !yc) return new BigNumber(a / 0);
+
+ // Either zero?
+ // Return y if y is non-zero, x if x is non-zero, or zero if both are zero.
+ if (!xc[0] || !yc[0]) return yc[0] ? y : new BigNumber(xc[0] ? x : a * 0);
+ }
+
+ xe = bitFloor(xe);
+ ye = bitFloor(ye);
+ xc = xc.slice();
+
+ // Prepend zeros to equalise exponents. Faster to use reverse then do unshifts.
+ if (a = xe - ye) {
+ if (a > 0) {
+ ye = xe;
+ t = yc;
+ } else {
+ a = -a;
+ t = xc;
+ }
+
+ t.reverse();
+ for (; a--; t.push(0));
+ t.reverse();
+ }
+
+ a = xc.length;
+ b = yc.length;
+
+ // Point xc to the longer array, and b to the shorter length.
+ if (a - b < 0) {
+ t = yc;
+ yc = xc;
+ xc = t;
+ b = a;
+ }
+
+ // Only start adding at yc.length - 1 as the further digits of xc can be ignored.
+ for (a = 0; b;) {
+ a = (xc[--b] = xc[b] + yc[b] + a) / BASE | 0;
+ xc[b] = BASE === xc[b] ? 0 : xc[b] % BASE;
+ }
+
+ if (a) {
+ xc = [a].concat(xc);
+ ++ye;
+ }
+
+ // No need to check for zero, as +x + +y != 0 && -x + -y != 0
+ // ye = MAX_EXP + 1 possible
+ return normalise(y, xc, ye);
+ };
+
+
+ /*
+ * If sd is undefined or null or true or false, return the number of significant digits of
+ * the value of this BigNumber, or null if the value of this BigNumber is ±Infinity or NaN.
+ * If sd is true include integer-part trailing zeros in the count.
+ *
+ * Otherwise, if sd is a number, return a new BigNumber whose value is the value of this
+ * BigNumber rounded to a maximum of sd significant digits using rounding mode rm, or
+ * ROUNDING_MODE if rm is omitted.
+ *
+ * sd {number|boolean} number: significant digits: integer, 1 to MAX inclusive.
+ * boolean: whether to count integer-part trailing zeros: true or false.
+ * [rm] {number} Rounding mode. Integer, 0 to 8 inclusive.
+ *
+ * '[BigNumber Error] Argument {not a primitive number|not an integer|out of range}: {sd|rm}'
+ */
+ P.precision = P.sd = function (sd, rm) {
+ var c, n, v,
+ x = this;
+
+ if (sd != null && sd !== !!sd) {
+ intCheck(sd, 1, MAX);
+ if (rm == null) rm = ROUNDING_MODE;
+ else intCheck(rm, 0, 8);
+
+ return round(new BigNumber(x), sd, rm);
+ }
+
+ if (!(c = x.c)) return null;
+ v = c.length - 1;
+ n = v * LOG_BASE + 1;
+
+ if (v = c[v]) {
+
+ // Subtract the number of trailing zeros of the last element.
+ for (; v % 10 == 0; v /= 10, n--);
+
+ // Add the number of digits of the first element.
+ for (v = c[0]; v >= 10; v /= 10, n++);
+ }
+
+ if (sd && x.e + 1 > n) n = x.e + 1;
+
+ return n;
+ };
+
+
+ /*
+ * Return a new BigNumber whose value is the value of this BigNumber shifted by k places
+ * (powers of 10). Shift to the right if n > 0, and to the left if n < 0.
+ *
+ * k {number} Integer, -MAX_SAFE_INTEGER to MAX_SAFE_INTEGER inclusive.
+ *
+ * '[BigNumber Error] Argument {not a primitive number|not an integer|out of range}: {k}'
+ */
+ P.shiftedBy = function (k) {
+ intCheck(k, -MAX_SAFE_INTEGER, MAX_SAFE_INTEGER);
+ return this.times('1e' + k);
+ };
+
+
+ /*
+ * sqrt(-n) = N
+ * sqrt(N) = N
+ * sqrt(-I) = N
+ * sqrt(I) = I
+ * sqrt(0) = 0
+ * sqrt(-0) = -0
+ *
+ * Return a new BigNumber whose value is the square root of the value of this BigNumber,
+ * rounded according to DECIMAL_PLACES and ROUNDING_MODE.
+ */
+ P.squareRoot = P.sqrt = function () {
+ var m, n, r, rep, t,
+ x = this,
+ c = x.c,
+ s = x.s,
+ e = x.e,
+ dp = DECIMAL_PLACES + 4,
+ half = new BigNumber('0.5');
+
+ // Negative/NaN/Infinity/zero?
+ if (s !== 1 || !c || !c[0]) {
+ return new BigNumber(!s || s < 0 && (!c || c[0]) ? NaN : c ? x : 1 / 0);
+ }
+
+ // Initial estimate.
+ s = Math.sqrt(+valueOf(x));
+
+ // Math.sqrt underflow/overflow?
+ // Pass x to Math.sqrt as integer, then adjust the exponent of the result.
+ if (s == 0 || s == 1 / 0) {
+ n = coeffToString(c);
+ if ((n.length + e) % 2 == 0) n += '0';
+ s = Math.sqrt(+n);
+ e = bitFloor((e + 1) / 2) - (e < 0 || e % 2);
+
+ if (s == 1 / 0) {
+ n = '5e' + e;
+ } else {
+ n = s.toExponential();
+ n = n.slice(0, n.indexOf('e') + 1) + e;
+ }
+
+ r = new BigNumber(n);
+ } else {
+ r = new BigNumber(s + '');
+ }
+
+ // Check for zero.
+ // r could be zero if MIN_EXP is changed after the this value was created.
+ // This would cause a division by zero (x/t) and hence Infinity below, which would cause
+ // coeffToString to throw.
+ if (r.c[0]) {
+ e = r.e;
+ s = e + dp;
+ if (s < 3) s = 0;
+
+ // Newton-Raphson iteration.
+ for (; ;) {
+ t = r;
+ r = half.times(t.plus(div(x, t, dp, 1)));
+
+ if (coeffToString(t.c).slice(0, s) === (n = coeffToString(r.c)).slice(0, s)) {
+
+ // The exponent of r may here be one less than the final result exponent,
+ // e.g 0.0009999 (e-4) --> 0.001 (e-3), so adjust s so the rounding digits
+ // are indexed correctly.
+ if (r.e < e) --s;
+ n = n.slice(s - 3, s + 1);
+
+ // The 4th rounding digit may be in error by -1 so if the 4 rounding digits
+ // are 9999 or 4999 (i.e. approaching a rounding boundary) continue the
+ // iteration.
+ if (n == '9999' || !rep && n == '4999') {
+
+ // On the first iteration only, check to see if rounding up gives the
+ // exact result as the nines may infinitely repeat.
+ if (!rep) {
+ round(t, t.e + DECIMAL_PLACES + 2, 0);
+
+ if (t.times(t).eq(x)) {
+ r = t;
+ break;
+ }
+ }
+
+ dp += 4;
+ s += 4;
+ rep = 1;
+ } else {
+
+ // If rounding digits are null, 0{0,4} or 50{0,3}, check for exact
+ // result. If not, then there are further digits and m will be truthy.
+ if (!+n || !+n.slice(1) && n.charAt(0) == '5') {
+
+ // Truncate to the first rounding digit.
+ round(r, r.e + DECIMAL_PLACES + 2, 1);
+ m = !r.times(r).eq(x);
+ }
+
+ break;
+ }
+ }
+ }
+ }
+
+ return round(r, r.e + DECIMAL_PLACES + 1, ROUNDING_MODE, m);
+ };
+
+
+ /*
+ * Return a string representing the value of this BigNumber in exponential notation and
+ * rounded using ROUNDING_MODE to dp fixed decimal places.
+ *
+ * [dp] {number} Decimal places. Integer, 0 to MAX inclusive.
+ * [rm] {number} Rounding mode. Integer, 0 to 8 inclusive.
+ *
+ * '[BigNumber Error] Argument {not a primitive number|not an integer|out of range}: {dp|rm}'
+ */
+ P.toExponential = function (dp, rm) {
+ if (dp != null) {
+ intCheck(dp, 0, MAX);
+ dp++;
+ }
+ return format(this, dp, rm, 1);
+ };
+
+
+ /*
+ * Return a string representing the value of this BigNumber in fixed-point notation rounding
+ * to dp fixed decimal places using rounding mode rm, or ROUNDING_MODE if rm is omitted.
+ *
+ * Note: as with JavaScript's number type, (-0).toFixed(0) is '0',
+ * but e.g. (-0.00001).toFixed(0) is '-0'.
+ *
+ * [dp] {number} Decimal places. Integer, 0 to MAX inclusive.
+ * [rm] {number} Rounding mode. Integer, 0 to 8 inclusive.
+ *
+ * '[BigNumber Error] Argument {not a primitive number|not an integer|out of range}: {dp|rm}'
+ */
+ P.toFixed = function (dp, rm) {
+ if (dp != null) {
+ intCheck(dp, 0, MAX);
+ dp = dp + this.e + 1;
+ }
+ return format(this, dp, rm);
+ };
+
+
+ /*
+ * Return a string representing the value of this BigNumber in fixed-point notation rounded
+ * using rm or ROUNDING_MODE to dp decimal places, and formatted according to the properties
+ * of the format or FORMAT object (see BigNumber.set).
+ *
+ * The formatting object may contain some or all of the properties shown below.
+ *
+ * FORMAT = {
+ * prefix: '',
+ * groupSize: 3,
+ * secondaryGroupSize: 0,
+ * groupSeparator: ',',
+ * decimalSeparator: '.',
+ * fractionGroupSize: 0,
+ * fractionGroupSeparator: '\xA0', // non-breaking space
+ * suffix: ''
+ * };
+ *
+ * [dp] {number} Decimal places. Integer, 0 to MAX inclusive.
+ * [rm] {number} Rounding mode. Integer, 0 to 8 inclusive.
+ * [format] {object} Formatting options. See FORMAT pbject above.
+ *
+ * '[BigNumber Error] Argument {not a primitive number|not an integer|out of range}: {dp|rm}'
+ * '[BigNumber Error] Argument not an object: {format}'
+ */
+ P.toFormat = function (dp, rm, format) {
+ var str,
+ x = this;
+
+ if (format == null) {
+ if (dp != null && rm && typeof rm == 'object') {
+ format = rm;
+ rm = null;
+ } else if (dp && typeof dp == 'object') {
+ format = dp;
+ dp = rm = null;
+ } else {
+ format = FORMAT;
+ }
+ } else if (typeof format != 'object') {
+ throw Error
+ (bignumberError + 'Argument not an object: ' + format);
+ }
+
+ str = x.toFixed(dp, rm);
+
+ if (x.c) {
+ var i,
+ arr = str.split('.'),
+ g1 = +format.groupSize,
+ g2 = +format.secondaryGroupSize,
+ groupSeparator = format.groupSeparator || '',
+ intPart = arr[0],
+ fractionPart = arr[1],
+ isNeg = x.s < 0,
+ intDigits = isNeg ? intPart.slice(1) : intPart,
+ len = intDigits.length;
+
+ if (g2) {
+ i = g1;
+ g1 = g2;
+ g2 = i;
+ len -= i;
+ }
+
+ if (g1 > 0 && len > 0) {
+ i = len % g1 || g1;
+ intPart = intDigits.substr(0, i);
+ for (; i < len; i += g1) intPart += groupSeparator + intDigits.substr(i, g1);
+ if (g2 > 0) intPart += groupSeparator + intDigits.slice(i);
+ if (isNeg) intPart = '-' + intPart;
+ }
+
+ str = fractionPart
+ ? intPart + (format.decimalSeparator || '') + ((g2 = +format.fractionGroupSize)
+ ? fractionPart.replace(new RegExp('\\d{' + g2 + '}\\B', 'g'),
+ '$&' + (format.fractionGroupSeparator || ''))
+ : fractionPart)
+ : intPart;
+ }
+
+ return (format.prefix || '') + str + (format.suffix || '');
+ };
+
+
+ /*
+ * Return an array of two BigNumbers representing the value of this BigNumber as a simple
+ * fraction with an integer numerator and an integer denominator.
+ * The denominator will be a positive non-zero value less than or equal to the specified
+ * maximum denominator. If a maximum denominator is not specified, the denominator will be
+ * the lowest value necessary to represent the number exactly.
+ *
+ * [md] {number|string|BigNumber} Integer >= 1, or Infinity. The maximum denominator.
+ *
+ * '[BigNumber Error] Argument {not an integer|out of range} : {md}'
+ */
+ P.toFraction = function (md) {
+ var d, d0, d1, d2, e, exp, n, n0, n1, q, r, s,
+ x = this,
+ xc = x.c;
+
+ if (md != null) {
+ n = new BigNumber(md);
+
+ // Throw if md is less than one or is not an integer, unless it is Infinity.
+ if (!n.isInteger() && (n.c || n.s !== 1) || n.lt(ONE)) {
+ throw Error
+ (bignumberError + 'Argument ' +
+ (n.isInteger() ? 'out of range: ' : 'not an integer: ') + valueOf(n));
+ }
+ }
+
+ if (!xc) return new BigNumber(x);
+
+ d = new BigNumber(ONE);
+ n1 = d0 = new BigNumber(ONE);
+ d1 = n0 = new BigNumber(ONE);
+ s = coeffToString(xc);
+
+ // Determine initial denominator.
+ // d is a power of 10 and the minimum max denominator that specifies the value exactly.
+ e = d.e = s.length - x.e - 1;
+ d.c[0] = POWS_TEN[(exp = e % LOG_BASE) < 0 ? LOG_BASE + exp : exp];
+ md = !md || n.comparedTo(d) > 0 ? (e > 0 ? d : n1) : n;
+
+ exp = MAX_EXP;
+ MAX_EXP = 1 / 0;
+ n = new BigNumber(s);
+
+ // n0 = d1 = 0
+ n0.c[0] = 0;
+
+ for (; ;) {
+ q = div(n, d, 0, 1);
+ d2 = d0.plus(q.times(d1));
+ if (d2.comparedTo(md) == 1) break;
+ d0 = d1;
+ d1 = d2;
+ n1 = n0.plus(q.times(d2 = n1));
+ n0 = d2;
+ d = n.minus(q.times(d2 = d));
+ n = d2;
+ }
+
+ d2 = div(md.minus(d0), d1, 0, 1);
+ n0 = n0.plus(d2.times(n1));
+ d0 = d0.plus(d2.times(d1));
+ n0.s = n1.s = x.s;
+ e = e * 2;
+
+ // Determine which fraction is closer to x, n0/d0 or n1/d1
+ r = div(n1, d1, e, ROUNDING_MODE).minus(x).abs().comparedTo(
+ div(n0, d0, e, ROUNDING_MODE).minus(x).abs()) < 1 ? [n1, d1] : [n0, d0];
+
+ MAX_EXP = exp;
+
+ return r;
+ };
+
+
+ /*
+ * Return the value of this BigNumber converted to a number primitive.
+ */
+ P.toNumber = function () {
+ return +valueOf(this);
+ };
+
+
+ /*
+ * Return a string representing the value of this BigNumber rounded to sd significant digits
+ * using rounding mode rm or ROUNDING_MODE. If sd is less than the number of digits
+ * necessary to represent the integer part of the value in fixed-point notation, then use
+ * exponential notation.
+ *
+ * [sd] {number} Significant digits. Integer, 1 to MAX inclusive.
+ * [rm] {number} Rounding mode. Integer, 0 to 8 inclusive.
+ *
+ * '[BigNumber Error] Argument {not a primitive number|not an integer|out of range}: {sd|rm}'
+ */
+ P.toPrecision = function (sd, rm) {
+ if (sd != null) intCheck(sd, 1, MAX);
+ return format(this, sd, rm, 2);
+ };
+
+
+ /*
+ * Return a string representing the value of this BigNumber in base b, or base 10 if b is
+ * omitted. If a base is specified, including base 10, round according to DECIMAL_PLACES and
+ * ROUNDING_MODE. If a base is not specified, and this BigNumber has a positive exponent
+ * that is equal to or greater than TO_EXP_POS, or a negative exponent equal to or less than
+ * TO_EXP_NEG, return exponential notation.
+ *
+ * [b] {number} Integer, 2 to ALPHABET.length inclusive.
+ *
+ * '[BigNumber Error] Base {not a primitive number|not an integer|out of range}: {b}'
+ */
+ P.toString = function (b) {
+ var str,
+ n = this,
+ s = n.s,
+ e = n.e;
+
+ // Infinity or NaN?
+ if (e === null) {
+ if (s) {
+ str = 'Infinity';
+ if (s < 0) str = '-' + str;
+ } else {
+ str = 'NaN';
+ }
+ } else {
+ if (b == null) {
+ str = e <= TO_EXP_NEG || e >= TO_EXP_POS
+ ? toExponential(coeffToString(n.c), e)
+ : toFixedPoint(coeffToString(n.c), e, '0');
+ } else if (b === 10 && alphabetHasNormalDecimalDigits) {
+ n = round(new BigNumber(n), DECIMAL_PLACES + e + 1, ROUNDING_MODE);
+ str = toFixedPoint(coeffToString(n.c), n.e, '0');
+ } else {
+ intCheck(b, 2, ALPHABET.length, 'Base');
+ str = convertBase(toFixedPoint(coeffToString(n.c), e, '0'), 10, b, s, true);
+ }
+
+ if (s < 0 && n.c[0]) str = '-' + str;
+ }
+
+ return str;
+ };
+
+
+ /*
+ * Return as toString, but do not accept a base argument, and include the minus sign for
+ * negative zero.
+ */
+ P.valueOf = P.toJSON = function () {
+ return valueOf(this);
+ };
+
+
+ P._isBigNumber = true;
+
+ if (configObject != null) BigNumber.set(configObject);
+
+ return BigNumber;
+ }
+
+
+ // PRIVATE HELPER FUNCTIONS
+
+ // These functions don't need access to variables,
+ // e.g. DECIMAL_PLACES, in the scope of the `clone` function above.
+
+
+ function bitFloor(n) {
+ var i = n | 0;
+ return n > 0 || n === i ? i : i - 1;
+ }
+
+
+ // Return a coefficient array as a string of base 10 digits.
+ function coeffToString(a) {
+ var s, z,
+ i = 1,
+ j = a.length,
+ r = a[0] + '';
+
+ for (; i < j;) {
+ s = a[i++] + '';
+ z = LOG_BASE - s.length;
+ for (; z--; s = '0' + s);
+ r += s;
+ }
+
+ // Determine trailing zeros.
+ for (j = r.length; r.charCodeAt(--j) === 48;);
+
+ return r.slice(0, j + 1 || 1);
+ }
+
+
+ // Compare the value of BigNumbers x and y.
+ function compare(x, y) {
+ var a, b,
+ xc = x.c,
+ yc = y.c,
+ i = x.s,
+ j = y.s,
+ k = x.e,
+ l = y.e;
+
+ // Either NaN?
+ if (!i || !j) return null;
+
+ a = xc && !xc[0];
+ b = yc && !yc[0];
+
+ // Either zero?
+ if (a || b) return a ? b ? 0 : -j : i;
+
+ // Signs differ?
+ if (i != j) return i;
+
+ a = i < 0;
+ b = k == l;
+
+ // Either Infinity?
+ if (!xc || !yc) return b ? 0 : !xc ^ a ? 1 : -1;
+
+ // Compare exponents.
+ if (!b) return k > l ^ a ? 1 : -1;
+
+ j = (k = xc.length) < (l = yc.length) ? k : l;
+
+ // Compare digit by digit.
+ for (i = 0; i < j; i++) if (xc[i] != yc[i]) return xc[i] > yc[i] ^ a ? 1 : -1;
+
+ // Compare lengths.
+ return k == l ? 0 : k > l ^ a ? 1 : -1;
+ }
+
+
+ /*
+ * Check that n is a primitive number, an integer, and in range, otherwise throw.
+ */
+ function intCheck(n, min, max, name) {
+ if (n < min || n > max || n !== mathfloor(n)) {
+ throw Error
+ (bignumberError + (name || 'Argument') + (typeof n == 'number'
+ ? n < min || n > max ? ' out of range: ' : ' not an integer: '
+ : ' not a primitive number: ') + String(n));
+ }
+ }
+
+
+ // Assumes finite n.
+ function isOdd(n) {
+ var k = n.c.length - 1;
+ return bitFloor(n.e / LOG_BASE) == k && n.c[k] % 2 != 0;
+ }
+
+
+ function toExponential(str, e) {
+ return (str.length > 1 ? str.charAt(0) + '.' + str.slice(1) : str) +
+ (e < 0 ? 'e' : 'e+') + e;
+ }
+
+
+ function toFixedPoint(str, e, z) {
+ var len, zs;
+
+ // Negative exponent?
+ if (e < 0) {
+
+ // Prepend zeros.
+ for (zs = z + '.'; ++e; zs += z);
+ str = zs + str;
+
+ // Positive exponent
+ } else {
+ len = str.length;
+
+ // Append zeros.
+ if (++e > len) {
+ for (zs = z, e -= len; --e; zs += z);
+ str += zs;
+ } else if (e < len) {
+ str = str.slice(0, e) + '.' + str.slice(e);
+ }
+ }
+
+ return str;
+ }
+
+
+ // EXPORT
+
+
+ BigNumber = clone();
+ BigNumber['default'] = BigNumber.BigNumber = BigNumber;
+
+ // AMD.
+ if (typeof define == 'function' && define.amd) {
+ define(function () { return BigNumber; });
+
+ // Node.js and other environments that support module.exports.
+ } else if (typeof module != 'undefined' && module.exports) {
+ module.exports = BigNumber;
+
+ // Browser.
+ } else {
+ if (!globalObject) {
+ globalObject = typeof self != 'undefined' && self ? self : window;
+ }
+
+ globalObject.BigNumber = BigNumber;
+ }
+})(this);
diff --git a/scripts/bit_reader.js b/scripts/bit_reader.js
new file mode 100755
index 0000000..e7202b1
--- /dev/null
+++ b/scripts/bit_reader.js
@@ -0,0 +1,124 @@
+// Use strict
+"use strict";
+
+
+// Classes
+
+// Bit reader class
+class BitReader {
+
+ // Public
+
+ // Constructor
+ constructor(data) {
+
+ // Set data
+ this.data = data;
+
+ // Initialize byte index
+ this.byteIndex = 0;
+
+ // Initialize bit index
+ this.bitIndex = 0;
+ }
+
+ // Get bits
+ getBits(numberOfBits) {
+
+ // Check if more than one byte is requested
+ if(numberOfBits > Common.BITS_IN_A_BYTE) {
+
+ // Initialize result
+ var result = 0;
+
+ // Go through all bytes
+ while(numberOfBits > 0) {
+
+ // Update result to make space for more bits
+ result <<= Math.min(numberOfBits, Common.BITS_IN_A_BYTE);
+
+ // Include bits in result
+ result |= this.getBits(Math.min(numberOfBits, Common.BITS_IN_A_BYTE));
+
+ // Update number of bits
+ numberOfBits -= Common.BITS_IN_A_BYTE;
+ }
+
+ // Return result
+ return result;
+ }
+
+ // Otherwise
+ else {
+
+ // Check if no bits requested
+ if(numberOfBits === 0) {
+
+ // Return zero
+ return 0;
+ }
+
+ // Check if number of bits is invalid
+ if(this.byteIndex === this.data["length"] || (this.byteIndex === this.data["length"] - 1 && this.bitIndex + numberOfBits > Common.BITS_IN_A_BYTE)) {
+
+ // Throw error
+ throw "Invalid number of bits.";
+ }
+
+ // Initialize result to data at the byte index
+ var result = this.data[this.byteIndex] << Common.BITS_IN_A_BYTE;
+
+ // Check if more data is needed
+ if(this.bitIndex + numberOfBits > Common.BITS_IN_A_BYTE) {
+
+ // Append next byte to the result
+ result |= this.data[this.byteIndex + 1];
+ }
+
+ // Remove upper bits from result
+ result &= (1 << (Common.BITS_IN_A_BYTE * 2 - this.bitIndex)) - 1;
+
+ // Remove lower bits from result
+ result >>>= (Common.BITS_IN_A_BYTE * 2 - (this.bitIndex + numberOfBits));
+
+ // Update bit index
+ this.bitIndex += numberOfBits;
+
+ // Check if bit index overflowed into the next byte
+ if(this.bitIndex >= Common.BITS_IN_A_BYTE) {
+
+ // Increment byte index
+ ++this.byteIndex;
+
+ // Correct bit index
+ this.bitIndex %= Common.BITS_IN_A_BYTE;
+ }
+
+ // Return result
+ return result;
+ }
+ }
+
+ // Get bytes
+ getBytes(numberOfBytes) {
+
+ // Initialize result
+ var result = new Uint8Array(numberOfBytes);
+
+ // Go through all bytes
+ for(var i = 0; i < numberOfBytes; ++i) {
+
+ // Set byte in the result
+ result[i] = this.getBits(Common.BITS_IN_A_BYTE);
+ }
+
+ // Return result
+ return result;
+ }
+}
+
+
+// Main function
+
+// Set global object's bit reader
+globalThis["BitReader"] = BitReader;
diff --git a/scripts/bit_writer.js b/scripts/bit_writer.js
new file mode 100755
index 0000000..818dc63
--- /dev/null
+++ b/scripts/bit_writer.js
@@ -0,0 +1,110 @@
+// Use strict
+"use strict";
+
+
+// Classes
+
+// Bit writer class
+class BitWriter {
+
+ // Public
+
+ // Constructor
+ constructor() {
+
+ // Set data
+ this.data = new Uint8Array([]);
+
+ // Initialize byte index
+ this.byteIndex = 0;
+
+ // Initialize bit index
+ this.bitIndex = 0;
+ }
+
+ // Set bits
+ setBits(value, numberOfBits) {
+
+ // Go through all bytes past one byte
+ while(numberOfBits > Common.BITS_IN_A_BYTE) {
+
+ // Set byte
+ this.setBits(value >>> (Common.BITS_IN_A_BYTE * (Math.floor(numberOfBits / Common.BITS_IN_A_BYTE) - 1) + numberOfBits % Common.BITS_IN_A_BYTE), Math.min(numberOfBits, Common.BITS_IN_A_BYTE));
+
+ // Update number of bits
+ numberOfBits -= Common.BITS_IN_A_BYTE;
+ }
+
+ // Check if no bits requested
+ if(numberOfBits === 0) {
+
+ // Return
+ return;
+ }
+
+ // Check if more data is needed
+ if(this.bitIndex === 0 || this.bitIndex + numberOfBits > Common.BITS_IN_A_BYTE) {
+
+ // Increase data's size by one
+ var temp = new Uint8Array(this.data["length"] + 1);
+
+ temp.set(this.data);
+
+ this.data = temp;
+ }
+
+ // Check if value will overflow into the next byte
+ if(this.bitIndex + numberOfBits > Common.BITS_IN_A_BYTE) {
+
+ // Include data in value at byte index
+ this.data[this.byteIndex] |= value >>> ((this.bitIndex + numberOfBits) - Common.BITS_IN_A_BYTE);
+
+ // Include data in value at next byte index
+ this.data[this.byteIndex + 1] |= value << (Common.BITS_IN_A_BYTE * 2 - (this.bitIndex + numberOfBits));
+ }
+
+ // Otherwise
+ else {
+
+ // Include data in value at byte index
+ this.data[this.byteIndex] |= value << (Common.BITS_IN_A_BYTE - (this.bitIndex + numberOfBits));
+ }
+
+ // Update bit index
+ this.bitIndex += numberOfBits;
+
+ // Check if bit index overflowed into the next byte
+ if(this.bitIndex >= Common.BITS_IN_A_BYTE) {
+
+ // Increment byte index
+ ++this.byteIndex;
+
+ // Correct bit index
+ this.bitIndex %= Common.BITS_IN_A_BYTE;
+ }
+ }
+
+ // Set bytes
+ setBytes(bytes) {
+
+ // Go through all bytes
+ for(var i = 0; i < bytes["length"]; ++i) {
+
+ // Set byte in the data
+ this.setBits(bytes[i], Common.BITS_IN_A_BYTE);
+ }
+ }
+
+ // Get bytes
+ getBytes() {
+
+ // Return data
+ return this.data;
+ }
+}
+
+
+// Main function
+
+// Set global object's bit writer
+globalThis["BitWriter"] = BitWriter;
diff --git a/scripts/camera.js b/scripts/camera.js
new file mode 100755
index 0000000..abf57fa
--- /dev/null
+++ b/scripts/camera.js
@@ -0,0 +1,434 @@
+// Use strict
+"use strict";
+
+
+// Classes
+
+// Camera class
+class Camera {
+
+ // Public
+
+ // Initialize
+ static initialize() {
+
+ // Initialize request index
+ Camera.requestIndex = 0;
+
+ // Create worker
+ Camera.worker = new Worker(Camera.WORKER_FILE_LOCATION);
+
+ // Window before unload event
+ $(window).on("beforeunload", function() {
+
+ // Get current request index
+ var currentRequestIndex = Camera.requestIndex++;
+
+ // Check if current request index is at the max safe integer
+ if(currentRequestIndex === Number.MAX_SAFE_INTEGER)
+
+ // Reset request index
+ Camera.requestIndex = 0;
+
+ // Send worker an uninitialize request
+ Camera.worker.postMessage([
+
+ // Request index
+ currentRequestIndex,
+
+ // Type
+ Camera.UNINITIALIZE_REQUEST_TYPE
+ ]);
+
+ // Terminate worker
+ Camera.worker.terminate();
+ });
+
+ // Return promise
+ return new Promise(function(resolve, reject) {
+
+ // Worker on error
+ Camera.worker["onerror"] = function() {
+
+ // Reject error
+ reject("Failed to create camera worker.");
+ };
+
+ // Worker on message
+ Camera.worker["onmessage"] = function(event) {
+
+ // Get message
+ var message = event["data"];
+
+ // Get message's request index
+ var requestIndex = message[Camera.MESSAGE_REQUEST_INDEX_OFFSET];
+
+ // Check message's type
+ switch(message[Camera.MESSAGE_TYPE_OFFSET]) {
+
+ // Initialize request type
+ case Camera.INITIALIZE_REQUEST_TYPE:
+
+ // Get message's status
+ var status = message[Camera.MESSAGE_STATUS_OFFSET];
+
+ // Check if worker initialized successfully
+ if(status === Camera.STATUS_SUCCESS_VALUE)
+
+ // Resolve
+ resolve();
+
+ // Otherwise
+ else
+
+ // Reject error
+ reject("Failed to initialize camera worker.");
+
+ // Break
+ break;
+
+ // Default
+ default:
+
+ // Get message's response
+ var response = message[Camera.MESSAGE_RESPONSE_OFFSET];
+
+ // Trigger response request index event
+ $(document).trigger(Camera.RESPONSE_EVENT + requestIndex.toFixed(), [
+
+ // Response
+ response
+ ]);
+
+ // Break
+ break;
+ }
+ };
+
+ // Get current request index
+ var currentRequestIndex = Camera.requestIndex++;
+
+ // Check if current request index is at the max safe integer
+ if(currentRequestIndex === Number.MAX_SAFE_INTEGER)
+
+ // Reset request index
+ Camera.requestIndex = 0;
+
+ // Send worker an initialize request
+ Camera.worker.postMessage([
+
+ // Request index
+ currentRequestIndex,
+
+ // Type
+ Camera.INITIALIZE_REQUEST_TYPE,
+
+ // URL query string
+ Common.URL_QUERY_STRING_SEPARATOR + encodeURIComponent(Consensus.OVERRIDE_WALLET_TYPE_URL_PARAMETER_NAME).replace(/%20/ug, "+") + Common.URL_QUERY_STRING_PARAMETER_VALUE_SEPARATOR + encodeURIComponent(Consensus.walletTypeToText(Consensus.getWalletType())).replace(/%20/ug, "+") + Common.URL_QUERY_STRING_PARAMETER_SEPARATOR + encodeURIComponent(Consensus.OVERRIDE_NETWORK_TYPE_URL_PARAMETER_NAME).replace(/%20/ug, "+") + Common.URL_QUERY_STRING_PARAMETER_VALUE_SEPARATOR + encodeURIComponent(Consensus.networkTypeToText(Consensus.getNetworkType())).replace(/%20/ug, "+")
+ ]);
+ });
+ }
+
+ // Get value
+ static getValue(image, width, height) {
+
+ // Create canvas
+ var canvas = document.createElement("canvas");
+
+ // Set canvas's dimensions
+ canvas["width"] = width;
+ canvas["height"] = height;
+
+ // Draw image on canvas
+ var context = canvas.getContext("2d");
+ context.drawImage(image, 0, 0, width, height);
+
+ // Get image's data from the canvas
+ var imageData = context.getImageData(0, 0, width, height)["data"]["buffer"];
+
+ // Return promise
+ return new Promise(function(resolve, reject) {
+
+ // Return sending decode request
+ return Camera.sendRequest([
+
+ // Type
+ Camera.DECODE_REQUEST_TYPE,
+
+ // Width
+ width,
+
+ // Height
+ height,
+
+ // Image data
+ imageData
+ ], [
+
+ // Image data
+ imageData
+
+ ]).then(function(response) {
+
+ // Check if response is no value
+ if(response === Camera.NO_VALUE)
+
+ // Resolve no value
+ resolve(Camera.NO_VALUE);
+
+ // Otherwise
+ else {
+
+ // Get data from response
+ var data = response[Camera.RESPONSE_DATA_INDEX];
+
+ // Get top left corner X from response
+ var topLeftCornerX = response[Camera.RESPONSE_TOP_LEFT_CORNER_X_INDEX];
+
+ // Get top left corner Y from response
+ var topLeftCornerY = response[Camera.RESPONSE_TOP_LEFT_CORNER_Y_INDEX];
+
+ // Get top right corner X from response
+ var topRightCornerX = response[Camera.RESPONSE_TOP_RIGHT_CORNER_X_INDEX];
+
+ // Get top right corner Y from response
+ var topRightCornerY = response[Camera.RESPONSE_TOP_RIGHT_CORNER_Y_INDEX];
+
+ // Get bottom left corner X from response
+ var bottomLeftCornerX = response[Camera.RESPONSE_BOTTOM_LEFT_CORNER_X_INDEX];
+
+ // Get bottom left corner Y from response
+ var bottomLeftCornerY = response[Camera.RESPONSE_BOTTOM_LEFT_CORNER_Y_INDEX];
+
+ // Get bottom right corner X from response
+ var bottomRightCornerX = response[Camera.RESPONSE_BOTTOM_RIGHT_CORNER_X_INDEX];
+
+ // Get bottom right corner Y from response
+ var bottomRightCornerY = response[Camera.RESPONSE_BOTTOM_RIGHT_CORNER_Y_INDEX];
+
+ // Resolve data
+ resolve(data);
+ }
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Reject error
+ reject(error);
+ });
+ });
+ }
+
+ // Initialize request type
+ static get INITIALIZE_REQUEST_TYPE() {
+
+ // Return initialize request type
+ return 0;
+ }
+
+ // Uninitialize request type
+ static get UNINITIALIZE_REQUEST_TYPE() {
+
+ // Return uninitialize request type
+ return Camera.INITIALIZE_REQUEST_TYPE + 1;
+ }
+
+ // Decode request type
+ static get DECODE_REQUEST_TYPE() {
+
+ // Return decode request type
+ return Camera.UNINITIALIZE_REQUEST_TYPE + 1;
+ }
+
+ // Message request index offset
+ static get MESSAGE_REQUEST_INDEX_OFFSET() {
+
+ // Return message request index offset
+ return 0;
+ }
+
+ // Message type offset
+ static get MESSAGE_TYPE_OFFSET() {
+
+ // Return message type offset
+ return Camera.MESSAGE_REQUEST_INDEX_OFFSET + 1;
+ }
+
+ // Message initialize URL query string offset
+ static get MESSAGE_INITIALIZE_URL_QUERY_STRING_OFFSET() {
+
+ // Return message initialize URL query string offset
+ return Camera.MESSAGE_TYPE_OFFSET + 1;
+ }
+
+ // Message width offset
+ static get MESSAGE_WIDTH_OFFSET() {
+
+ // Return message width offset
+ return Camera.MESSAGE_TYPE_OFFSET + 1;
+ }
+
+ // Message height offset
+ static get MESSAGE_HEIGHT_OFFSET() {
+
+ // Return message height offset
+ return Camera.MESSAGE_WIDTH_OFFSET + 1;
+ }
+
+ // Message image data offset
+ static get MESSAGE_IMAGE_DATA_OFFSET() {
+
+ // Return message image data offset
+ return Camera.MESSAGE_HEIGHT_OFFSET + 1;
+ }
+
+ // Status success value
+ static get STATUS_SUCCESS_VALUE() {
+
+ // Return status success value
+ return true;
+ }
+
+ // Status failed value
+ static get STATUS_FAILED_VALUE() {
+
+ // Return status failed value
+ return false;
+ }
+
+ // No value
+ static get NO_VALUE() {
+
+ // Return no value
+ return null;
+ }
+
+ // Private
+
+ // Send request
+ static sendRequest(request, transfer) {
+
+ // Get current request index
+ var currentRequestIndex = Camera.requestIndex++;
+
+ // Check if current request index is at the max safe integer
+ if(currentRequestIndex === Number.MAX_SAFE_INTEGER)
+
+ // Reset request index
+ Camera.requestIndex = 0;
+
+ // Add current request index to request
+ request.unshift(currentRequestIndex);
+
+ // Return promise
+ return new Promise(function(resolve, reject) {
+
+ // Response current request index event
+ $(document).one(Camera.RESPONSE_EVENT + currentRequestIndex.toFixed(), function(event, response) {
+
+ // Resolve response
+ resolve(response);
+ });
+
+ // Send worker the request
+ Camera.worker.postMessage(request, transfer);
+ });
+ }
+
+ // Message status offset
+ static get MESSAGE_STATUS_OFFSET() {
+
+ // Return message status offset
+ return Camera.MESSAGE_TYPE_OFFSET + 1;
+ }
+
+ // Message response offset
+ static get MESSAGE_RESPONSE_OFFSET() {
+
+ // Return message response offset
+ return Camera.MESSAGE_TYPE_OFFSET + 1;
+ }
+
+ // Response event
+ static get RESPONSE_EVENT() {
+
+ // Return response event
+ return "CameraResponseEvent";
+ }
+
+ // Response data index
+ static get RESPONSE_DATA_INDEX() {
+
+ // Return response data index
+ return 0;
+ }
+
+ // Response top left corner X index
+ static get RESPONSE_TOP_LEFT_CORNER_X_INDEX() {
+
+ // Return response top left corner X index
+ return Camera.RESPONSE_DATA_INDEX + 1;
+ }
+
+ // Response top left corner Y index
+ static get RESPONSE_TOP_LEFT_CORNER_Y_INDEX() {
+
+ // Return response top left corner Y index
+ return Camera.RESPONSE_TOP_LEFT_CORNER_X_INDEX + 1;
+ }
+
+ // Response top right corner X index
+ static get RESPONSE_TOP_RIGHT_CORNER_X_INDEX() {
+
+ // Return response top right corner X index
+ return Camera.RESPONSE_TOP_LEFT_CORNER_Y_INDEX + 1;
+ }
+
+ // Response top right corner Y index
+ static get RESPONSE_TOP_RIGHT_CORNER_Y_INDEX() {
+
+ // Return response top right corner Y index
+ return Camera.RESPONSE_TOP_RIGHT_CORNER_X_INDEX + 1;
+ }
+
+ // Response bottom left corner X index
+ static get RESPONSE_BOTTOM_LEFT_CORNER_X_INDEX() {
+
+ // Return response bottom left corner X index
+ return Camera.RESPONSE_TOP_RIGHT_CORNER_Y_INDEX + 1;
+ }
+
+ // Response bottom left corner Y index
+ static get RESPONSE_BOTTOM_LEFT_CORNER_Y_INDEX() {
+
+ // Return response bottom left corner Y index
+ return Camera.RESPONSE_BOTTOM_LEFT_CORNER_X_INDEX + 1;
+ }
+
+ // Response bottom right corner X index
+ static get RESPONSE_BOTTOM_RIGHT_CORNER_X_INDEX() {
+
+ // Return response bottom right corner X index
+ return Camera.RESPONSE_BOTTOM_LEFT_CORNER_Y_INDEX + 1;
+ }
+
+ // Response bottom right corner Y index
+ static get RESPONSE_BOTTOM_RIGHT_CORNER_Y_INDEX() {
+
+ // Return response bottom right corner Y index
+ return Camera.RESPONSE_BOTTOM_RIGHT_CORNER_X_INDEX + 1;
+ }
+
+ // Worker file location
+ static get WORKER_FILE_LOCATION() {
+
+ // Return worker file location
+ return "." + getResource("./scripts/camera_worker.js");
+ }
+}
+
+
+// Main function
+
+// Set global object's camera
+globalThis["Camera"] = Camera;
diff --git a/scripts/camera_worker.js b/scripts/camera_worker.js
new file mode 100755
index 0000000..fd315da
--- /dev/null
+++ b/scripts/camera_worker.js
@@ -0,0 +1,176 @@
+// Use strict
+"use strict";
+
+
+// Import scripts
+importScripts(".= escapeString(getResource("./scripts/camera.js")); ?>");
+importScripts(".= escapeString(getResource("./scripts/jsQR-1.4.0.js")); ?>");
+
+
+// Constants
+
+// No QR code
+const NO_QR_CODE = null;
+
+
+// Global variables
+
+// URL query string
+var URL_QUERY_STRING;
+
+
+// Events
+
+// Message event
+self.addEventListener("message", function(event) {
+
+ // Get message
+ var message = event["data"];
+
+ // Get message's request index
+ var requestIndex = message[Camera.MESSAGE_REQUEST_INDEX_OFFSET];
+
+ // Get message's type
+ var type = message[Camera.MESSAGE_TYPE_OFFSET];
+
+ // Check type
+ switch(type) {
+
+ // Initialize request type
+ case Camera.INITIALIZE_REQUEST_TYPE:
+
+ // Get message's URL query string
+ URL_QUERY_STRING = message[Camera.MESSAGE_INITIALIZE_URL_QUERY_STRING_OFFSET];
+
+ // Respond with success status
+ postMessage([
+
+ // Request index
+ requestIndex,
+
+ // Type
+ type,
+
+ // Status
+ Camera.STATUS_SUCCESS_VALUE
+ ]);
+
+ // Break
+ break;
+
+ // Uninitialize request type
+ case Camera.UNINITIALIZE_REQUEST_TYPE:
+
+ // Respond with success status
+ postMessage([
+
+ // Request index
+ requestIndex,
+
+ // Type
+ type,
+
+ // Status
+ Camera.STATUS_SUCCESS_VALUE
+ ]);
+
+ // Break
+ break;
+
+ // Decode request type
+ case Camera.DECODE_REQUEST_TYPE:
+
+ // Get message's width
+ var width = message[Camera.MESSAGE_WIDTH_OFFSET];
+
+ // Get message's height
+ var height = message[Camera.MESSAGE_HEIGHT_OFFSET];
+
+ // Get message's image data
+ var imageData = new Uint8Array(message[Camera.MESSAGE_IMAGE_DATA_OFFSET]);
+
+ // Get QR code from image data, width, and height
+ var qrCode = jsQR(imageData, width, height, {
+
+ // Inversion attempts
+ "inversionAttempts": "attemptBoth"
+ });
+
+ // Check if getting QR code failed
+ if(qrCode === NO_QR_CODE) {
+
+ // Respond with no value
+ postMessage([
+
+ // Request index
+ requestIndex,
+
+ // Type
+ type,
+
+ // Value
+ Camera.NO_VALUE
+ ]);
+ }
+
+ // Otherwise
+ else {
+
+ // Respond with value
+ postMessage([
+
+ // Request index
+ requestIndex,
+
+ // Type
+ type,
+
+ // Value
+ [
+
+ // Data
+ qrCode["data"],
+
+ // Top left corner X
+ qrCode["location"]["topLeftCorner"]["x"],
+
+ // Top left corner Y
+ qrCode["location"]["topLeftCorner"]["y"],
+
+ // Top right corner X
+ qrCode["location"]["topRightCorner"]["x"],
+
+ // Top right corner Y
+ qrCode["location"]["topRightCorner"]["y"],
+
+ // Bottom left corner X
+ qrCode["location"]["bottomLeftCorner"]["x"],
+
+ // Bottom left corner Y
+ qrCode["location"]["bottomLeftCorner"]["y"],
+
+ // Bottom right corner X
+ qrCode["location"]["bottomRightCorner"]["x"],
+
+ // Bottom right corner Y
+ qrCode["location"]["bottomRightCorner"]["y"]
+ ]
+ ]);
+ }
+
+ // Break;
+ break;
+ }
+});
diff --git a/scripts/caps_lock.js b/scripts/caps_lock.js
new file mode 100755
index 0000000..1c348f6
--- /dev/null
+++ b/scripts/caps_lock.js
@@ -0,0 +1,135 @@
+// Use strict
+"use strict";
+
+
+// Classes
+
+// Caps lock class
+class CapsLock {
+
+ // Public
+
+ // Constructor
+ constructor() {
+
+ // Set state
+ this.state = false;
+
+ // Get is linux
+ this.isLinux = (typeof navigator === "object" && navigator !== null) ? (("userAgentData" in navigator === true && "platform" in navigator["userAgentData"] === true) ? navigator["userAgentData"]["platform"] : navigator["platform"]).toLowerCase().indexOf("linux") !== Common.INDEX_NOT_FOUND : false;
+
+ // Turning off
+ this.turningOff = false;
+
+ // Get body display
+ this.bodyDisplay = $("body");
+
+ // Set self
+ var self = this;
+
+ // Document key down, key up, mouse down, mouse up, and mousemove event
+ $(document).on("keydown keyup mousedown mouseup mousemove", function(event) {
+
+ // Check if Linux, event is key up or key down, and caps lock is the key
+ if(self.isLinux === true && (event["type"] === "keyup" || event["type"] === "keydown") && event["originalEvent"]["code"] === "CapsLock") {
+
+ // Check if event is key down
+ if(event["type"] === "keydown") {
+
+ // Check if caps lock is off
+ if(event["originalEvent"].getModifierState("CapsLock") === false) {
+
+ // Check if state is off
+ if(self.state === false) {
+
+ // Set state
+ self.state = true;
+
+ // Set that body display has caps lock
+ self.bodyDisplay.addClass("capsLock");
+ }
+ }
+
+ // Otherwise
+ else
+
+ // Set turning off
+ self.turningOff = true;
+ }
+
+ // Otherwise assume event is key up
+ else {
+
+ // Check if turning off
+ if(self.turningOff === true) {
+
+ // Clear turning off
+ self.turningOff = false;
+
+ // Check if state is on
+ if(self.state === true) {
+
+ // Clear state
+ self.state = false;
+
+ // Set that body display doesn't have caps lock
+ self.bodyDisplay.removeClass("capsLock");
+ }
+ }
+ }
+ }
+
+ // Otherwise check if event includes the ability to get the modifier state
+ else if(typeof event === "object" && event !== null && "originalEvent" in event === true && typeof event["originalEvent"] === "object" && event["originalEvent"] !== null && "getModifierState" in event["originalEvent"] === true) {
+
+ // Check if caps lock is on
+ if(event["originalEvent"].getModifierState("CapsLock") === true) {
+
+ // Check if state is off
+ if(self.state === false) {
+
+ // Set state
+ self.state = true;
+
+ // Set that body display has caps lock
+ self.bodyDisplay.addClass("capsLock");
+ }
+ }
+
+ // Otherwise check if caps lock is off
+ else if(event["originalEvent"].getModifierState("CapsLock") === false) {
+
+ // Check if state is on
+ if(self.state === true) {
+
+ // Clear state
+ self.state = false;
+
+ // Set that body display doesn't have caps lock
+ self.bodyDisplay.removeClass("capsLock");
+ }
+ }
+ }
+ });
+ }
+
+ // Is on
+ isOn() {
+
+ // Return if caps lock is on
+ return this.state === true;
+ }
+
+ // Is off
+ isOff() {
+
+ // Return if not on
+ return this.isOn() === false;
+ }
+}
+
+
+// Main function
+
+// Set global object's caps lock
+globalThis["CapsLock"] = CapsLock;
diff --git a/scripts/clipboard.js b/scripts/clipboard.js
new file mode 100755
index 0000000..b53e409
--- /dev/null
+++ b/scripts/clipboard.js
@@ -0,0 +1,48 @@
+// Use strict
+"use strict";
+
+
+// Classes
+
+// Clipboard class
+class Clipboard {
+
+ // Public
+
+ // Copy
+ copy(text) {
+
+ // Return promise
+ return new Promise(function(resolve, reject) {
+
+ // Check if clipboard API is supported
+ if(typeof navigator === "object" && navigator !== null && "clipboard" in navigator === true) {
+
+ // Return writing text to clipboard
+ return navigator["clipboard"].writeText(text).then(function() {
+
+ // Resolve
+ resolve();
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Reject error
+ reject("Writing to clipboard failed.");
+ });
+ }
+
+ // Otherwise
+ else
+
+ // Reject error
+ reject("Clipboard not supported.");
+ });
+ }
+}
+
+
+// Main function
+
+// Set global object's clipboard
+globalThis["Clipboard"] = Clipboard;
diff --git a/scripts/common.js b/scripts/common.js
new file mode 100755
index 0000000..bdb956f
--- /dev/null
+++ b/scripts/common.js
@@ -0,0 +1,1951 @@
+// Use strict
+"use strict";
+
+
+// Classes
+
+// Common class
+class Common {
+
+ // Public
+
+ // From hex string
+ static fromHexString(hexString, useSharedArrayBuffer = false) {
+
+ // Initialize result
+ var result = new Uint8Array((useSharedArrayBuffer === true && crossOriginIsolated === true) ? new SharedArrayBuffer(Common.hexStringLength(hexString)) : Common.hexStringLength(hexString));
+
+ // Go through all bytes in the result
+ for(var i = 0; i < result["length"]; ++i) {
+
+ // Set character as a byte in the result
+ result[i] = (Common.HEX_CHARACTER_TO_VALUE[hexString[i * Common.HEX_NUMBER_LENGTH]] << (Common.BITS_IN_A_BYTE / 2)) | Common.HEX_CHARACTER_TO_VALUE[hexString[i * Common.HEX_NUMBER_LENGTH + 1]];
+ }
+
+ // Return result
+ return result;
+ }
+
+ // To hex string
+ static toHexString(byteArray) {
+
+ // Initialize result
+ var result = "";
+
+ // Go through all bytes in the byte array
+ for(var i = 0; i < byteArray["length"]; ++i) {
+
+ // Get byte
+ var byte = byteArray[i];
+
+ // Append byte as characters to the result
+ result = result.concat(Common.VALUE_TO_HEX_CHARACTER[byte >>> (Common.BITS_IN_A_BYTE / 2)], Common.VALUE_TO_HEX_CHARACTER[byte & 0x0F]);
+ }
+
+ // Return result
+ return result;
+ }
+
+ // Is hex string
+ static isHexString(string) {
+
+ // Check if string isn't a string
+ if(typeof string !== "string")
+
+ // Return false
+ return false;
+
+ // Check if string's length is invalid
+ if(string["length"] % Common.HEX_NUMBER_LENGTH !== 0) {
+
+ // Return false
+ return false;
+ }
+
+ // Return if string is a hex string
+ return Common.HEX_STRING_PATTERN.test(string) === true;
+ }
+
+ // Hex string length
+ static hexStringLength(string) {
+
+ // Return hex string length
+ return string["length"] / Common.HEX_NUMBER_LENGTH;
+ }
+
+ // Is number string
+ static isNumberString(string) {
+
+ // Check if string isn't a string
+ if(typeof string !== "string")
+
+ // Return false
+ return false;
+
+ // Return if string is a number string
+ return Common.NUMBER_STRING_PATTERN.test(string) === true;
+ }
+
+ // Is RFC 3339 string
+ static isRfc3339String(string) {
+
+ // Check if string isn't a string
+ if(typeof string !== "string")
+
+ // Return false
+ return false;
+
+ // Check if string isn't an RFC 3339 string
+ if(Common.RFC_3339_STRING_PATTERN.test(string) === false)
+
+ // Return false
+ return false;
+
+ // Return if string could be converted from a RFC 3339 string to a timestamp
+ return Number.isNaN(Common.rfc3339StringToTimestamp(string)) === false;
+ }
+
+ // Is lowercase string
+ static isLowercaseString(string) {
+
+ // Check if string isn't a string
+ if(typeof string !== "string")
+
+ // Return false
+ return false;
+
+ // Return if string is a a lowercase string
+ return Common.LOWERCASE_STRING_PATTERN.test(string) === true;
+ }
+
+ // RFC 3339 string to timestamp
+ static rfc3339StringToTimestamp(string) {
+
+ // Return timestamp from string
+ return Date.parse(string);
+ }
+
+ // Merge arrays
+ static mergeArrays(arrays) {
+
+ // Initialize result
+ var result = new Uint8Array([]);
+
+ // Go through all arrays
+ for(var i = 0; i < arrays["length"]; ++i) {
+
+ // Get array
+ var array = arrays[i];
+
+ // Set updated result to be the length of the current result and the array
+ var updatedResult = new Uint8Array(result["length"] + array["length"]);
+
+ // Set arrays in the updated result
+ updatedResult.set(result);
+ updatedResult.set(array, result["length"]);
+
+ // Set result to the updated result
+ result = updatedResult;
+ }
+
+ // Return result
+ return result;
+ }
+
+ // Arrays are equal
+ static arraysAreEqual(arrayOne, arrayTwo) {
+
+ // Check if arrays have different lengths
+ if(arrayOne["length"] !== arrayTwo["length"])
+
+ // Return false
+ return false;
+
+ // Go through all values each array
+ for(var i = 0; i < arrayOne["length"]; ++i)
+
+ // Check if array values differ
+ if(arrayOne[i] !== arrayTwo[i])
+
+ // Return false
+ return false;
+
+ // Return true
+ return true;
+ }
+
+ // Arrays are equal timing safe
+ static arraysAreEqualTimingSafe(arrayOne, arrayTwo) {
+
+ // Initialize results
+ var result = (arrayOne["length"] === arrayTwo["length"]) ? 0 : 1;
+
+ // Go through all values in the first array
+ for(var i = 0; i < arrayOne["length"]; ++i) {
+
+ // Get current value from each array
+ var arrayOneValue = arrayOne[i];
+ var arrayTwoValue = (i < arrayTwo["length"]) ? arrayTwo[i] : 0;
+
+ // Update result to if current values are equal
+ result |= (arrayOneValue === arrayTwoValue) ? 0 : 1;
+ }
+
+ // Return if arrays are equal
+ return result === 0;
+ }
+
+ // Get current timestamp
+ static getCurrentTimestamp() {
+
+ // Return current timestamp
+ return Math.floor(Date.now() / Common.MILLISECONDS_IN_A_SECOND);
+ }
+
+ // Is extension
+ static isExtension() {
+
+ // Return if extension
+ return typeof location !== "undefined" && location["protocol"]["length"] > "-extension:"["length"] && location["protocol"].substring(location["protocol"]["length"] - "-extension:"["length"]) === "-extension:";
+ }
+
+ // Is popup
+ static isPopup() {
+
+ // Get URL parameters
+ var urlParameters = Common.getUrlParameters();
+
+ // Return if popup
+ return Common.isExtension() === true && "Is Popup" in urlParameters === true && urlParameters["Is Popup"] === "True";
+ }
+
+ // Is app
+ static isApp() {
+
+ // Return if app
+ return Common.isExtension() === false && ((typeof navigator === "object" && navigator !== null && "standalone" in navigator === true && navigator["standalone"] === true) || (typeof matchMedia === "function" && matchMedia("(display-mode: standalone)")["matches"] === true));
+ }
+
+ // HTML encode
+ static htmlEncode(string) {
+
+ // Return string with HTML entities encoded
+ return $("").text(string).html().replace(Common.DOUBLE_QUOTE_PATTERN, Common.DOUBLE_QUOTE_HTML_ENTITY).replace(Common.SINGLE_QUOTE_PATTERN, Common.SINGLE_QUOTE_HTML_ENTITY).replace(Common.GRAVE_ACCENT_PATTERN, Common.GRAVE_ACCENT_HTML_ENTITY);
+ }
+
+ // HTML decode
+ static htmlDecode(htmlString) {
+
+ // Return html string with HTML entities decoded
+ return $("
").html(htmlString).text();
+ }
+
+ // Random number
+ static randomNumber(minValue, maxValue) {
+
+ // Return random number between values inclusively
+ return Math.floor(Math.random() * (maxValue - minValue + 1)) + minValue;
+ }
+
+ // Escape regular expression
+ static escapeRegularExpression(string) {
+
+ // Return string with regular expression meta characters escaped
+ return string.replace(/[.*+?^${}()|[\]\\]/ug, "\\$&");
+ }
+
+ // Map
+ static map(value, inMin, inMax, outMin, outMax) {
+
+ // Return value mapped to different range
+ return (new BigNumber(outMax)).minus(outMin).multipliedBy((new BigNumber(value)).minus(inMin)).dividedBy((new BigNumber(inMax)).minus(inMin)).plus(outMin);
+ }
+
+ // Is valid URL
+ static isValidUrl(url) {
+
+ // Check if URL doesn't have a protocol
+ if(Common.urlContainsProtocol(url) === false) {
+
+ // Add protocol to URL
+ url = Common.HTTP_PROTOCOL + "//" + url;
+ }
+
+ // Try
+ try {
+
+ // Parse URL as a URL
+ new URL(url);
+ }
+
+ // Catch errors
+ catch(error) {
+
+ // Return false
+ return false;
+ }
+
+ // Return true
+ return true;
+ }
+
+ // Is iframe
+ static isIframe() {
+
+ // Try
+ try {
+
+ // Return if self isn't the top window
+ return window["self"] !== window["top"];
+ }
+
+ // Catch errors
+ catch(error) {
+
+ // Return true
+ return true;
+ }
+ }
+
+ // On reduced data connection
+ static onReducedDataConnection() {
+
+ // Return if save data is enabled
+ return typeof navigator === "object" && navigator !== null && "connection" in navigator === true && typeof navigator["connection"] === "object" && navigator["connection"] !== null && "saveData" in navigator["connection"] === true && navigator["connection"]["saveData"] === true;
+ }
+
+ // Is low memory device
+ static isLowMemoryDevice() {
+
+ // Return if device has low memory
+ return typeof navigator === "object" && navigator !== null && "deviceMemory" in navigator === true && navigator["deviceMemory"] <= Common.LOW_MEMORY_THRESHOLD_GIGABYTES;
+ }
+
+ // Is high memory device
+ static isHighMemoryDevice() {
+
+ // Return if device has high memory
+ return typeof navigator === "object" && navigator !== null && "deviceMemory" in navigator === true && navigator["deviceMemory"] >= Common.HIGH_MEMORY_THRESHOLD_GIGABYTES;
+ }
+
+ // Is mobile device
+ static isMobileDevice() {
+
+ // Return if device is a mobile device
+ return typeof navigator === "object" && navigator !== null && "userAgentData" in navigator === true && "mobile" in navigator["userAgentData"] === true && navigator["userAgentData"]["mobile"] === true;
+ }
+
+
+ // Is using cellular network
+ static isUsingCellularNetwork() {
+
+ // Return if using cellular network
+ return typeof navigator === "object" && navigator !== null && "connection" in navigator === true && "type" in navigator["connection"] === true && navigator["connection"]["type"] === "cellular";
+ }
+
+ // Upgrade applicable insecure URL
+ static upgradeApplicableInsecureUrl(url) {
+
+ // Try
+ try {
+
+ // Parse HTTPS server address as a URL
+ var parsedHttpsServerUrl = new URL(HTTPS_SERVER_ADDRESS);
+
+ // Parse Tor server address as a URL
+ var parsedTorServerUrl = new URL(TOR_SERVER_ADDRESS);
+
+ // Parse URL as a URL
+ var parsedUrl = new URL(url);
+ }
+
+ // Catch errors
+ catch(error) {
+
+ // Return url
+ return url;
+ }
+
+ // Check if URL's protocol is HTTP, the HTTPS server address's protocol is HTTPS, and the URL is the same site as the HTTPS server address
+ if(parsedUrl["protocol"] === Common.HTTP_PROTOCOL && parsedHttpsServerUrl["protocol"] === Common.HTTPS_PROTOCOL && parsedUrl["hostname"] === parsedHttpsServerUrl["hostname"]) {
+
+ // Create an HTTPS URL from the URL
+ var httpsUrl = Common.HTTPS_PROTOCOL + Common.ltrim(url).substring(Common.HTTP_PROTOCOL["length"]);
+
+ // Return HTTPS URL
+ return httpsUrl;
+ }
+
+ // Otherwise check if URL's protocol is WebSocket, the HTTPS server address's protocol is HTTPS, and the URL is the same site as the HTTPS server address
+ else if(parsedUrl["protocol"] === Common.WEBSOCKET_PROTOCOL && parsedHttpsServerUrl["protocol"] === Common.HTTPS_PROTOCOL && parsedUrl["hostname"] === parsedHttpsServerUrl["hostname"]) {
+
+ // Create an WebSocket secure URL from the URL
+ var wssUrl = Common.WEBSOCKET_SECURE_PROTOCOL + Common.ltrim(url).substring(Common.WEBSOCKET_PROTOCOL["length"]);
+
+ // Return WebSocket secure URL
+ return wssUrl;
+ }
+
+ // Otherwise check if URL's protocol is HTTPS, the Tor server address's protocol is HTTP, and the URL is the same site as the Tor server address
+ else if(parsedUrl["protocol"] === Common.HTTPS_PROTOCOL && parsedTorServerUrl["protocol"] === Common.HTTP_PROTOCOL && parsedUrl["hostname"] === parsedTorServerUrl["hostname"]) {
+
+ // Create an HTTP URL from the URL
+ var httpUrl = Common.HTTP_PROTOCOL + Common.ltrim(url).substring(Common.HTTPS_PROTOCOL["length"]);
+
+ // Return HTTP URL
+ return httpUrl;
+ }
+
+ // Otherwise check if URL's protocol is WebSocket secure, the Tor server address's protocol is HTTP, and the URL is the same site as the Tor server address
+ else if(parsedUrl["protocol"] === Common.WEBSOCKET_SECURE_PROTOCOL && parsedTorServerUrl["protocol"] === Common.HTTP_PROTOCOL && parsedUrl["hostname"] === parsedTorServerUrl["hostname"]) {
+
+ // Create an WebSocket URL from the URL
+ var wsUrl = Common.WEBSOCKET_PROTOCOL + Common.ltrim(url).substring(Common.WEBSOCKET_SECURE_PROTOCOL["length"]);
+
+ // Return WebSocket URL
+ return wsUrl;
+ }
+
+ // Otherwise
+ else {
+
+ // Return URL
+ return url;
+ }
+ }
+
+ // Get URL parmaters
+ static getUrlParameters() {
+
+ // Initialize result
+ var result = {};
+
+ // Check if URL query string exists
+ if(typeof URL_QUERY_STRING !== "undefined") {
+
+ // Get URL parameters from URL query string
+ var urlParameters = URL_QUERY_STRING.substring(Common.URL_QUERY_STRING_SEPARATOR["length"]).split(Common.URL_QUERY_STRING_PARAMETER_SEPARATOR);
+
+ // Go through all URL parameters
+ for(var i = 0; i < urlParameters["length"]; ++i) {
+
+ // Get URL parameter
+ var urlParameter = urlParameters[i].split(Common.URL_QUERY_STRING_PARAMETER_VALUE_SEPARATOR);
+
+ // Check if URL parameter is valid
+ if(urlParameter["length"] === 2) {
+
+ // Get URL parameter's key
+ var key = decodeURIComponent(urlParameter[0].replace(/\+/ug, "%20"));
+
+ // Check if key isn't already in the rersult
+ if(key in result === false) {
+
+ // Set URL parameter in the result
+ result[key] = decodeURIComponent(urlParameter[1].replace(/\+/ug, "%20"));
+ }
+ }
+ }
+ }
+
+ // Return result
+ return result;
+ }
+
+ // URL contains protocol
+ static urlContainsProtocol(url) {
+
+ // Return if the URL contains protocol
+ return Common.URL_PROTOCOL_PATTERN.test(url) === true;
+ }
+
+ // URL contains top-level domain
+ static urlContainsTopLevelDomain(url) {
+
+ // Return if the URL contains a top-level domain
+ return Common.URL_TOP_LEVEL_DOMAIN_PATTERN.test(url) === true;
+ }
+
+ // Remove subdomain
+ static removeSubdomain(url) {
+
+ // Return URL's without its subdomain
+ return url.match(Common.URL_DOMAIN_NAME_PATTERN)[0];
+ }
+
+ // Remove top-level domain
+ static removeTopLevelDomain(url) {
+
+ // Return URL without its top-level domain
+ return url.match(Common.URL_WITHOUT_TOP_LEVEL_DOMAIN_PATTERN)[1];
+ }
+
+ // Remove trailing slashes
+ static removeTrailingSlashes(text) {
+
+ // Return text with its trailing slashes removed
+ return Common.rtrim(text).replace(Common.TRAILING_SLASHES_PATTERN, "");
+ }
+
+ // Remove duplicate slashes
+ static removeDuplicateSlashes(text) {
+
+ // Return text with its duplicate slashes removed
+ return text.replace(Common.DUPLICATE_SLASHES_PATTERN, "/");
+ }
+
+ // Split remaining
+ static splitRemaining(text, separator, limit) {
+
+ // Initialzie result
+ var result = [];
+
+ // Go through limit
+ for(var i = 0; i < limit; ++i) {
+
+ // Get last separator end
+ var lastSeparatorEnd = separator["lastIndex"];
+
+ // Get current separator start
+ var currentSeparatorStart = separator.exec(text);
+
+ // Check if separator start exists
+ if(currentSeparatorStart !== Common.NO_MATCH_FOUND) {
+
+ // Append part to result
+ result.push(text.slice(lastSeparatorEnd, currentSeparatorStart["index"]));
+ }
+
+ // Otherwise
+ else {
+
+ // Get last part
+ var lastPart = text.slice(lastSeparatorEnd);
+
+ // Check if last part exists and isn't a separator
+ if(lastPart["length"] !== 0 && separator.test(lastPart) === false) {
+
+ // Append last part to result
+ result.push(lastPart);
+ }
+
+ // Return result
+ return result;
+ }
+ }
+
+ // Get last part
+ var lastPart = text.slice(separator["lastIndex"]);
+
+ // Check if last part exists and isn't a separator
+ if(lastPart["length"] !== 0 && separator.test(lastPart) === false) {
+
+ // Append last part to result
+ result.push(lastPart);
+ }
+
+ // Return result
+ return result;
+ }
+
+ // Remove duplicate newlines
+ static removeDuplicateNewlines(text) {
+
+ // Return text with duplicate newlines and trailing newlines removed from it
+ return text.replace(Common.DUPLICATE_NEWLINE_PATTERN, "\n").replace(Common.TRAILING_NEWLINE_PATTERN, "");
+ }
+
+ // Remove trailing zeros
+ static removeTrailingZeros(text) {
+
+ // Remove trailing zeros and ending decimal point from text
+ text = text.replace(Common.TRAILING_ZEROS_PATTERN, "$1").replace(Common.ENDING_DECIMAL_POINT_PATTERN, "");
+
+ // Check if text is invalid
+ if(Number.isNaN(parseFloat(text)) === true) {
+
+ // Return zero
+ return "0";
+ }
+
+ // Otherwise
+ else {
+
+ // Return text
+ return text;
+ }
+ }
+
+ // Get number string precision
+ static getNumberStringPrecision(text) {
+
+ // Remove trailing zeros from text
+ text = Common.removeTrailingZeros(text);
+
+ // Check if text doesn't contains a fractional component
+ var fractionIndex = text.indexOf(".");
+
+ if(fractionIndex === Common.INDEX_NOT_FOUND) {
+
+ // Return zero
+ return 0;
+ }
+
+ // Otherwise
+ else {
+
+ // Return number of fractional digits
+ return text["length"] - (fractionIndex + "."["length"]);
+ }
+ }
+
+ // Serialize object
+ static serializeObject(object) {
+
+ // Check if object is an array
+ if(Array.isArray(object) === true) {
+
+ // Return serialized object
+ return {
+
+ // Value
+ "Value": object.map(function(value) {
+
+ // Return serialized value
+ return Common.serializeObject(value);
+ })
+ };
+ }
+
+ // Otherwise check if object is an object
+ else if(typeof object === "object" && object !== null) {
+
+ // Check if object is a typed array
+ if("buffer" in object === true && object["buffer"] instanceof ArrayBuffer === true) {
+
+ // Get object's type
+ var type = Object["prototype"]["toString"].call(object).match(Common.OBJECT_TYPE_PATTERN);
+
+ // Return serialized object
+ return {
+
+ // Constructor
+ "Constructor": (type !== Common.NO_MATCH_FOUND) ? type[1] : object["constructor"]["name"],
+
+ // Value
+ "Value": Array.from(object)
+ };
+ }
+
+ // Otherwise
+ else {
+
+ // Return serialized object
+ return {
+
+ // Constructor
+ "Constructor": (object instanceof BigNumber === true) ? "BigNumber" : object["constructor"]["name"],
+
+ // Properties
+ "Properties": Object.entries(object).map(function(property) {
+
+ // Return serialized property
+ return {
+
+ // Name
+ "Name": property[0],
+
+ // Value
+ "Value": Common.serializeObject(property[1])
+ };
+ })
+ };
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Return serialized object
+ return {
+
+ // Value
+ "Value": object
+ };
+ }
+ }
+
+ // Unserialize object
+ static unserializeObject(serializedObject) {
+
+ // Check if serialized object contains a constructor
+ if("Constructor" in serializedObject === true) {
+
+ // Check if serialized object can be created
+ if(serializedObject["Constructor"] in globalThis === true) {
+
+ // Check if serialzed object is a typed array
+ if("Value" in serializedObject === true) {
+
+ // Create object
+ var object = new globalThis[serializedObject["Constructor"]](serializedObject["Value"]);
+
+ // Return object
+ return object;
+ }
+
+ // Otherwise
+ else {
+
+ // Create object
+ var object = Object.create(globalThis[serializedObject["Constructor"]]["prototype"]);
+
+ // Go through all of the serialized object's properties
+ for(var i = 0; i < serializedObject["Properties"]["length"]; ++i) {
+
+ // Get property
+ var property = serializedObject["Properties"][i];
+
+ // Set property in the object
+ object[property["Name"]] = Common.unserializeObject(property["Value"]);
+ }
+
+ // Return object
+ return object;
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Return undefined
+ return undefined;
+ }
+ }
+
+ // Otherwise check if serialzied object is an array
+ else if(Array.isArray(serializedObject["Value"]) === true) {
+
+ // Return serialized object's value
+ return serializedObject["Value"].map(function(serializedValue) {
+
+ // Return unserializing the serialized value
+ return Common.unserializeObject(serializedValue);
+ });
+ }
+
+ // Otherwise
+ else {
+
+ // Return serialized object's value
+ return serializedObject["Value"];
+ }
+ }
+
+ // Request animation frame or timeout
+ static requestAnimationFrameOrTimeout(callback) {
+
+ // Initialize animation frame
+ var animationFrame;
+
+ // Set timeout
+ var timeout = setTimeout(function() {
+
+ // Cancel animation frame
+ cancelAnimationFrame(animationFrame);
+
+ // Run callback
+ callback(event);
+
+ }, Common.REQUEST_ANIMATION_FRAME_TIMEOUT_MILLISECONDS);
+
+ // Set animation frame
+ animationFrame = requestAnimationFrame(function(event) {
+
+ // Clear timeout
+ clearTimeout(timeout);
+
+ // Run callback
+ callback(event);
+ });
+ }
+
+ // Ltrim
+ static ltrim(text) {
+
+ // Return text with leading whitespace removed
+ return text.replace(Common.LEADING_WHITESPACE_PATTERN, "");
+ }
+
+ // Rtrim
+ static rtrim(text) {
+
+ // Return text with trailing whitespace removed
+ return text.replace(Common.TRAILING_WHITESPACE_PATTERN, "");
+ }
+
+ // Get preserved URL parameters
+ static getPreservedUrlParameters() {
+
+ // Check if is popup
+ if(Common.isPopup() === true) {
+
+ // Return preserved URL parameters
+ return Common.URL_QUERY_STRING_PARAMETER_SEPARATOR + encodeURIComponent("Is Popup").replace(/%20/ug, "+") + Common.URL_QUERY_STRING_PARAMETER_VALUE_SEPARATOR + encodeURIComponent("True").replace(/%20/ug, "+");
+ }
+
+ // Otherwise
+ else {
+
+ // Return nothing
+ return "";
+ }
+ }
+
+ // Save file
+ static saveFile(name, contents) {
+
+ // Create anchor
+ var anchor = $("
");
+
+ // Create URL from contents
+ var url = URL.createObjectURL(new Blob([
+
+ // Contents
+ contents
+ ], {
+
+ // Type
+ "type": "application/octet-stream"
+ }));
+
+ // Set anchor's href to URL
+ anchor.attr("href", url);
+
+ // Set anchor's download to name
+ anchor.attr("download", name);
+
+ // Click on anchor
+ anchor.get(0).click();
+
+ // Set timeout
+ setTimeout(function() {
+
+ // Revoke URL
+ URL.revokeObjectURL(url);
+ }, 0);
+ }
+
+ // Has whitespace
+ static hasWhitespace(text) {
+
+ // Return if text has whitespace
+ return Common.HAS_WHITESPACE_PATTERN.test(text) === true;
+ }
+
+ // Remove whitespace
+ static removeWhitespace(text) {
+
+ // Return text with whitespace removed
+ return text.replace(Common.HAS_WHITESPACE_PATTERN, "");
+ }
+
+ // Milliseconds in a second
+ static get MILLISECONDS_IN_A_SECOND() {
+
+ // Return milliseconds in a second
+ return 1000;
+ }
+
+ // Seconds in a minute
+ static get SECONDS_IN_A_MINUTE() {
+
+ // Return minutes in a second
+ return 60;
+ }
+
+ // Minutes in an hour
+ static get MINUTES_IN_AN_HOUR() {
+
+ // Return minutes in an hour
+ return 60;
+ }
+
+ // Hours in a day
+ static get HOURS_IN_A_DAY() {
+
+ // Return hours in a day
+ return 24;
+ }
+
+ // Days in a week
+ static get DAYS_IN_A_WEEK() {
+
+ // Return days in a week
+ return 7;
+ }
+
+ // Weeks in a year
+ static get WEEKS_IN_A_YEAR() {
+
+ // Return weeks in a year
+ return 52;
+ }
+
+ // Byte max value
+ static get BYTE_MAX_VALUE() {
+
+ // Return byte max value
+ return Math.pow(2, Common.BITS_IN_A_BYTE) - 1;
+ }
+
+ // Uint16 max value
+ static get UINT16_MAX_VALUE() {
+
+ // Return uint16 max value
+ return Math.pow(2, Common.BITS_IN_A_BYTE * Uint16Array["BYTES_PER_ELEMENT"]) - 1;
+ }
+
+ // Uint32 max value
+ static get UINT32_MAX_VALUE() {
+
+ // Return uint32 max value
+ return Math.pow(2, Common.BITS_IN_A_BYTE * Uint32Array["BYTES_PER_ELEMENT"]) - 1;
+ }
+
+ // Int32 max value
+ static get INT32_MAX_VALUE() {
+
+ // Return int32 max value
+ return Math.pow(2, Common.BITS_IN_A_BYTE * Int32Array["BYTES_PER_ELEMENT"] - 1) - 1;
+ }
+
+ // Bytes in a uint8
+ static get BYTES_IN_A_UINT8() {
+
+ // Return bytes in a uint8
+ return 8 / Common.BITS_IN_A_BYTE;
+ }
+
+ // Bytes in a uint16
+ static get BYTES_IN_A_UINT16() {
+
+ // Return bytes in a uint16
+ return 16 / Common.BITS_IN_A_BYTE;
+ }
+
+ // Bytes in a uint32
+ static get BYTES_IN_A_UINT32() {
+
+ // Return bytes in a uint32
+ return 32 / Common.BITS_IN_A_BYTE;
+ }
+
+ // Bytes in a uint64
+ static get BYTES_IN_A_UINT64() {
+
+ // Return bytes in a uint64
+ return 64 / Common.BITS_IN_A_BYTE;
+ }
+
+ // Invalid local storage item
+ static get INVALID_LOCAL_STORAGE_ITEM() {
+
+ // Return invalid local storage item
+ return null;
+ }
+
+ // Index not found
+ static get INDEX_NOT_FOUND() {
+
+ // Return index not found
+ return -1;
+ }
+
+ // No match found
+ static get NO_MATCH_FOUND() {
+
+ // Return no match found
+ return null;
+ }
+
+ // No tab index
+ static get NO_TAB_INDEX() {
+
+ // Return no tab index
+ return "-1";
+ }
+
+ // Decimal number base
+ static get DECIMAL_NUMBER_BASE() {
+
+ // Return decimal number base
+ return 10;
+ }
+
+ // Hex number base
+ static get HEX_NUMBER_BASE() {
+
+ // Return hex number base
+ return 16;
+ }
+
+ // Hex number length
+ static get HEX_NUMBER_LENGTH() {
+
+ // Return hex number length
+ return "FF"["length"];
+ }
+
+ // Hex color length
+ static get HEX_COLOR_LENGTH() {
+
+ // Return hex color length
+ return "FFFFFF"["length"];
+ }
+
+ // Hex number padding
+ static get HEX_NUMBER_PADDING() {
+
+ // Return hex number padding
+ return "0";
+ }
+
+ // Focus change event
+ static get FOCUS_CHANGE_EVENT() {
+
+ // Return focus change event
+ return "FocusChangeEvent";
+ }
+
+ // No attribute
+ static get NO_ATTRIBUTE() {
+
+ // Return no attribute
+ return undefined;
+ }
+
+ // No argument
+ static get NO_ARGUMENT() {
+
+ // Return no argument
+ return undefined;
+ }
+
+ // URL query string separator
+ static get URL_QUERY_STRING_SEPARATOR() {
+
+ // Return URL query string separator
+ return "?";
+ }
+
+ // URL query string parameter separator
+ static get URL_QUERY_STRING_PARAMETER_SEPARATOR() {
+
+ // Return URL query string parameter separator
+ return "&";
+ }
+
+ // URL query string parameter value separator
+ static get URL_QUERY_STRING_PARAMETER_VALUE_SEPARATOR() {
+
+ // Return URL query string parameter value separator
+ return "=";
+ }
+
+ // Data attribute prefix
+ static get DATA_ATTRIBUTE_PREFIX() {
+
+ // Return data attribute prefix
+ return "data-";
+ }
+
+ // First match result index
+ static get FIRST_MATCH_RESULT_INDEX() {
+
+ // Return first match result index
+ return 1;
+ }
+
+ // JSON NULL value
+ static get JSON_NULL_VALUE() {
+
+ // Return JSON NULL value
+ return null;
+ }
+
+ // HTTP protocol
+ static get HTTP_PROTOCOL() {
+
+ // Return HTTP protocol
+ return "http:";
+ }
+
+ // HTTPS protocol
+ static get HTTPS_PROTOCOL() {
+
+ // Return HTTPS protocol
+ return "https:";
+ }
+
+ // WebSocket protocol
+ static get WEBSOCKET_PROTOCOL() {
+
+ // Return WebSocket protocol
+ return "ws:";
+ }
+
+ // WebSocket secure protocol
+ static get WEBSOCKET_SECURE_PROTOCOL() {
+
+ // Return WebSocket secure protocol
+ return "wss:";
+ }
+
+ // File protocol
+ static get FILE_PROTOCOL() {
+
+ // Return file protocol
+ return "file:";
+ }
+
+ // HTTP no response status
+ static get HTTP_NO_RESPONSE_STATUS() {
+
+ // Return HTTP no response status
+ return 0;
+ }
+
+ // HTTP ok status
+ static get HTTP_OK_STATUS() {
+
+ // Return HTTP ok status
+ return 200;
+ }
+
+ // HTTP bad request status
+ static get HTTP_BAD_REQUEST_STATUS() {
+
+ // Return HTTP bad request status
+ return 400;
+ }
+
+ // HTTP unauthorized status
+ static get HTTP_UNAUTHORIZED_STATUS() {
+
+ // Return HTTP unauthorized status
+ return 401;
+ }
+
+ // HTTP forbidden status
+ static get HTTP_FORBIDDEN_STATUS() {
+
+ // Return HTTP forbidden status
+ return 403;
+ }
+
+ // HTTP not found status
+ static get HTTP_NOT_FOUND_STATUS() {
+
+ // Return HTTP not found status
+ return 404;
+ }
+
+ // HTTP payload too large status
+ static get HTTP_PAYLOAD_TOO_LARGE_STATUS() {
+
+ // Return HTTP payload too large status
+ return 413;
+ }
+
+ // HTTP unsupported media type status
+ static get HTTP_UNSUPPORTED_MEDIA_TYPE_STATUS() {
+
+ // Return HTTP unsupported media type status
+ return 415;
+ }
+
+ // HTTP bad gateway status
+ static get HTTP_BAD_GATEWAY_STATUS() {
+
+ // Return HTTP bad gateway status
+ return 502;
+ }
+
+ // HTTP gateway timeout status
+ static get HTTP_GATEWAY_TIMEOUT_STATUS() {
+
+ // Return HTTP gateway timeout status
+ return 504;
+ }
+
+ // Double quote pattern
+ static get DOUBLE_QUOTE_PATTERN() {
+
+ // Return double quote pattern
+ return /"/ug;
+ }
+
+ // Enable event
+ static get ENABLE_EVENT() {
+
+ // Return enable event
+ return "CommonEnableEvent";
+ }
+
+ // Primary pointer button bitmask
+ static get PRIMARY_POINTER_BUTTON_BITMASK() {
+
+ // Return primary pointer button bitmask
+ return 1;
+ }
+
+ // January month index
+ static get JANUARY_MONTH_INDEX() {
+
+ // Return january month index
+ return 0;
+ }
+
+ // Sort equal to
+ static get SORT_EQUAL() {
+
+ // Return sort equal
+ return 0;
+ }
+
+ // Sort greater than
+ static get SORT_GREATER_THAN() {
+
+ // Return sort greater than
+ return Common.SORT_EQUAL + 1;
+ }
+
+ // Sort less than
+ static get SORT_LESS_THAN() {
+
+ // Return sort less than
+ return Common.SORT_EQUAL - 1;
+ }
+
+ // Visibility state visible
+ static get VISIBILITY_STATE_VISIBLE() {
+
+ // Return visibility state visible
+ return "visible";
+ }
+
+ // Visibility state hidden
+ static get VISIBILITY_STATE_HIDDEN() {
+
+ // Return visibility state hidden
+ return "hidden";
+ }
+
+ // Canceled error
+ static get CANCELED_ERROR() {
+
+ // Return canceled error
+ return "CommonCancelError";
+ }
+
+ // No cancel occurred
+ static get NO_CANCEL_OCCURRED() {
+
+ // Return no cancel occurred
+ return null;
+ }
+
+ // Hex prefix
+ static get HEX_PREFIX() {
+
+ // Return hex prefix
+ return "0x";
+ }
+
+ // Default HTTP port
+ static get DEFAULT_HTTP_PORT() {
+
+ // Return default HTTP port
+ return 80;
+ }
+
+ // Default HTTPS port
+ static get DEFAULT_HTTPS_PORT() {
+
+ // Return default HTTPS port
+ return 443;
+ }
+
+ // Private
+
+ // Bits in a byte
+ static get BITS_IN_A_BYTE() {
+
+ // Return bits in a byte
+ return 8;
+ }
+
+ // Number string pattern
+ static get NUMBER_STRING_PATTERN() {
+
+ // Return number string pattern
+ return /^[+-]?(?:0(?:\.\d+)?|[1-9]\d*(?:\.\d+)?|\.\d+)$/u;
+ }
+
+ // Hex string pattern
+ static get HEX_STRING_PATTERN() {
+
+ // Return hex string pattern
+ return /^(?:[0-9A-F])+$/iu;
+ }
+
+ // Hex character to value
+ static get HEX_CHARACTER_TO_VALUE() {
+
+ // Return hex character to value
+ return {
+
+ // Zero
+ "0": 0x00,
+
+ // One
+ "1": 0x01,
+
+ // Two
+ "2": 0x02,
+
+ // Three
+ "3": 0x03,
+
+ // Four
+ "4": 0x04,
+
+ // Five
+ "5": 0x05,
+
+ // Six
+ "6": 0x06,
+
+ // Seven
+ "7": 0x07,
+
+ // Eight
+ "8": 0x08,
+
+ // Nine
+ "9": 0x09,
+
+ // Uppercase a
+ "A": 0x0A,
+
+ // Uppercase b
+ "B": 0x0B,
+
+ // Uppercase c
+ "C": 0x0C,
+
+ // Uppercase d
+ "D": 0x0D,
+
+ // Uppercase e
+ "E": 0x0E,
+
+ // Uppercase f
+ "F": 0x0F,
+
+ // Lowercase a
+ "a": 0x0A,
+
+ // Lowercase b
+ "b": 0x0B,
+
+ // Lowercase c
+ "c": 0x0C,
+
+ // Lowercase d
+ "d": 0x0D,
+
+ // Lowercase e
+ "e": 0x0E,
+
+ // Lowercase f
+ "f": 0x0F
+ };
+ }
+
+ // Value to hex character
+ static get VALUE_TO_HEX_CHARACTER() {
+
+ // Return value to hex character
+ return [
+
+ // Zero
+ "0",
+
+ // One
+ "1",
+
+ // Two
+ "2",
+
+ // Three
+ "3",
+
+ // Four
+ "4",
+
+ // Five
+ "5",
+
+ // Six
+ "6",
+
+ // Seven
+ "7",
+
+ // Eight
+ "8",
+
+ // Nine
+ "9",
+
+ // A
+ "a",
+
+ // B
+ "b",
+
+ // C
+ "c",
+
+ // D
+ "d",
+
+ // E
+ "e",
+
+ // F
+ "f"
+ ];
+ }
+
+ // RFC 3339 string pattern
+ static get RFC_3339_STRING_PATTERN() {
+
+ // Return RFC 3339 string pattern
+ return /^(?:\d+)-(?:0[1-9]|1[012])-(?:0[1-9]|[12]\d|3[01])[Tt](?:[01]\d|2[0-3]):(?:[0-5]\d):(?:[0-5]\d|60)(?:\.\d+)?(?:(?:[Zz])|(?:[\+|\-](?:[01]\d|2[0-3]):[0-5]\d))$/u;
+ }
+
+ // Lowercase string pattern
+ static get LOWERCASE_STRING_PATTERN() {
+
+ // Return lowercase string pattern
+ return /^[a-z0-9]+$/u;
+ }
+
+ // Single quote HTML entity
+ static get SINGLE_QUOTE_HTML_ENTITY() {
+
+ // Return single quote HTML entity
+ return "'";
+ }
+
+ // Double quote HTML entity
+ static get DOUBLE_QUOTE_HTML_ENTITY() {
+
+ // Return double quote HTML entity
+ return """;
+ }
+
+ // Grave accent HTML entity
+ static get GRAVE_ACCENT_HTML_ENTITY() {
+
+ // Return grave accent HTML entity
+ return "`";
+ }
+
+ // Single quote pattern
+ static get SINGLE_QUOTE_PATTERN() {
+
+ // Return single quote pattern
+ return /'/ug;
+ }
+
+ // Grave accent pattern
+ static get GRAVE_ACCENT_PATTERN() {
+
+ // Return grave accent pattern
+ return /`/ug;
+ }
+
+ // Return low memory threshold gigabytes
+ static get LOW_MEMORY_THRESHOLD_GIGABYTES() {
+
+ // Return low memory threshold gigabytes
+ return 2;
+ }
+
+ // Return high memory threshold gigabytes
+ static get HIGH_MEMORY_THRESHOLD_GIGABYTES() {
+
+ // Return high memory threshold gigabytes
+ return 8;
+ }
+
+ // URL protocol pattern
+ static get URL_PROTOCOL_PATTERN() {
+
+ // Return URL protocol pattern
+ return /^[^:]+:\/\/.+/u;
+ }
+
+ // URL top-level domain pattern
+ static get URL_TOP_LEVEL_DOMAIN_PATTERN() {
+
+ // Return URL top-level domain pattern
+ return /[^\.]+\.[^\/?:#\s]+(?:[\/?:#\s]|$)/u;
+ }
+
+ // URL domain name pattern
+ static get URL_DOMAIN_NAME_PATTERN() {
+
+ // Return URL domain name pattern
+ return /[^\.]+\.[^\.]+$/u;
+ }
+
+ // URL without top-level domain pattern
+ static get URL_WITHOUT_TOP_LEVEL_DOMAIN_PATTERN() {
+
+ // Return URL without top-level domain pattern
+ return /([^\.]+)\.[^\.]+$/u;
+ }
+
+ // Trailing slashes pattern
+ static get TRAILING_SLASHES_PATTERN() {
+
+ // Return trailing slashes pattern
+ return /\/+$/u;
+ }
+
+ // Duplicate slashes pattern
+ static get DUPLICATE_SLASHES_PATTERN() {
+
+ // Return duplicate slashes pattern
+ return /\/+/ug;
+ }
+
+ // Duplicate newline pattern
+ static get DUPLICATE_NEWLINE_PATTERN() {
+
+ // Return duplicate newline pattern
+ return /\n+/ug;
+ }
+
+ // Trailing zeros pattern
+ static get TRAILING_ZEROS_PATTERN() {
+
+ // Return trailing zeros pattern
+ return /(\.\d*?)0+$/u;
+ }
+
+ // Ending decimal point pattern
+ static get ENDING_DECIMAL_POINT_PATTERN() {
+
+ // Return ending decimal point pattern
+ return /\.$/u;
+ }
+
+ // Trailing newline pattern
+ static get TRAILING_NEWLINE_PATTERN() {
+
+ // Return trailing newline pattern
+ return /\n$/u;
+ }
+
+ // Object type pattern
+ static get OBJECT_TYPE_PATTERN() {
+
+ // Return object type pattern
+ return /^\[object\s*(.*)\]$/u;
+ }
+
+ // Request animation frame timeout milliseconds
+ static get REQUEST_ANIMATION_FRAME_TIMEOUT_MILLISECONDS() {
+
+ // Return request animation frame timeout milliseconds
+ return 100;
+ }
+
+ // Leading whitespace pattern
+ static get LEADING_WHITESPACE_PATTERN() {
+
+ // Return leading whitespace pattern
+ return /^\s+/u;
+ }
+
+ // Trailing whitespace pattern
+ static get TRAILING_WHITESPACE_PATTERN() {
+
+ // Return leading whitespace pattern
+ return /\s+$/u;
+ }
+
+ // Has whitespace pattern
+ static get HAS_WHITESPACE_PATTERN() {
+
+ // Return has whitespace pattern
+ return /\s/ug;
+ }
+}
+
+
+// Main function
+
+// Set global object's common
+globalThis["Common"] = Common;
+
+// Check if jQuery exists
+if(typeof jQuery === "function") {
+
+ // Disable tab
+ $["prototype"].disableTab = function() {
+
+ // Go through each element
+ this.each(function() {
+
+ // Add no tab index to element
+ $(this).attr("tabindex", Common.NO_TAB_INDEX);
+ });
+
+ // Return elements
+ return this;
+ };
+
+ // Enable tab
+ $["prototype"].enableTab = function() {
+
+ // Go through each element
+ this.each(function() {
+
+ // Remove tab index from element
+ $(this).removeAttr("tabindex");
+ });
+
+ // Return elements
+ return this;
+ };
+
+ // Disable
+ $["prototype"].disable = function() {
+
+ // Go through each element
+ this.each(function() {
+
+ // Disable the element
+ $(this).prop("disabled", true);
+ });
+
+ // Return elements
+ return this;
+ };
+
+ // Enable
+ $["prototype"].enable = function() {
+
+ // Go through each element
+ this.each(function() {
+
+ // Enable the element and trigger an enable event on the element
+ $(this).prop("disabled", false).trigger(Common.ENABLE_EVENT);
+ });
+
+ // Return elements
+ return this;
+ };
+
+ // Outer Html
+ $["prototype"].outerHtml = function() {
+
+ // Return element's outer HTML
+ return this.wrap("
").parent().html();
+ };
+
+ // Scroll idle duration milliseconds
+ var SCROLL_IDLE_DURATION_MILLISECONDS = 250;
+
+ // Scroll stopped
+ $["fn"].scrollStopped = function(callback) {
+
+ // Go through each element
+ this.each(function() {
+
+ // Set self
+ var self = $(this);
+
+ // Get index
+ var index = (typeof self.data("scrollStoppedIndex") === "undefined") ? 0 : self.data("scrollStoppedIndex");
+
+ // Self scroll scroll stopped index event
+ self.on("scroll.scrollStopped" + index.toFixed(), function(event) {
+
+ // Check if scroll stopped timeout index exists
+ if(typeof self.data("scrollStoppedTimeout" + index.toFixed()) !== "undefined") {
+
+ // Clear scroll stopped timeout index
+ clearTimeout(self.data("scrollStoppedTimeout" + index.toFixed()));
+ }
+
+ // Set scroll stopped timeout index
+ self.data("scrollStoppedTimeout" + index.toFixed(), setTimeout(function() {
+
+ // Remove scroll stopped timeout index
+ self.removeData("scrollStoppedTimeout" + index.toFixed());
+
+ // Run callback
+ callback.bind(self)(event);
+
+ }, SCROLL_IDLE_DURATION_MILLISECONDS));
+ });
+
+ // Increment index
+ self.data("scrollStoppedIndex", (index === Number.MAX_SAFE_INTEGER) ? 0 : index + 1);
+ });
+
+ // Return elements
+ return this;
+ };
+
+ // Transition end timeout milliseconds
+ var TRANSITION_END_TIMEOUT_MILLISECONDS = 100;
+
+ // Transition end or timeout
+ $["fn"].transitionEndOrTimeout = function(callback, property) {
+
+ // Go through each element
+ this.each(function() {
+
+ // Set self
+ var self = $(this);
+
+ // Set timeout
+ var timeout = TRANSITION_END_TIMEOUT_MILLISECONDS;
+
+ // Go through all of the element's transition properties
+ var properties = self.css("transition-property").split(",");
+ for(var i = 0; i < properties["length"]; ++i) {
+
+ // Check if property was found
+ if(properties[i].trim() === property) {
+
+ // Get transition's duration
+ var duration = self.css("transition-duration").split(",")[i].trim();
+
+ // Get transition's delay
+ var delay = self.css("transition-delay").split(",")[i].trim();
+
+ // Set timeout to the duration
+ timeout = parseFloat(duration) * ((duration.indexOf("ms") !== Common.INDEX_NOT_FOUND) ? 1 : Common.MILLISECONDS_IN_A_SECOND) + parseFloat(delay) * ((delay.indexOf("ms") !== Common.INDEX_NOT_FOUND) ? 1 : Common.MILLISECONDS_IN_A_SECOND) + TRANSITION_END_TIMEOUT_MILLISECONDS;
+
+ // Break
+ break;
+ }
+ }
+
+ // Get index
+ var index = (typeof self.data("transitionEndOrTimeoutIndex") === "undefined") ? 0 : self.data("transitionEndOrTimeoutIndex");
+
+ // Set transition end or timeout timeout index
+ self.data("transitionEndOrTimeoutTimeout" + index.toFixed(), setTimeout(function() {
+
+ // Turn off transition end transition end or timeout index event
+ self.off("transitionend.transitionEndOrTimeout" + index.toFixed());
+
+ // Remove transition end or timeout timeout index
+ self.removeData("transitionEndOrTimeoutTimeout" + index.toFixed());
+
+ // Run callback
+ callback.bind(self)();
+
+ }, timeout));
+
+ // Self transition end transition end or timeout index event
+ self.one("transitionend.transitionEndOrTimeout" + index.toFixed(), function(event) {
+
+ // Clear transition end or timeout timeout index
+ clearTimeout(self.data("transitionEndOrTimeoutTimeout" + index.toFixed()));
+
+ // Remove transition end or timeout timeout index
+ self.removeData("transitionEndOrTimeoutTimeout" + index.toFixed());
+
+ // Run callback
+ callback.bind(self)(event);
+ });
+
+ // Increment index
+ self.data("transitionEndOrTimeoutIndex", (index === Number.MAX_SAFE_INTEGER) ? 0 : index + 1);
+ });
+
+ // Return elements
+ return this;
+ };
+
+ // Off transition end or timeout
+ $["fn"].offTransitionEndOrTimeout = function() {
+
+ // Go through each element
+ this.each(function() {
+
+ // Set self
+ var self = $(this);
+
+ // Turn off transition end event
+ self.off("transitionend");
+
+ // Get data
+ var data = self.data();
+
+ // Go through all data
+ for(var key in data) {
+
+ if(data.hasOwnProperty(key) === true) {
+
+ // Check if data is a transition end or timeout timeout index
+ if(key.indexOf("transitionEndOrTimeoutTimeout") === 0) {
+
+ // Clear transition end or timeout timeout index
+ clearTimeout(self.data(key));
+
+ // Remove transition end or timeout timeout index
+ self.removeData(key);
+ }
+ }
+ }
+ });
+
+ // Return elements
+ return this;
+ };
+}
+
+// Check if BigNumber exists
+if(typeof BigNumber === "function") {
+
+ // Big endian
+ BigNumber.BIG_ENDIAN = 0;
+
+ // Little endian
+ BigNumber.LITTLE_ENDIAN = 1;
+
+ // Any length
+ BigNumber.ANY_LENGTH = -1;
+
+ // To bytes
+ BigNumber.prototype.toBytes = function(endianness = BigNumber.LITTLE_ENDIAN, length = BigNumber.ANY_LENGTH) {
+
+ // Check if is number isn't supported
+ if(this.isFinite() === false || this.isNaN() === true || this.isNegative() === true || this.isInteger() === false) {
+
+ // Throw error
+ throw "Unsupported number.";
+ }
+
+ // Otherwise
+ else {
+
+ // Create bytes
+ var bytes = new Array((length === BigNumber.ANY_LENGTH) ? 1 : length).fill(0);
+
+ // Make number a whole number
+ var temp = this.dividedToIntegerBy(1);
+
+ // Go through all bytes in the whole number
+ for(var i = 0; temp.isGreaterThan(0); ++i) {
+
+ // Get byte from the whole number
+ var byte = temp.modulo(Common.BYTE_MAX_VALUE + 1).toNumber();
+
+ // Remove byte from the whole number
+ temp = temp.dividedToIntegerBy(Common.BYTE_MAX_VALUE + 1);
+
+ // Check if space exists in bytes for the byte
+ if(i < bytes["length"]) {
+
+ // Set byte in bytes
+ bytes[i] = byte;
+ }
+
+ // Otherwise check if length is Constrained
+ else if(length !== BigNumber.ANY_LENGTH) {
+
+ // Throw error
+ throw "Insufficient length.";
+ }
+
+ // Otherwise
+ else {
+
+ // Append byte to bytes
+ bytes.push(byte);
+ }
+ }
+
+ // Check if endianness is big endian
+ if(endianness === BigNumber.BIG_ENDIAN) {
+
+ // Reverse bytes order
+ bytes = bytes.reverse();
+ }
+
+ // Return bytes
+ return new Uint8Array(bytes);
+ }
+ };
+}
+
+// Check if object exists
+if(typeof Object === "function") {
+
+ // Is object
+ Object.isObject = function(variable) {
+
+ // Try
+ try {
+
+ // Return if variable is an object
+ return Object.getPrototypeOf(variable)["constructor"]["name"] === "Object";
+ }
+
+ // Catch errors
+ catch(error) {
+
+ // Return false
+ return false;
+ }
+ };
+}
+
+// Check if Uint8Array exist
+if(typeof Uint8Array === "function") {
+
+ // Read uint32 little endian
+ Uint8Array["prototype"].readUInt32LE = function(index) {
+
+ // Return value at index as a uint32 in little endian
+ return this[index] | (this[index + 1] << Common.BITS_IN_A_BYTE) | (this[index + 2] << (Common.BITS_IN_A_BYTE * 2)) | (this[index + 3] << (Common.BITS_IN_A_BYTE * 3));
+ };
+
+ // Read uint16 little endian
+ Uint8Array["prototype"].readUInt16LE = function(index) {
+
+ // Return value at index as a uint16 in little endian
+ return this[index] | (this[index + 1] << Common.BITS_IN_A_BYTE);
+ };
+}
+
+// Check if cross-origin isolated doesn't exist
+if(typeof crossOriginIsolated === "undefined") {
+
+ // Set cross-origin isolated to false
+ var crossOriginIsolated = false;
+}
diff --git a/scripts/consensus.js b/scripts/consensus.js
new file mode 100755
index 0000000..4eda8fe
--- /dev/null
+++ b/scripts/consensus.js
@@ -0,0 +1,1587 @@
+// Use strict
+"use strict";
+
+
+// Classes
+
+// Consensus class
+class Consensus {
+
+ // Public
+
+ // Initialize
+ static initialize() {
+
+ // Update saved wallet type and network type
+ Consensus.getWalletType();
+ Consensus.getNetworkType();
+ }
+
+ // Get wallet type
+ static getWalletType() {
+
+ // Check if consensus's wallet type doesn't exist
+ if(typeof Consensus.walletType === "undefined") {
+
+ // Check if not not a web worker and local storage is usable
+ if((typeof self === "undefined" || typeof WorkerGlobalScope === "undefined" || self instanceof WorkerGlobalScope === false) && typeof localStorage !== "undefined") {
+
+ // Get the saved wallet type
+ var walletType = localStorage.getItem(Consensus.WALLET_TYPE_LOCAL_STORAGE_NAME);
+
+ // Check if wallet type doesn't exist or isn't valid
+ if(walletType === Common.INVALID_LOCAL_STORAGE_ITEM || Number.isNaN(parseInt(walletType, Common.DECIMAL_NUMBER_BASE)) === true) {
+
+ // Set wallet type to the default wallet type
+ walletType = Consensus.DEFAULT_WALLET_TYPE;
+ }
+
+ // Otherwise
+ else {
+
+ // Get wallet type in the correct format
+ walletType = parseInt(walletType, Common.DECIMAL_NUMBER_BASE);
+
+ // Check if the wallet type doesn't exist
+ if(walletType < Consensus.MWC_WALLET_TYPE || walletType > Consensus.EPIC_WALLET_TYPE) {
+
+ // Set wallet type to the default wallet type
+ walletType = Consensus.DEFAULT_WALLET_TYPE;
+ }
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Set wallet type to the default wallet type
+ var walletType = Consensus.DEFAULT_WALLET_TYPE;
+ }
+
+ // Get URL parameters
+ var urlParameters = Common.getUrlParameters();
+
+ // Check if override wallet type exists
+ if(Consensus.OVERRIDE_WALLET_TYPE_URL_PARAMETER_NAME in urlParameters === true) {
+
+ // Check override wallet type
+ switch(urlParameters[Consensus.OVERRIDE_WALLET_TYPE_URL_PARAMETER_NAME]) {
+
+ // MWC wallet
+ case Consensus.WALLET_MWC_TEXT_VALUE:
+
+ // Set wallet type to MWC wallet type
+ walletType = Consensus.MWC_WALLET_TYPE;
+
+ // Break
+ break;
+
+ // GRIN wallet
+ case Consensus.WALLET_GRIN_TEXT_VALUE:
+
+ // Set wallet type to GRIN wallet type
+ walletType = Consensus.GRIN_WALLET_TYPE;
+
+ // Break
+ break;
+
+ // EPIC wallet
+ case Consensus.WALLET_EPIC_TEXT_VALUE:
+
+ // Set wallet type to EPIC wallet type
+ walletType = Consensus.EPIC_WALLET_TYPE;
+
+ // Break
+ break;
+ }
+ }
+
+ // Check if not not a web worker and local storage is usable
+ if((typeof self === "undefined" || typeof WorkerGlobalScope === "undefined" || self instanceof WorkerGlobalScope === false) && typeof localStorage !== "undefined") {
+
+ // Check if saved wallet type needs to be updated
+ if(walletType.toFixed() !== localStorage.getItem(Consensus.WALLET_TYPE_LOCAL_STORAGE_NAME)) {
+
+ // Try
+ try {
+
+ // Save wallet type in local storage
+ localStorage.setItem(Consensus.WALLET_TYPE_LOCAL_STORAGE_NAME, walletType.toFixed());
+ }
+
+ // Catch errors
+ catch(error) {
+
+ }
+ }
+ }
+
+ // Set consensus's wallet type
+ Consensus.walletType = walletType;
+ }
+
+ // Return consensus's wallet type
+ return Consensus.walletType;
+ }
+
+ // Get network type
+ static getNetworkType() {
+
+ // Check if consensus's network type doesn't exist
+ if(typeof Consensus.networkType === "undefined") {
+
+ // Check if not not a web worker and local storage is usable
+ if((typeof self === "undefined" || typeof WorkerGlobalScope === "undefined" || self instanceof WorkerGlobalScope === false) && typeof localStorage !== "undefined") {
+
+ // Get the saved network type
+ var networkType = localStorage.getItem(Consensus.NETWORK_TYPE_LOCAL_STORAGE_NAME);
+
+ // Check if network type doesn't exist or isn't valid
+ if(networkType === Common.INVALID_LOCAL_STORAGE_ITEM || Number.isNaN(parseInt(networkType, Common.DECIMAL_NUMBER_BASE)) === true) {
+
+ // Set network type to the default network type
+ networkType = Consensus.DEFAULT_NETWORK_TYPE;
+ }
+
+ // Otherwise
+ else {
+
+ // Get network type in the correct format
+ networkType = parseInt(networkType, Common.DECIMAL_NUMBER_BASE);
+
+ // Check if the network type doesn't exist
+ if(networkType < Consensus.MAINNET_NETWORK_TYPE || networkType > Consensus.TESTNET_NETWORK_TYPE) {
+
+ // Set network type to the default network type
+ networkType = Consensus.DEFAULT_NETWORK_TYPE;
+ }
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Set network type to the default network type
+ var networkType = Consensus.DEFAULT_NETWORK_TYPE;
+ }
+
+ // Get URL parameters
+ var urlParameters = Common.getUrlParameters();
+
+ // Check if override network type exists
+ if(Consensus.OVERRIDE_NETWORK_TYPE_URL_PARAMETER_NAME in urlParameters === true) {
+
+ // Check override network type
+ switch(urlParameters[Consensus.OVERRIDE_NETWORK_TYPE_URL_PARAMETER_NAME]) {
+
+ // Mainnet network
+ case Consensus.NETWORK_MAINNET_TEXT_VALUE:
+
+ // Set network type to mainnet network type
+ networkType = Consensus.MAINNET_NETWORK_TYPE;
+
+ // Break
+ break;
+
+ // Testnet network
+ case Consensus.NETWORK_TESTNET_TEXT_VALUE:
+
+ // Check wallet type
+ switch(Consensus.getWalletType()) {
+
+ // GRIN wallet
+ case Consensus.GRIN_WALLET_TYPE:
+
+ // Set network type to testnet network type
+ networkType = Consensus.TESTNET_NETWORK_TYPE;
+
+ // Break
+ break;
+ }
+
+ // Break
+ break;
+
+ // Floonet network
+ case Consensus.NETWORK_FLOONET_TEXT_VALUE:
+
+ // Check wallet type
+ switch(Consensus.getWalletType()) {
+
+ // MWC or EPIC wallet
+ case Consensus.MWC_WALLET_TYPE:
+ case Consensus.EPIC_WALLET_TYPE:
+
+ // Set network type to testnet network type
+ networkType = Consensus.TESTNET_NETWORK_TYPE;
+
+ // Break
+ break;
+ }
+
+ // Break
+ break;
+ }
+ }
+
+ // Check if not not a web worker and local storage is usable
+ if((typeof self === "undefined" || typeof WorkerGlobalScope === "undefined" || self instanceof WorkerGlobalScope === false) && typeof localStorage !== "undefined") {
+
+ // Check if saved network type needs to be updated
+ if(networkType.toFixed() !== localStorage.getItem(Consensus.NETWORK_TYPE_LOCAL_STORAGE_NAME)) {
+
+ // Try
+ try {
+
+ // Save network type in local storage
+ localStorage.setItem(Consensus.NETWORK_TYPE_LOCAL_STORAGE_NAME, networkType.toFixed());
+ }
+
+ // Catch errors
+ catch(error) {
+
+ }
+ }
+ }
+
+ // Set consensus's network type
+ Consensus.networkType = networkType;
+ }
+
+ // Return consensus's network type
+ return Consensus.networkType;
+ }
+
+ // Wallet type to text
+ static walletTypeToText(walletType) {
+
+ // Check wallet type
+ switch(walletType) {
+
+ // MWC wallet type
+ case Consensus.MWC_WALLET_TYPE:
+
+ // Return MWC wallet text value
+ return Consensus.WALLET_MWC_TEXT_VALUE;
+
+ // GRIN wallet type
+ case Consensus.GRIN_WALLET_TYPE:
+
+ // Return GRIN wallet text value
+ return Consensus.WALLET_GRIN_TEXT_VALUE;
+
+ // EPIC wallet type
+ case Consensus.EPIC_WALLET_TYPE:
+
+ // Return EPIC wallet text value
+ return Consensus.WALLET_EPIC_TEXT_VALUE;
+ }
+ }
+
+ // Network type to text
+ static networkTypeToText(networkType) {
+
+ // Check network type
+ switch(networkType) {
+
+ // Mainnet network type
+ case Consensus.MAINNET_NETWORK_TYPE:
+
+ // Return mainnet network text value
+ return Consensus.NETWORK_MAINNET_TEXT_VALUE;
+
+ // Testnet network type
+ case Consensus.TESTNET_NETWORK_TYPE:
+
+ // Check wallet type
+ switch(Consensus.getWalletType()) {
+
+ // MWC or EPIC wallet
+ case Consensus.MWC_WALLET_TYPE:
+ case Consensus.EPIC_WALLET_TYPE:
+
+ // Return floonet network text value
+ return Consensus.NETWORK_FLOONET_TEXT_VALUE;
+
+ // GRIN wallet
+ case Consensus.GRIN_WALLET_TYPE:
+
+ // Return testnet network text value
+ return Consensus.NETWORK_TESTNET_TEXT_VALUE;
+ }
+ }
+ }
+
+ // Get reward
+ static getReward(isMainnet, fees, height) {
+
+ // Get block reward at height
+ var reward = Consensus.getBlockReward(isMainnet, height);
+
+ // Add fee to reward
+ reward = reward.plus(fees);
+
+ // Return reward
+ return reward;
+ }
+
+ // Is valid header version
+ static isValidHeaderVersion(isMainnet, height, version) {
+
+ // Return if version is valid at height while taking hard forks into account
+ return version.isEqualTo(Consensus.getHeaderVersion(isMainnet, height)) === true;
+ }
+
+ // Is no recent duplicate kernel enabled
+ static isNoRecentDuplicateKernelsEnabled(isMainnet) {
+
+ // Check wallet type
+ switch(Consensus.getWalletType()) {
+
+ // MWC wallet
+ case Consensus.MWC_WALLET_TYPE:
+
+ // Check if is mainnet
+ if(isMainnet === true) {
+
+ // Return false
+ return false;
+ }
+
+ // Otherwise
+ else {
+
+ // TODO Support no recent duplicate kernels
+ return false;
+ }
+
+ // Break
+ break;
+
+ // GRIN wallet
+ case Consensus.GRIN_WALLET_TYPE:
+
+ // Check if is mainnet
+ if(isMainnet === true) {
+
+ // Return false
+ return false;
+ }
+
+ // Otherwise
+ else {
+
+ // TODO Support no recent duplicate kernels
+ return false;
+ }
+
+ // Break
+ break;
+
+ // EPIC wallet
+ case Consensus.EPIC_WALLET_TYPE:
+
+ // Return false
+ return false;
+ }
+ }
+
+ // Block time seconds
+ static get BLOCK_TIME_SECONDS() {
+
+ // Return block time seconds
+ return 60;
+ }
+
+ // Block height minute
+ static get BLOCK_HEIGHT_MINUTE() {
+
+ // Return block height minute
+ return Math.floor(Common.SECONDS_IN_A_MINUTE / Consensus.BLOCK_TIME_SECONDS);
+ }
+
+ // Block height hour
+ static get BLOCK_HEIGHT_HOUR() {
+
+ // Return block height hour
+ return Common.MINUTES_IN_AN_HOUR * Consensus.BLOCK_HEIGHT_MINUTE;
+ }
+
+ // Block height day
+ static get BLOCK_HEIGHT_DAY() {
+
+ // Return block height day
+ return Common.HOURS_IN_A_DAY * Consensus.BLOCK_HEIGHT_HOUR;
+ }
+
+ // Block height week
+ static get BLOCK_HEIGHT_WEEK() {
+
+ // Return block height week
+ return Common.DAYS_IN_A_WEEK * Consensus.BLOCK_HEIGHT_DAY;
+ }
+
+ // Block height year
+ static get BLOCK_HEIGHT_YEAR() {
+
+ // Return block height year
+ return Common.WEEKS_IN_A_YEAR * Consensus.BLOCK_HEIGHT_WEEK;
+ }
+
+ // Coinbase maturity
+ static get COINBASE_MATURITY() {
+
+ // Return coinbase maturity
+ return Consensus.BLOCK_HEIGHT_DAY;
+ }
+
+ // First block height
+ static get FIRST_BLOCK_HEIGHT() {
+
+ // Return first block height
+ return 0;
+ }
+
+ // Value number base
+ static get VALUE_NUMBER_BASE() {
+
+ // Check wallet type
+ switch(Consensus.getWalletType()) {
+
+ // MWC or GRIN wallet
+ case Consensus.MWC_WALLET_TYPE:
+ case Consensus.GRIN_WALLET_TYPE:
+
+ // Return value number base
+ return 1E9;
+
+ // EPIC wallet
+ case Consensus.EPIC_WALLET_TYPE:
+
+ // Return value number base
+ return 1E8;
+ }
+ }
+
+ // Currency name
+ static get CURRENCY_NAME() {
+
+ // Check wallet type
+ switch(Consensus.getWalletType()) {
+
+ // MWC wallet
+ case Consensus.MWC_WALLET_TYPE:
+
+ // Return currency name
+ return "MWC";
+
+ // GRIN wallet
+ case Consensus.GRIN_WALLET_TYPE:
+
+ // Return currency name
+ return "GRIN";
+
+ // EPIC wallet
+ case Consensus.EPIC_WALLET_TYPE:
+
+ // Return currency name
+ return "EPIC";
+ }
+ }
+
+ // Slatepack address human-readable part
+ static get SLATEPACK_ADDRESS_HUMAN_READABLE_PART() {
+
+ // Check wallet type
+ switch(Consensus.getWalletType()) {
+
+ // GRIN wallet
+ case Consensus.GRIN_WALLET_TYPE:
+
+ // Check network type
+ switch(Consensus.getNetworkType()) {
+
+ // Mainnet network type
+ case Consensus.MAINNET_NETWORK_TYPE:
+
+ // Return Slatepack address human-readable part
+ return "grin";
+
+ // Testnet network type
+ case Consensus.TESTNET_NETWORK_TYPE:
+
+ // Return Slatepack address human-readable part
+ return "tgrin";
+ }
+ }
+ }
+
+ // MWC wallet type
+ static get MWC_WALLET_TYPE() {
+
+ // Return MWC wallet type
+ return 0;
+ }
+
+ // GRIN wallet type
+ static get GRIN_WALLET_TYPE() {
+
+ // Return GRIN wallet type
+ return Consensus.MWC_WALLET_TYPE + 1;
+ }
+
+ // EPIC wallet type
+ static get EPIC_WALLET_TYPE() {
+
+ // Return EPIC wallet type
+ return Consensus.GRIN_WALLET_TYPE + 1;
+ }
+
+ // Mainnet network type
+ static get MAINNET_NETWORK_TYPE() {
+
+ // Return mainnet network type
+ return 0;
+ }
+
+ // Testnet type
+ static get TESTNET_NETWORK_TYPE() {
+
+ // Return testnet network type
+ return Consensus.MAINNET_NETWORK_TYPE + 1;
+ }
+
+ // Legacy header version
+ static get LEGACY_HEADER_VERSION() {
+
+ // Check wallet type
+ switch(Consensus.getWalletType()) {
+
+ // MWC or GRIN wallet
+ case Consensus.MWC_WALLET_TYPE:
+ case Consensus.GRIN_WALLET_TYPE:
+
+ // Return legacy header version
+ return new BigNumber(1);
+
+ // EPIC wallet
+ case Consensus.EPIC_WALLET_TYPE:
+
+ // Return legacy header version
+ return new BigNumber(6);
+ }
+ }
+
+ // Block input weight
+ static get BLOCK_INPUT_WEIGHT() {
+
+ // Return block input weight
+ return 1;
+ }
+
+ // Block output weight
+ static get BLOCK_OUTPUT_WEIGHT() {
+
+ // Return block output weight
+ return 21;
+ }
+
+ // Block kernel weight
+ static get BLOCK_KERNEL_WEIGHT() {
+
+ // Return block kernel weight
+ return 3;
+ }
+
+ // Maximum block weight
+ static get MAXIMUM_BLOCK_WEIGHT() {
+
+ // Return maximum block weight
+ return 40000;
+ }
+
+ // BIP44 coin type
+ static get BIP44_COIN_TYPE() {
+
+ // Check wallet type
+ switch(Consensus.getWalletType()) {
+
+ // MWC wallet
+ case Consensus.MWC_WALLET_TYPE:
+
+ // Check network type
+ switch(Consensus.getNetworkType()) {
+
+ // Mainnet network
+ case Consensus.MAINNET_NETWORK_TYPE:
+
+ // Return BIP44 coin type
+ return 593;
+
+ // Testnet network
+ case Consensus.TESTNET_NETWORK_TYPE:
+
+ // Return BIP44 coin type
+ return 1;
+ }
+
+ // Break
+ break;
+
+ // GRIN wallet
+ case Consensus.GRIN_WALLET_TYPE:
+
+ // Check network type
+ switch(Consensus.getNetworkType()) {
+
+ // Mainnet network
+ case Consensus.MAINNET_NETWORK_TYPE:
+
+ // Return BIP44 coin type
+ return 592;
+
+ // Testnet network
+ case Consensus.TESTNET_NETWORK_TYPE:
+
+ // Return BIP44 coin type
+ return 1;
+ }
+
+ // Break
+ break;
+
+ // EPIC wallet
+ case Consensus.EPIC_WALLET_TYPE:
+
+ // Check network type
+ switch(Consensus.getNetworkType()) {
+
+ // Mainnet network
+ case Consensus.MAINNET_NETWORK_TYPE:
+
+ // Return BIP44 coin type
+ return 23000;
+
+ // Testnet network
+ case Consensus.TESTNET_NETWORK_TYPE:
+
+ // Return BIP44 coin type
+ return 1;
+ }
+
+ // Break
+ break;
+ }
+ }
+
+ // Explorer kernel excess URL
+ static get EXPLORER_KERNEL_EXCESS_URL() {
+
+ // Check wallet type
+ switch(Consensus.getWalletType()) {
+
+ // MWC wallet
+ case Consensus.MWC_WALLET_TYPE:
+
+ // Check network type
+ switch(Consensus.getNetworkType()) {
+
+ // Mainnet network
+ case Consensus.MAINNET_NETWORK_TYPE:
+
+ // Return explorer kernel excess URL
+ return "https://explorer.mwc.mw/#k";
+
+ // Testnet network
+ case Consensus.TESTNET_NETWORK_TYPE:
+
+ // Return explorer kernel excess URL
+ return "https://explorer.floonet.mwc.mw/#k";
+ }
+
+ // Break
+ break;
+
+ // GRIN wallet
+ case Consensus.GRIN_WALLET_TYPE:
+
+ // Check network type
+ switch(Consensus.getNetworkType()) {
+
+ // Mainnet network
+ case Consensus.MAINNET_NETWORK_TYPE:
+
+ // Return no explorer URL
+ return Consensus.NO_EXPLORER_URL;
+
+ // Testnet network
+ case Consensus.TESTNET_NETWORK_TYPE:
+
+ // Return no explorer URL
+ return Consensus.NO_EXPLORER_URL;
+ }
+
+ // Break
+ break;
+
+ // EPIC wallet
+ case Consensus.EPIC_WALLET_TYPE:
+
+ // Check network type
+ switch(Consensus.getNetworkType()) {
+
+ // Mainnet network
+ case Consensus.MAINNET_NETWORK_TYPE:
+
+ // Return no explorer URL
+ return Consensus.NO_EXPLORER_URL;
+
+ // Testnet network
+ case Consensus.TESTNET_NETWORK_TYPE:
+
+ // Return no explorer URL
+ return Consensus.NO_EXPLORER_URL;
+ }
+
+ // Break
+ break;
+ }
+ }
+
+ // Explorer output commitment URL
+ static get EXPLORER_OUTPUT_COMMITMENT_URL() {
+
+ // Check wallet type
+ switch(Consensus.getWalletType()) {
+
+ // MWC wallet
+ case Consensus.MWC_WALLET_TYPE:
+
+ // Check network type
+ switch(Consensus.getNetworkType()) {
+
+ // Mainnet network
+ case Consensus.MAINNET_NETWORK_TYPE:
+
+ // Return explorer output commitment URL
+ return "https://explorer.mwc.mw/#o";
+
+ // Testnet network
+ case Consensus.TESTNET_NETWORK_TYPE:
+
+ // Return explorer output commitment URL
+ return "https://explorer.floonet.mwc.mw/#o";
+ }
+
+ // Break
+ break;
+
+ // GRIN wallet
+ case Consensus.GRIN_WALLET_TYPE:
+
+ // Check network type
+ switch(Consensus.getNetworkType()) {
+
+ // Mainnet network
+ case Consensus.MAINNET_NETWORK_TYPE:
+
+ // Return no explorer URL
+ return Consensus.NO_EXPLORER_URL;
+
+ // Testnet network
+ case Consensus.TESTNET_NETWORK_TYPE:
+
+ // Return no explorer URL
+ return Consensus.NO_EXPLORER_URL;
+ }
+
+ // Break
+ break;
+
+ // EPIC wallet
+ case Consensus.EPIC_WALLET_TYPE:
+
+ // Check network type
+ switch(Consensus.getNetworkType()) {
+
+ // Mainnet network
+ case Consensus.MAINNET_NETWORK_TYPE:
+
+ // Return explorer output commitment URL
+ return "https://explorer.epiccash.com/blockdetail/";
+
+ // Testnet network
+ case Consensus.TESTNET_NETWORK_TYPE:
+
+ // Return no explorer URL
+ return Consensus.NO_EXPLORER_URL;
+ }
+
+ // Break
+ break;
+ }
+ }
+
+ // No explorer URL
+ static get NO_EXPLORER_URL() {
+
+ // Return no explorer URL
+ return null;
+ }
+
+ // Hardware waller starting height
+ static get HARDWARE_WALLET_STARTING_HEIGHT() {
+
+ // Check wallet type
+ switch(Consensus.getWalletType()) {
+
+ // MWC wallet
+ case Consensus.MWC_WALLET_TYPE:
+
+ // Check network type
+ switch(Consensus.getNetworkType()) {
+
+ // Mainnet network
+ case Consensus.MAINNET_NETWORK_TYPE:
+
+ // Return hardware wallet starting height
+ return 1239928;
+
+ // Testnet network
+ case Consensus.TESTNET_NETWORK_TYPE:
+
+ // Return hardware wallet starting height
+ return 1115028;
+ }
+
+ // Break
+ break;
+
+ // GRIN wallet
+ case Consensus.GRIN_WALLET_TYPE:
+
+ // Check network type
+ switch(Consensus.getNetworkType()) {
+
+ // Mainnet network
+ case Consensus.MAINNET_NETWORK_TYPE:
+
+ // Return hardware wallet starting height
+ return 1687446;
+
+ // Testnet network
+ case Consensus.TESTNET_NETWORK_TYPE:
+
+ // Return hardware wallet starting height
+ return 1195819;
+ }
+
+ // Break
+ break;
+
+ // EPIC wallet
+ case Consensus.EPIC_WALLET_TYPE:
+
+ // Check network type
+ switch(Consensus.getNetworkType()) {
+
+ // Mainnet network
+ case Consensus.MAINNET_NETWORK_TYPE:
+
+ // Return hardware wallet starting height
+ return 1943415;
+
+ // Testnet network
+ case Consensus.TESTNET_NETWORK_TYPE:
+
+ // Return hardware wallet starting height
+ return 0;
+ }
+
+ // Break
+ break;
+ }
+ }
+
+ // Private
+
+ // Get block reward
+ static getBlockReward(isMainnet, height) {
+
+ // Check wallet type
+ switch(Consensus.getWalletType()) {
+
+ // MWC wallet
+ case Consensus.MWC_WALLET_TYPE:
+
+ // Check if height is the first block height
+ if(height.isEqualTo(Consensus.FIRST_BLOCK_HEIGHT) === true)
+
+ // Return genesis block reward
+ return new BigNumber("10000000041800000");
+
+ // Otherwise check if height is less than the second block offset
+ else if(height.isLessThan(Consensus.getEpochBlockOffset(isMainnet, 2)) === true)
+
+ // Return reward during the first epoch range
+ return new BigNumber(2380952380);
+
+ // Otherwise check if height is less than the third block offset
+ else if(height.isLessThan(Consensus.getEpochBlockOffset(isMainnet, 3)) === true)
+
+ // Return reward during the second epoch range
+ return new BigNumber(600000000);
+
+ // Otherwise check if height is less than the fourth block offset
+ else if(height.isLessThan(Consensus.getEpochBlockOffset(isMainnet, 4)) === true)
+
+ // Return reward during the third epoch range
+ return new BigNumber(450000000);
+
+ // Otherwise check if height is less than the fifth block offset
+ else if(height.isLessThan(Consensus.getEpochBlockOffset(isMainnet, 5)) === true)
+
+ // Return reward during the fourth epoch range
+ return new BigNumber(300000000);
+
+ // Otherwise check if height is less than the sixth block offset
+ else if(height.isLessThan(Consensus.getEpochBlockOffset(isMainnet, 6)) === true)
+
+ // Return reward during the fifth epoch range
+ return new BigNumber(250000000);
+
+ // Otherwise check if height is less than the seventh block offset
+ else if(height.isLessThan(Consensus.getEpochBlockOffset(isMainnet, 7)) === true)
+
+ // Return reward during the sixth epoch range
+ return new BigNumber(200000000);
+
+ // Otherwise check if height is less than the eighth block offset
+ else if(height.isLessThan(Consensus.getEpochBlockOffset(isMainnet, 8)) === true)
+
+ // Return reward during the seventh epoch range
+ return new BigNumber(150000000);
+
+ // Otherwise check if height is less than the ninth block offset
+ else if(height.isLessThan(Consensus.getEpochBlockOffset(isMainnet, 9)) === true)
+
+ // Return reward during the eighth epoch range
+ return new BigNumber(100000000);
+
+ // Otherwise check if height is less than the tenth block offset
+ else if(height.isLessThan(Consensus.getEpochBlockOffset(isMainnet, 10)) === true)
+
+ // Return reward during the ninth epoch range
+ return new BigNumber(50000000);
+
+ // Otherwise check if height is less than the eleventh block offset
+ else if(height.isLessThan(Consensus.getEpochBlockOffset(isMainnet, 11)) === true)
+
+ // Return reward during the tenth epoch range
+ return new BigNumber(25000000);
+
+ // Otherwise check if height is less than the eleventh block offset plus the eleventh epoch duration
+ else if(height.isLessThan(Consensus.getEpochBlockOffset(isMainnet, 11).plus(Consensus.getEpochDuration(isMainnet, 11))) === true)
+
+ // Return reward during the eleventh epoch range
+ return new BigNumber(10000000);
+
+ // Otherwise check if height is equal to the eleventh block offset plus the eleventh epoch duration
+ else if(height.isEqualTo(Consensus.getEpochBlockOffset(isMainnet, 11).plus(Consensus.getEpochDuration(isMainnet, 11))) === true)
+
+ // Return reward during the twelfth epoch range
+ return new BigNumber(2211980);
+
+ // Otherwise
+ else
+
+ // Return zero
+ return new BigNumber(0);
+
+ // Break
+ break;
+
+ // GRIN wallet
+ case Consensus.GRIN_WALLET_TYPE:
+
+ // Return block reward
+ return new BigNumber("60000000000");
+
+ // EPIC wallet
+ case Consensus.EPIC_WALLET_TYPE:
+
+ // Check if is mainnet
+ if(isMainnet === true) {
+
+ // Check if height is the first block era
+ if(height.isLessThanOrEqualTo(Consensus.FIRST_BLOCK_ERA_HEIGHT) === true) {
+
+ // Return reward during the first block era
+ return new BigNumber("1600000000");
+ }
+
+ // Otherwise check if height is the second block era
+ else if(height.isLessThanOrEqualTo(Consensus.SECOND_BLOCK_ERA_HEIGHT) === true) {
+
+ // Return reward during the second block era
+ return new BigNumber("800000000");
+ }
+
+ // Otherwise check if height is the third block era
+ else if(height.isLessThanOrEqualTo(Consensus.THIRD_BLOCK_ERA_HEIGHT) === true) {
+
+ // Return reward during the third block era
+ return new BigNumber("400000000");
+ }
+
+ // Otherwise check if height is the fourth block era
+ else if(height.isLessThanOrEqualTo(Consensus.FOURTH_BLOCK_ERA_HEIGHT) === true) {
+
+ // Return reward during the fourth block era
+ return new BigNumber("200000000");
+ }
+
+ // Otherwise check if height is the fifth block era
+ else if(height.isLessThanOrEqualTo(Consensus.FIFTH_BLOCK_ERA_HEIGHT) === true) {
+
+ // Return reward during the fifth block era
+ return new BigNumber("100000000");
+ }
+
+ // Otherwise
+ else {
+
+ // Get height after fifth era
+ var heightAfterFifthEra = height.minus(Consensus.FIFTH_BLOCK_ERA_HEIGHT + 1);
+
+ // Return reward during current era
+ return (new BigNumber("15625000")).dividedToIntegerBy((new BigNumber(2)).exponentiatedBy(heightAfterFifthEra.dividedToIntegerBy(Consensus.SIXTH_AND_UP_BLOCK_ERA_INTERVAL)));
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Check if height is the first block era
+ if(height.isLessThanOrEqualTo(Consensus.FIRST_BLOCK_ERA_HEIGHT) === true) {
+
+ // Return reward during the first block era
+ return new BigNumber("1600000000");
+ }
+
+ // Otherwise
+ else {
+
+ // Return reward during the second block era
+ return new BigNumber("800000000");
+ }
+ }
+
+ // Break
+ break;
+ }
+ }
+
+ // Get epoch duration
+ static getEpochDuration(isMainnet, epoch) {
+
+ // Check wallet type
+ switch(Consensus.getWalletType()) {
+
+ // MWC wallet
+ case Consensus.MWC_WALLET_TYPE:
+
+ // Check epoch
+ switch(epoch) {
+
+ // One
+ case 1:
+
+ // Return duration of first epoch
+ return 0;
+
+ // Two
+ case 2:
+
+ // Return duration of second epoch
+ return (isMainnet === true) ? 120 * Consensus.BLOCK_HEIGHT_DAY : Consensus.BLOCK_HEIGHT_DAY;
+
+ // Three
+ case 3:
+
+ // Return duration of third epoch
+ return (isMainnet === true) ? 60 * Consensus.BLOCK_HEIGHT_DAY : Consensus.BLOCK_HEIGHT_DAY;
+
+ // Four
+ case 4:
+
+ // Return duration of fourth epoch
+ return 120 * Consensus.BLOCK_HEIGHT_DAY;
+
+ // Five
+ case 5:
+
+ // Return duration of fifth epoch
+ return 180 * Consensus.BLOCK_HEIGHT_DAY;
+
+ // Six
+ case 6:
+
+ // Return duration of sixth epoch
+ return 180 * Consensus.BLOCK_HEIGHT_DAY;
+
+ // Seven
+ case 7:
+
+ // Return duration of seventh epoch
+ return Consensus.BLOCK_HEIGHT_YEAR;
+
+ // Eight
+ case 8:
+
+ // Return duration of eighth epoch
+ return Consensus.BLOCK_HEIGHT_YEAR;
+
+ // Nine
+ case 9:
+
+ // Return duration of ninth epoch
+ return 6 * Consensus.BLOCK_HEIGHT_YEAR;
+
+ // Tenth
+ case 10:
+
+ // Return duration of tenth epoch
+ return 10 * Consensus.BLOCK_HEIGHT_YEAR;
+
+ // Eleven
+ case 11:
+
+ // Return duration of eleventh epoch
+ return 876349148;
+ }
+ }
+ }
+
+ // Get epoch block offset
+ static getEpochBlockOffset(isMainnet, epoch) {
+
+ // Check wallet type
+ switch(Consensus.getWalletType()) {
+
+ // MWC wallet
+ case Consensus.MWC_WALLET_TYPE:
+
+ // Set offset to the C31 hard fork height
+ var offset = Consensus.getC31HardForkHeight(isMainnet);
+
+ // Check if past second epoch range
+ if(epoch >= 2)
+
+ // Add initial block duration to offset
+ offset = offset.plus((isMainnet === true) ? Consensus.BLOCK_HEIGHT_WEEK : Consensus.BLOCK_HEIGHT_DAY);
+
+ // Go through all remaining epoch ranges
+ for(var i = 3; i <= epoch; ++i)
+
+ // Add epoch's block duration to offset
+ offset = offset.plus(Consensus.getEpochDuration(isMainnet, i - 1));
+
+ // Return offset
+ return offset;
+ }
+ }
+
+ // Get C31 hard fork height
+ static getC31HardForkHeight(isMainnet) {
+
+ // Check wallet type
+ switch(Consensus.getWalletType()) {
+
+ // MWC wallet
+ case Consensus.MWC_WALLET_TYPE:
+
+ // Return C31 hard fork height
+ return (isMainnet === true) ? new BigNumber(202500) : new BigNumber(270000);
+ }
+ }
+
+ // Get header version
+ static getHeaderVersion(isMainnet, height) {
+
+ // Check wallet type
+ switch(Consensus.getWalletType()) {
+
+ // MWC wallet
+ case Consensus.MWC_WALLET_TYPE:
+
+ // Check if height is less than the C31 hard fork height
+ if(height.isLessThan(Consensus.getC31HardForkHeight(isMainnet)) === true)
+
+ // Return legacy header version
+ return Consensus.LEGACY_HEADER_VERSION;
+
+ // Otherwise
+ else
+
+ // Return second header version
+ return Consensus.LEGACY_HEADER_VERSION.plus(1);
+
+ // Break
+ break;
+
+ // GRIN wallet
+ case Consensus.GRIN_WALLET_TYPE:
+
+ // Check if mainnet
+ if(isMainnet === true) {
+
+ // Get header version based on number of hard forks
+ var headerVersion = height.dividedToIntegerBy(Consensus.HARD_FORK_INTERVAL).plus(1);
+
+ // Check if header version is greater than the maximum header version
+ if(headerVersion.isGreaterThan(Consensus.MAXIMUM_HEADER_VERSION) === true)
+
+ // Set header version to the maximum header version
+ headerVersion = new BigNumber(Consensus.MAXIMUM_HEADER_VERSION);
+
+ // Return header version
+ return headerVersion;
+ }
+
+ // Otherwise
+ else {
+
+ // Check is height is less than first hard fork height
+ if(height.isLessThan(Consensus.FIRST_HARD_FORK_HEIGHT) === true)
+
+ // Return legacy header version
+ return Consensus.LEGACY_HEADER_VERSION;
+
+ // Otherwise check if height is less than second hard fork height
+ else if(height.isLessThan(Consensus.SECOND_HARD_FORK_HEIGHT) === true)
+
+ // Return second header version
+ return Consensus.LEGACY_HEADER_VERSION.plus(1);
+
+ // Otherwise check if height is less than third hard fork height
+ else if(height.isLessThan(Consensus.THIRD_HARD_FORK_HEIGHT) === true)
+
+ // Return third header version
+ return Consensus.LEGACY_HEADER_VERSION.plus(2);
+
+ // Otherwise check if height is less than fourth hard forks height
+ else if(height.isLessThan(Consensus.FOURTH_HARD_FORK_HEIGHT) === true)
+
+ // Return fourth header version
+ return Consensus.LEGACY_HEADER_VERSION.plus(3);
+
+ // Otherwise
+ else
+
+ // Return fifth header version
+ return Consensus.LEGACY_HEADER_VERSION.plus(4);
+ }
+
+ // Break
+ break;
+
+ // EPIC wallet
+ case Consensus.EPIC_WALLET_TYPE:
+
+ // Check if height is less than the first hard fork height
+ if(height.isLessThan(Consensus.getFirstHardForkHeight(isMainnet)) === true)
+
+ // Return legacy header version
+ return Consensus.LEGACY_HEADER_VERSION;
+
+ // Otherwise
+ else
+
+ // Return second header version
+ return Consensus.LEGACY_HEADER_VERSION.plus(1);
+
+ // Break
+ break;
+ }
+ }
+
+ // Get first hard fork height
+ static getFirstHardForkHeight(isMainnet) {
+
+ // Check wallet type
+ switch(Consensus.getWalletType()) {
+
+ // EPIC wallet
+ case Consensus.EPIC_WALLET_TYPE:
+
+ // Return first hard fork height
+ return (isMainnet === true) ? new BigNumber(9000000) : new BigNumber(25800);
+ }
+ }
+
+ // Hard fork interval
+ static get HARD_FORK_INTERVAL() {
+
+ // Check wallet type
+ switch(Consensus.getWalletType()) {
+
+ // GRIN wallet
+ case Consensus.GRIN_WALLET_TYPE:
+
+ // Return hard fork interval
+ return Math.floor(Consensus.BLOCK_HEIGHT_YEAR / 2);
+ }
+ }
+
+ // First hard fork height
+ static get FIRST_HARD_FORK_HEIGHT() {
+
+ // Check wallet type
+ switch(Consensus.getWalletType()) {
+
+ // GRIN wallet
+ case Consensus.GRIN_WALLET_TYPE:
+
+ // Return first hard fork height
+ return 185040;
+ }
+ }
+
+ // Second hard fork height
+ static get SECOND_HARD_FORK_HEIGHT() {
+
+ // Check wallet type
+ switch(Consensus.getWalletType()) {
+
+ // GRIN wallet
+ case Consensus.GRIN_WALLET_TYPE:
+
+ // Return second hard fork height
+ return 298080;
+ }
+ }
+
+ // Third hard fork height
+ static get THIRD_HARD_FORK_HEIGHT() {
+
+ // Check wallet type
+ switch(Consensus.getWalletType()) {
+
+ // GRIN wallet
+ case Consensus.GRIN_WALLET_TYPE:
+
+ // Return third hard fork height
+ return 552960;
+ }
+ }
+
+ // Fourth hard fork height
+ static get FOURTH_HARD_FORK_HEIGHT() {
+
+ // Check wallet type
+ switch(Consensus.getWalletType()) {
+
+ // GRIN wallet
+ case Consensus.GRIN_WALLET_TYPE:
+
+ // Return fourth hard fork height
+ return 642240;
+ }
+ }
+
+ // Maximum header version
+ static get MAXIMUM_HEADER_VERSION() {
+
+ // Check wallet type
+ switch(Consensus.getWalletType()) {
+
+ // GRIN wallet
+ case Consensus.GRIN_WALLET_TYPE:
+
+ // Return maximum header version
+ return 5;
+ }
+ }
+
+ // Default Wallet type
+ static get DEFAULT_WALLET_TYPE() {
+
+ // Return default wallet type
+ return Consensus.MWC_WALLET_TYPE;
+ }
+
+ // Default network type
+ static get DEFAULT_NETWORK_TYPE() {
+
+ // Return default network type
+ return Consensus.MAINNET_NETWORK_TYPE;
+ }
+
+ // Wallet MWC text value
+ static get WALLET_MWC_TEXT_VALUE() {
+
+ // Return wallet MWC text value
+ return (typeof Language !== "undefined") ? Language.getDefaultTranslation('MimbleWimble Coin') : "MimbleWimble Coin";
+ }
+
+ // Wallet GRIN value
+ static get WALLET_GRIN_TEXT_VALUE() {
+
+ // Return wallet GRIN text value
+ return (typeof Language !== "undefined") ? Language.getDefaultTranslation('Grin') : "Grin";
+ }
+
+ // Wallet EPIC value
+ static get WALLET_EPIC_TEXT_VALUE() {
+
+ // Return wallet EPIC text value
+ return (typeof Language !== "undefined") ? Language.getDefaultTranslation('Epic Cash') : "Epic Cash";
+ }
+
+ // Network mainnet value
+ static get NETWORK_MAINNET_TEXT_VALUE() {
+
+ // Return network mainnet text value
+ return (typeof Language !== "undefined") ? Language.getDefaultTranslation('Mainnet') : "Mainnet";
+ }
+
+ // Network floonet value
+ static get NETWORK_FLOONET_TEXT_VALUE() {
+
+ // Return network floonet text value
+ return (typeof Language !== "undefined") ? Language.getDefaultTranslation('Floonet') : "Floonet";
+ }
+
+ // Network testnet value
+ static get NETWORK_TESTNET_TEXT_VALUE() {
+
+ // Return network testnet text value
+ return (typeof Language !== "undefined") ? Language.getDefaultTranslation('Testnet') : "Testnet";
+ }
+
+ // Override wallet type URL parameter name
+ static get OVERRIDE_WALLET_TYPE_URL_PARAMETER_NAME() {
+
+ // Return override wallet type URL parameter name
+ return "Override Wallet Type";
+ }
+
+ // Override network type URL parameter name
+ static get OVERRIDE_NETWORK_TYPE_URL_PARAMETER_NAME() {
+
+ // Return override network type URL parameter name
+ return "Override Network Type";
+ }
+
+ // First block era height
+ static get FIRST_BLOCK_ERA_HEIGHT() {
+
+ // Check wallet type
+ switch(Consensus.getWalletType()) {
+
+ // EPIC wallet
+ case Consensus.EPIC_WALLET_TYPE:
+
+ // Return first block era height
+ return 334 * Consensus.BLOCK_HEIGHT_DAY;
+ }
+ }
+
+ // Second block era height
+ static get SECOND_BLOCK_ERA_HEIGHT() {
+
+ // Check wallet type
+ switch(Consensus.getWalletType()) {
+
+ // EPIC wallet
+ case Consensus.EPIC_WALLET_TYPE:
+
+ // Return second block era height
+ return 470 * Consensus.BLOCK_HEIGHT_DAY + Consensus.FIRST_BLOCK_ERA_HEIGHT;
+ }
+ }
+
+ // Third block era height
+ static get THIRD_BLOCK_ERA_HEIGHT() {
+
+ // Check wallet type
+ switch(Consensus.getWalletType()) {
+
+ // EPIC wallet
+ case Consensus.EPIC_WALLET_TYPE:
+
+ // Return third block era height
+ return 601 * Consensus.BLOCK_HEIGHT_DAY + Consensus.SECOND_BLOCK_ERA_HEIGHT;
+ }
+ }
+
+ // Fourth block era height
+ static get FOURTH_BLOCK_ERA_HEIGHT() {
+
+ // Check wallet type
+ switch(Consensus.getWalletType()) {
+
+ // EPIC wallet
+ case Consensus.EPIC_WALLET_TYPE:
+
+ // Return fourth block era height
+ return 800 * Consensus.BLOCK_HEIGHT_DAY + Consensus.THIRD_BLOCK_ERA_HEIGHT;
+ }
+ }
+
+ // Fifth block era height
+ static get FIFTH_BLOCK_ERA_HEIGHT() {
+
+ // Check wallet type
+ switch(Consensus.getWalletType()) {
+
+ // EPIC wallet
+ case Consensus.EPIC_WALLET_TYPE:
+
+ // Return fifth block era height
+ return 1019 * Consensus.BLOCK_HEIGHT_DAY + Consensus.FOURTH_BLOCK_ERA_HEIGHT;
+ }
+ }
+
+ // Sixth and up block era interval
+ static get SIXTH_AND_UP_BLOCK_ERA_INTERVAL() {
+
+ // Check wallet type
+ switch(Consensus.getWalletType()) {
+
+ // EPIC wallet
+ case Consensus.EPIC_WALLET_TYPE:
+
+ // Return sixth and up block era interval
+ return 1460 * Consensus.BLOCK_HEIGHT_DAY;
+ }
+ }
+
+ // Wallet type local storage name
+ static get WALLET_TYPE_LOCAL_STORAGE_NAME() {
+
+ // Return wallet type local storage name
+ return "Wallet Type";
+ }
+
+ // Network type local storage name
+ static get NETWORK_TYPE_LOCAL_STORAGE_NAME() {
+
+ // Return network type local storage name
+ return "Network Type";
+ }
+}
+
+
+// Main function
+
+// Set global object's consensus
+globalThis["Consensus"] = Consensus;
+
+// Check if not not a web worker
+if(typeof self === "undefined" || typeof WorkerGlobalScope === "undefined" || self instanceof WorkerGlobalScope === false) {
+
+ // Initialize consensus
+ Consensus.initialize();
+}
diff --git a/scripts/cookie_acceptance.js b/scripts/cookie_acceptance.js
new file mode 100755
index 0000000..6267bc4
--- /dev/null
+++ b/scripts/cookie_acceptance.js
@@ -0,0 +1,180 @@
+// Use strict
+"use strict";
+
+
+// Classes
+
+// Cookie acceptance class
+class CookieAcceptance {
+
+ // Public
+
+ // Constructor
+ constructor() {
+
+ // Get cookie acceptance display
+ this.cookieAcceptanceDisplay = $("section.cookieAcceptance");
+
+ // Get acknowledge button
+ this.acknowledgeButton = this.cookieAcceptanceDisplay.find("button");
+
+ // Set can show
+ this.canShow = false;
+
+ // Set self
+ var self = this;
+
+ // Cookie acceptance display transaition end event
+ this.cookieAcceptanceDisplay.on("transitionend", function() {
+
+ // Check if cookie acceptance display is hiding
+ if(self.cookieAcceptanceDisplay.hasClass("hide") === true) {
+
+ // Prevent focus on cookie acceptance display's elements
+ self.cookieAcceptanceDisplay.addClass("noFocus");
+
+ // Trigger is hidden event
+ $(self).trigger(CookieAcceptance.IS_HIDDEN_EVENT);
+ }
+ });
+
+ // Window storage event
+ $(window).on("storage", function(event) {
+
+ // Check if cookie acceptance message acknowledged was changed
+ if(event["originalEvent"]["key"] === CookieAcceptance.COOKIE_ACCEPTANCE_MESSAGE_ACKNOWLEDGED_LOCAL_STORAGE_NAME) {
+
+ // Hide
+ self.hide();
+
+ // Clear can show
+ self.canShow = false;
+ }
+ });
+
+ // Acknowledged button click event
+ this.acknowledgeButton.on("click", function() {
+
+ // Hide
+ self.hide();
+
+ // Clear can show
+ self.canShow = false;
+
+ // Try
+ try {
+
+ // Save cookie acceptance message acknowledged
+ localStorage.setItem(CookieAcceptance.COOKIE_ACCEPTANCE_MESSAGE_ACKNOWLEDGED_LOCAL_STORAGE_NAME, CookieAcceptance.COOKIE_ACCEPTANCE_MESSAGE_ACKNOWLEDGED_TRUE_VALUE);
+ }
+
+ // Catch errors
+ catch(error) {
+
+ // Trigger a fatal error
+ new FatalError(FatalError.LOCAL_STORAGE_ERROR);
+ }
+ });
+ }
+
+ // Show
+ show(canShow = true) {
+
+ // Check if can show
+ if(canShow === true || this.canShow === true) {
+
+ // Set can show
+ this.canShow = true;
+
+ // Check if not an app or extension and not loading from file
+ if(Common.isApp() === false && Common.isExtension() === false && location["protocol"] !== Common.FILE_PROTOCOL) {
+
+ // Get cookie acceptance message acknowledged
+ var cookieAcceptanceMessageAcknowledged = localStorage.getItem(CookieAcceptance.COOKIE_ACCEPTANCE_MESSAGE_ACKNOWLEDGED_LOCAL_STORAGE_NAME);
+
+ // Check if cookie acceptance message hasn't been acknowledged
+ if(cookieAcceptanceMessageAcknowledged === Common.INVALID_LOCAL_STORAGE_ITEM || cookieAcceptanceMessageAcknowledged !== CookieAcceptance.COOKIE_ACCEPTANCE_MESSAGE_ACKNOWLEDGED_TRUE_VALUE) {
+
+ // Show cookie acceptance display and make it so that its elements can be focused
+ this.cookieAcceptanceDisplay.removeClass("hide noFocus");
+
+ // Set self
+ var self = this;
+
+ // Windows resize cookie acceptance event
+ $(window).on("resize.cookieAcceptance", function() {
+
+ // Check if is an app
+ if(Common.isApp() === true) {
+
+ // Turn off window resize cookie acceptance event
+ $(window).off("resize.cookieAcceptance");
+
+ // Hide
+ self.hide();
+
+ // Clear can show
+ self.canShow = false;
+ }
+ });
+
+ // Return true
+ return true;
+ }
+
+ // Otherwise
+ else {
+
+ // Trigger is hidden event
+ $(this).trigger(CookieAcceptance.IS_HIDDEN_EVENT);
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Trigger is hidden event
+ $(this).trigger(CookieAcceptance.IS_HIDDEN_EVENT);
+ }
+ }
+
+ // Return false
+ return false;
+ }
+
+ // Is hidden event
+ static get IS_HIDDEN_EVENT() {
+
+ // Return is hidden event
+ return "CookieAcceptanceIsHiddenEvent";
+ }
+
+ // Private
+
+ // Hide
+ hide() {
+
+ // Hide cookie acceptance display
+ this.cookieAcceptanceDisplay.addClass("hide");
+ }
+
+ // Cookie acceptance message acknowledged local storage name
+ static get COOKIE_ACCEPTANCE_MESSAGE_ACKNOWLEDGED_LOCAL_STORAGE_NAME() {
+
+ // Return cookie acceptance message acknowledged local storage name
+ return "Cookie Acceptance Message Acknowledged";
+ }
+
+ // Cookie acceptance message acknowledged true value
+ static get COOKIE_ACCEPTANCE_MESSAGE_ACKNOWLEDGED_TRUE_VALUE() {
+
+ // Return cookie acceptance message acknowledged true value
+ return "true";
+ }
+}
+
+
+// Main function
+
+// Set global object's cookie acceptance
+globalThis["CookieAcceptance"] = CookieAcceptance;
diff --git a/scripts/copyright.js b/scripts/copyright.js
new file mode 100755
index 0000000..bdeffbf
--- /dev/null
+++ b/scripts/copyright.js
@@ -0,0 +1,100 @@
+// Use strict
+"use strict";
+
+
+// Classes
+
+// Copyright class
+class Copyright {
+
+ // Public
+
+ // Initialize
+ static initialize() {
+
+ // Update
+ Copyright.update();
+ }
+
+ // Private
+
+ // Update
+ static update() {
+
+ // Get current timestamp
+ var currentTimestamp = new Date();
+
+ // Get current year
+ var currentYear = currentTimestamp.getFullYear();
+
+ // Check if the current year is greater than the copyright year
+ if(currentYear > COPYRIGHT_YEAR) {
+
+ // Get new date copyright
+ var newDateCopyright = $(Language.createTranslatableContainer("
", Language.getDefaultTranslation('%1$s–%2$s'), [COPYRIGHT_YEAR.toFixed(), currentYear.toFixed()]));
+
+ // Get new rights
+ var newRights = $(Language.createTranslatableContainer("
", Language.getDefaultTranslation('© %1$s–%2$s Nicolas Flamel.'), [COPYRIGHT_YEAR.toFixed(), currentYear.toFixed()]));
+ }
+
+ // Otherwise
+ else {
+
+ // Get new date copyright
+ var newDateCopyright = $(Language.createTranslatableContainer("
", "%1$s", [COPYRIGHT_YEAR.toFixed()]));
+
+ // Get new rights
+ var newRights = $(Language.createTranslatableContainer("
", Language.getDefaultTranslation('© %1$s Nicolas Flamel.'), [COPYRIGHT_YEAR.toFixed()]));
+ }
+
+ // Set new date copyright's name
+ newDateCopyright.attr("name", Copyright.DATE_COPYRIGHT_NAME);
+
+ // Set new rights's name
+ newRights.attr("name", Copyright.RIGHTS_NAME);
+
+ // Replace date copyright with the new date copyright
+ $("meta[name=\"" + Copyright.DATE_COPYRIGHT_NAME + "\"]").replaceWith(newDateCopyright);
+
+ // Replace rights with the new rights
+ $("meta[name=\"" + Copyright.RIGHTS_NAME + "\"]").replaceWith(newRights);
+
+ // Get next year timestamp
+ var nextYearTimestamp = new Date(currentYear + 1, Common.JANUARY_MONTH_INDEX);
+
+ // Set timeout
+ setTimeout(function() {
+
+ // Update
+ Copyright.update();
+
+ }, Math.min(nextYearTimestamp - currentTimestamp, Common.INT32_MAX_VALUE));
+ }
+
+ // Date copyright name
+ static get DATE_COPYRIGHT_NAME() {
+
+ // Return date copyright name
+ return "dcterms.dateCopyrighted";
+ }
+
+ // Rights name
+ static get RIGHTS_NAME() {
+
+ // Return rights name
+ return "dcterms.rights";
+ }
+}
+
+
+// Main function
+
+// Set global object's copyright
+globalThis["Copyright"] = Copyright;
+
+// Ready event
+$(function() {
+
+ // Initialize copyright
+ Copyright.initialize();
+});
diff --git a/scripts/crc32 license.txt b/scripts/crc32 license.txt
new file mode 100755
index 0000000..9fd9d56
--- /dev/null
+++ b/scripts/crc32 license.txt
@@ -0,0 +1,14 @@
+Copyright (C) 2014-present SheetJS
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
diff --git a/scripts/crc32-1.2.0.js b/scripts/crc32-1.2.0.js
new file mode 100755
index 0000000..6bcecef
--- /dev/null
+++ b/scripts/crc32-1.2.0.js
@@ -0,0 +1,118 @@
+/* crc32.js (C) 2014-present SheetJS -- http://sheetjs.com */
+/* vim: set ts=2: */
+/*exported CRC32 */
+var CRC32;
+(function (factory) {
+ /*jshint ignore:start */
+ /*eslint-disable */
+ if(typeof DO_NOT_EXPORT_CRC === 'undefined') {
+ if('object' === typeof exports) {
+ factory(exports);
+ } else if ('function' === typeof define && define.amd) {
+ define(function () {
+ var module = {};
+ factory(module);
+ return module;
+ });
+ } else {
+ factory(CRC32 = {});
+ }
+ } else {
+ factory(CRC32 = {});
+ }
+ /*eslint-enable */
+ /*jshint ignore:end */
+}(function(CRC32) {
+CRC32.version = '1.2.0';
+/* see perf/crc32table.js */
+/*global Int32Array */
+function signed_crc_table() {
+ var c = 0, table = new Array(256);
+
+ for(var n =0; n != 256; ++n){
+ c = n;
+ c = ((c&1) ? (-306674912 ^ (c >>> 1)) : (c >>> 1));
+ c = ((c&1) ? (-306674912 ^ (c >>> 1)) : (c >>> 1));
+ c = ((c&1) ? (-306674912 ^ (c >>> 1)) : (c >>> 1));
+ c = ((c&1) ? (-306674912 ^ (c >>> 1)) : (c >>> 1));
+ c = ((c&1) ? (-306674912 ^ (c >>> 1)) : (c >>> 1));
+ c = ((c&1) ? (-306674912 ^ (c >>> 1)) : (c >>> 1));
+ c = ((c&1) ? (-306674912 ^ (c >>> 1)) : (c >>> 1));
+ c = ((c&1) ? (-306674912 ^ (c >>> 1)) : (c >>> 1));
+ table[n] = c;
+ }
+
+ return typeof Int32Array !== 'undefined' ? new Int32Array(table) : table;
+}
+
+var T = signed_crc_table();
+function crc32_bstr(bstr, seed) {
+ var C = seed ^ -1, L = bstr.length - 1;
+ for(var i = 0; i < L;) {
+ C = (C>>>8) ^ T[(C^bstr.charCodeAt(i++))&0xFF];
+ C = (C>>>8) ^ T[(C^bstr.charCodeAt(i++))&0xFF];
+ }
+ if(i === L) C = (C>>>8) ^ T[(C ^ bstr.charCodeAt(i))&0xFF];
+ return C ^ -1;
+}
+
+function crc32_buf(buf, seed) {
+ if(buf.length > 10000) return crc32_buf_8(buf, seed);
+ var C = seed ^ -1, L = buf.length - 3;
+ for(var i = 0; i < L;) {
+ C = (C>>>8) ^ T[(C^buf[i++])&0xFF];
+ C = (C>>>8) ^ T[(C^buf[i++])&0xFF];
+ C = (C>>>8) ^ T[(C^buf[i++])&0xFF];
+ C = (C>>>8) ^ T[(C^buf[i++])&0xFF];
+ }
+ while(i < L+3) C = (C>>>8) ^ T[(C^buf[i++])&0xFF];
+ return C ^ -1;
+}
+
+function crc32_buf_8(buf, seed) {
+ var C = seed ^ -1, L = buf.length - 7;
+ for(var i = 0; i < L;) {
+ C = (C>>>8) ^ T[(C^buf[i++])&0xFF];
+ C = (C>>>8) ^ T[(C^buf[i++])&0xFF];
+ C = (C>>>8) ^ T[(C^buf[i++])&0xFF];
+ C = (C>>>8) ^ T[(C^buf[i++])&0xFF];
+ C = (C>>>8) ^ T[(C^buf[i++])&0xFF];
+ C = (C>>>8) ^ T[(C^buf[i++])&0xFF];
+ C = (C>>>8) ^ T[(C^buf[i++])&0xFF];
+ C = (C>>>8) ^ T[(C^buf[i++])&0xFF];
+ }
+ while(i < L+7) C = (C>>>8) ^ T[(C^buf[i++])&0xFF];
+ return C ^ -1;
+}
+
+function crc32_str(str, seed) {
+ var C = seed ^ -1;
+ for(var i = 0, L=str.length, c, d; i < L;) {
+ c = str.charCodeAt(i++);
+ if(c < 0x80) {
+ C = (C>>>8) ^ T[(C ^ c)&0xFF];
+ } else if(c < 0x800) {
+ C = (C>>>8) ^ T[(C ^ (192|((c>>6)&31)))&0xFF];
+ C = (C>>>8) ^ T[(C ^ (128|(c&63)))&0xFF];
+ } else if(c >= 0xD800 && c < 0xE000) {
+ c = (c&1023)+64; d = str.charCodeAt(i++)&1023;
+ C = (C>>>8) ^ T[(C ^ (240|((c>>8)&7)))&0xFF];
+ C = (C>>>8) ^ T[(C ^ (128|((c>>2)&63)))&0xFF];
+ C = (C>>>8) ^ T[(C ^ (128|((d>>6)&15)|((c&3)<<4)))&0xFF];
+ C = (C>>>8) ^ T[(C ^ (128|(d&63)))&0xFF];
+ } else {
+ C = (C>>>8) ^ T[(C ^ (224|((c>>12)&15)))&0xFF];
+ C = (C>>>8) ^ T[(C ^ (128|((c>>6)&63)))&0xFF];
+ C = (C>>>8) ^ T[(C ^ (128|(c&63)))&0xFF];
+ }
+ }
+ return C ^ -1;
+}
+CRC32.table = T;
+// $FlowIgnore
+CRC32.bstr = crc32_bstr;
+// $FlowIgnore
+CRC32.buf = crc32_buf;
+// $FlowIgnore
+CRC32.str = crc32_str;
+}));
diff --git a/scripts/crypto.js b/scripts/crypto.js
new file mode 100755
index 0000000..647b002
--- /dev/null
+++ b/scripts/crypto.js
@@ -0,0 +1,1194 @@
+// Use strict
+"use strict";
+
+
+// Classes
+
+// Crypto class
+class Crypto {
+
+ // Public
+
+ // Derive child key
+ static deriveChildKey(extendedPrivateKey, paths, useBip39 = false) {
+
+ // Return promise
+ return new Promise(function(resolve, reject) {
+
+ // Get secret key and chain code from extended private key
+ var secretKey = extendedPrivateKey.subarray(0, Crypto.SECP256K1_SECRET_KEY_LENGTH);
+ var chainCode = extendedPrivateKey.subarray(Crypto.CHAIN_CODE_LENGTH);
+
+ // Set update values
+ var updateValues = new Promise(function(resolve, reject) {
+
+ // Resolve secret key and chain code
+ resolve([
+
+ // Copy secret key
+ new Uint8Array(secretKey),
+
+ // Copy chain code
+ new Uint8Array(chainCode)
+ ]);
+ });
+
+ // Initialize updating values
+ var updatingValues = [updateValues];
+
+ // Go through all of the paths
+ for(let i = 0; i < paths["length"]; ++i) {
+
+ // Set update to run after the previous update
+ updateValues = updateValues.then(function(values) {
+
+ // Return promise
+ return new Promise(function(resolve, reject) {
+
+ // Get updated secret key and chain code
+ var updatedSecretKey = values[Crypto.SECRET_KEY_INDEX];
+ var updatedChainCode = values[Crypto.CHAIN_CODE_INDEX];
+
+ // Return deriving the next secret key and chain code using the path
+ return Crypto.deriveSecretKeyAndChainCode(updatedSecretKey, updatedChainCode, paths[i], useBip39).then(function(updatedValues) {
+
+ // Securely clear updated secret key and chain code
+ updatedSecretKey.fill(0);
+ updatedChainCode.fill(0);
+
+ // Resolve updated values
+ resolve(updatedValues);
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Securely clear secret key and chain code
+ updatedSecretKey.fill(0);
+ updatedChainCode.fill(0);
+
+ // Reject error
+ reject(error);
+ });
+ });
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Return Promise
+ return new Promise(function(resolve, reject) {
+
+ // Reject error
+ reject(error);
+ });
+ });
+
+ // Append updating value to list
+ updatingValues.push(updateValues);
+ }
+
+ // Return updating all values
+ return Promise.all(updatingValues).then(function(values) {
+
+ // Get updated secret key and chain code
+ var updatedSecretKey = values[values["length"] - 1][Crypto.SECRET_KEY_INDEX];
+ var updatedChainCode = values[values["length"] - 1][Crypto.CHAIN_CODE_INDEX];
+
+ // Initialize new extended private key
+ var newExtendedPrivateKey = Common.mergeArrays([
+
+ // Updated secret key
+ updatedSecretKey,
+
+ // Chain code
+ updatedChainCode
+ ]);
+
+ // Securely clear updated secret key and chain code
+ updatedSecretKey.fill(0);
+ updatedChainCode.fill(0);
+
+ // Check if new extended private key's secret key is a valid secret key
+ if(Secp256k1Zkp.isValidSecretKey(newExtendedPrivateKey.subarray(0, Crypto.SECP256K1_SECRET_KEY_LENGTH)) === true) {
+
+ // Resolve new extended private key
+ resolve(newExtendedPrivateKey);
+ }
+
+ // Otherwise
+ else {
+
+ // Securely clear new extended private key
+ newExtendedPrivateKey.fill(0);
+
+ // Reject error
+ reject("Invalid extended private key.");
+ }
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Reject error
+ reject(error);
+ });
+ });
+ }
+
+ // Commit
+ static commit(extendedPrivateKey, amount, identifier, switchType) {
+
+ // Return promise
+ return new Promise(function(resolve, reject) {
+
+ // Return deriving secret key from extended private key, amount, identifier, and switch type
+ return Crypto.deriveSecretKey(extendedPrivateKey, amount, identifier, switchType).then(function(secretKey) {
+
+ // Check if performing Pedersen commit with the secret key and amount was successful
+ var commit = Secp256k1Zkp.pedersenCommit(secretKey, amount.toFixed());
+
+ if(commit !== Secp256k1Zkp.OPERATION_FAILED) {
+
+ // Resolve commit
+ resolve(commit);
+ }
+
+ // Otherwise
+ else {
+
+ // Securely clear secret key
+ secretKey.fill(0);
+
+ // Reject error
+ reject("Performing Pedersen commit failed.");
+ }
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Reject error
+ reject(error);
+ });
+ });
+ }
+
+ // Commit amount
+ static commitAmount(amount) {
+
+ // Check if performing Pedersen commit with zero blinding factor and amount was successful
+ var commit = Secp256k1Zkp.pedersenCommit(Crypto.ZERO_BLINDING_FACTOR, amount.toFixed());
+
+ if(commit !== Secp256k1Zkp.OPERATION_FAILED) {
+
+ // Return commit
+ return commit;
+ }
+
+ // Otherwise
+ else {
+
+ // Throw error
+ throw "Performing Pedersen commit failed.";
+ }
+ }
+
+ // Proof
+ static proof(extendedPrivateKey, amount, identifier, switchType, proofBuilder) {
+
+ // Return promise
+ return new Promise(function(resolve, reject) {
+
+ // Return getting commit from extended private key, amount, identifier, and switch type
+ return Crypto.commit(extendedPrivateKey, amount, identifier, switchType).then(function(commit) {
+
+ // Return deriving secret key from extended private key, amount, identifier, and switch type
+ return Crypto.deriveSecretKey(extendedPrivateKey, amount, identifier, switchType).then(function(secretKey) {
+
+ // Return getting rewind nonce from the commit
+ return proofBuilder.rewindNonce(commit).then(function(rewindNonce) {
+
+ // Return getting private nonce from the commit
+ return proofBuilder.privateNonce(commit).then(function(privateNonce) {
+
+ // Try
+ try {
+
+ // Get proof message from identifier and switch type
+ var message = proofBuilder.proofMessage(identifier, switchType);
+ }
+
+ // Catch errors
+ catch(error) {
+
+ // Securely clear secret key
+ secretKey.fill(0);
+
+ // Securely clear rewind nonce
+ rewindNonce.fill(0);
+
+ // Securely clear private nonce
+ privateNonce.fill(0);
+
+ // Reject error
+ reject("Getting proof message failed.");
+
+ // Return
+ return;
+ }
+
+ // Check if creating bulletproof with the secret key, amount, rewind nonce, private nonce, and message was successful
+ var bulletproof = Secp256k1Zkp.createBulletproof(secretKey, amount.toFixed(), rewindNonce, privateNonce, new Uint8Array([]), message);
+
+ if(bulletproof !== Secp256k1Zkp.OPERATION_FAILED) {
+
+ // Securely clear secret key
+ secretKey.fill(0);
+
+ // Securely clear rewind nonce
+ rewindNonce.fill(0);
+
+ // Securely clear private nonce
+ privateNonce.fill(0);
+
+ // Resolve bulletproof
+ resolve(bulletproof);
+ }
+
+ // Otherwise
+ else {
+
+ // Securely clear secret key
+ secretKey.fill(0);
+
+ // Securely clear rewind nonce
+ rewindNonce.fill(0);
+
+ // Securely clear private nonce
+ privateNonce.fill(0);
+
+ // Reject error
+ reject("Getting bulletproof failed.");
+ }
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Securely clear secret key
+ secretKey.fill(0);
+
+ // Securely clear rewind nonce
+ rewindNonce.fill(0);
+
+ // Reject error
+ reject(error);
+ });
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Securely clear secret key
+ secretKey.fill(0);
+
+ // Reject error
+ reject(error);
+ });
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Reject error
+ reject(error);
+ });
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Reject error
+ reject(error);
+ });
+ });
+ }
+
+ // Derive secret key
+ static deriveSecretKey(extendedPrivateKey, amount, identifier, switchType) {
+
+ // Return promise
+ return new Promise(function(resolve, reject) {
+
+ // Initialize paths
+ var paths = new Uint32Array(identifier.getDepth());
+
+ // Go through all the identifier's paths
+ for(var i = 0; i < identifier.getDepth(); ++i) {
+
+ // Set path to identifier's path
+ paths[i] = identifier.getPaths()[i];
+ }
+
+ // Return deriving the child key at the path from the extended private key
+ return Crypto.deriveChildKey(extendedPrivateKey, paths).then(function(childKey) {
+
+ // Get secret key from the child key
+ var secretKey = new Uint8Array(childKey.subarray(0, Crypto.SECP256K1_SECRET_KEY_LENGTH));
+
+ // Securely clear child key
+ childKey.fill(0);
+
+ // Check if switch type is none
+ if(switchType === Crypto.SWITCH_TYPE_NONE)
+
+ // Resolve secret key
+ resolve(secretKey);
+
+ // Otherwise check if switch type is regular
+ else if(switchType === Crypto.SWITCH_TYPE_REGULAR) {
+
+ // Check if getting blind switch of secret key and amount failed
+ var blindSwitch = Secp256k1Zkp.blindSwitch(secretKey, amount.toFixed());
+
+ if(blindSwitch === Secp256k1Zkp.OPERATION_FAILED) {
+
+ // Securely clear secret key
+ secretKey.fill(0);
+
+ // Reject error
+ reject("Performing blind switch failed.");
+
+ // Return
+ return;
+ }
+
+ // Securely clear secret key
+ secretKey.fill(0);
+
+ // Check if blind switch is a valid secret key
+ if(Secp256k1Zkp.isValidSecretKey(blindSwitch) === true) {
+
+ // Resolve blind switch
+ resolve(blindSwitch);
+ }
+
+ // Otherwise
+ else {
+
+ // Securely clear blind switch
+ blindSwitch.fill(0);
+
+ // Reject error
+ reject("Blind switch isn't a valid secret key.");
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Securely clear secret key
+ secretKey.fill(0);
+
+ // Reject error
+ reject("Invalid switch type.");
+ }
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Reject error
+ reject(error);
+ });
+ });
+ }
+
+ // Signature message
+ static signatureMessage(data) {
+
+ // Check if creating message was successful
+ var message = Blake2b.compute(Crypto.SINGLE_SIGNER_MESSAGE_LENGTH, data, new Uint8Array([]));
+
+ if(message !== Blake2b.OPERATION_FAILED) {
+
+ // Return message
+ return message;
+ }
+
+ // Otherwise
+ else {
+
+ // Throw error
+ throw "Creating signature message failed.";
+ }
+ }
+
+ // Root public key
+ static rootPublicKey(extendedPrivateKey) {
+
+ // Return promise
+ return new Promise(function(resolve, reject) {
+
+ // Get secret key from extended private key
+ var secretKey = extendedPrivateKey.subarray(0, Crypto.SECP256K1_SECRET_KEY_LENGTH);
+
+ // Check if getting public key from secret key was successful
+ var publicKey = Secp256k1Zkp.publicKeyFromSecretKey(secretKey);
+
+ if(publicKey !== Secp256k1Zkp.OPERATION_FAILED) {
+
+ // Resolve public key
+ resolve(publicKey);
+ }
+
+ // Otherwise
+ else {
+
+ // Reject error
+ reject("Getting public key failed.");
+ }
+ });
+ }
+
+ // Address key
+ static addressKey(extendedPrivateKey, index) {
+
+ // Return promise
+ return new Promise(function(resolve, reject) {
+
+ // Check wallet type
+ switch(Consensus.getWalletType()) {
+
+ // MWC wallet
+ case Consensus.MWC_WALLET_TYPE:
+
+ // Return deriving root key from extended private key, address key amount, root identifier, and regular switch type
+ return Crypto.deriveSecretKey(extendedPrivateKey, Crypto.ADDRESS_KEY_AMOUNT, new Identifier(Identifier.ROOT_SERIALIZED_IDENTIFIER), Crypto.SWITCH_TYPE_REGULAR).then(function(rootKey) {
+
+ // Return creating crypto key from address key seed
+ return crypto["subtle"].importKey("raw", (new TextEncoder()).encode(Crypto.ADDRESS_KEY_SEED), {
+
+ // Name
+ "name": Crypto.ADDRESS_KEY_ENCRYPTION_ALGORITHM,
+
+ // Hash
+ "hash": {
+
+ // Name
+ "name": Crypto.ADDRESS_KEY_DIGEST_ALGORITHM
+ }
+ }, false, [
+
+ // Sign
+ "sign"
+
+ ]).then(function(cryptoKey) {
+
+ // Return creating address extended private key from signing the root key
+ return crypto["subtle"].sign(Crypto.ADDRESS_KEY_ENCRYPTION_ALGORITHM, cryptoKey, rootKey).then(function(addressExtendedPrivateKey) {
+
+ // Securely clear root key
+ rootKey.fill(0);
+
+ // Get address extended private key in correct format
+ addressExtendedPrivateKey = new Uint8Array(addressExtendedPrivateKey);
+
+ // Get secret key and chain code from address extended private key
+ var secretKey = addressExtendedPrivateKey.subarray(0, Crypto.SECP256K1_SECRET_KEY_LENGTH);
+ var chainCode = addressExtendedPrivateKey.subarray(Crypto.CHAIN_CODE_LENGTH);
+
+ // Check if secret key is a valid secret key
+ if(Secp256k1Zkp.isValidSecretKey(secretKey) === true) {
+
+ // Return updating the next secret key and chain code using the index
+ return Crypto.deriveSecretKeyAndChainCode(secretKey, chainCode, index).then(function(value) {
+
+ // Securely clear address extended private key
+ addressExtendedPrivateKey.fill(0);
+
+ // Securely clear the chain code
+ value[Crypto.CHAIN_CODE_INDEX].fill(0);
+
+ // Resolve the secret key
+ resolve(value[Crypto.SECRET_KEY_INDEX]);
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Securely clear address extended private key
+ addressExtendedPrivateKey.fill(0);
+
+ // Reject error
+ reject(error);
+ });
+ }
+
+ // Otherwise
+ else {
+
+ // Securely clear address extended private key
+ addressExtendedPrivateKey.fill(0);
+
+ // Reject error
+ reject("Secret key is not a valid secret key.");
+ }
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Securely clear root key
+ rootKey.fill(0);
+
+ // Reject error
+ reject(error);
+ });
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Securely clear root key
+ rootKey.fill(0);
+
+ // Reject error
+ reject(error);
+ });
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Reject error
+ reject(error);
+ });
+
+ // GRIN or EPIC wallet
+ case Consensus.GRIN_WALLET_TYPE:
+ case Consensus.EPIC_WALLET_TYPE:
+
+ // Create child identifier
+ var childIdentifier = new Identifier().getChild();
+ childIdentifier.getPaths()[1] = 1;
+ childIdentifier.getPaths()[childIdentifier.getDepth() - 1] = index;
+
+ // Return deriving root key from extended private key, child identifier, and no switch type
+ return Crypto.deriveSecretKey(extendedPrivateKey, new BigNumber(0), childIdentifier, Crypto.SWITCH_TYPE_NONE).then(function(rootKey) {
+
+ // Check if getting secret key from root key was successful
+ var secretKey = Blake2b.compute(Crypto.SECP256K1_SECRET_KEY_LENGTH, rootKey, new Uint8Array([]));
+
+ if(secretKey !== Blake2b.OPERATION_FAILED) {
+
+ // Securely clear root key
+ rootKey.fill(0);
+
+ // Check if secret key is a valid secret key
+ if(Secp256k1Zkp.isValidSecretKey(secretKey) === true) {
+
+ // Resolve the secret key
+ resolve(secretKey);
+ }
+
+ // Otherwise
+ else {
+
+ // Securely clear secret key
+ secretKey.fill(0);
+
+ // Reject error
+ reject("Secret key is not a valid secret key.");
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Securely clear root key
+ rootKey.fill(0);
+
+ // Reject error
+ reject("Getting secret key from root key failed.");
+ }
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Reject error
+ reject(error);
+ });
+ }
+ });
+ }
+
+ // Login key
+ static loginKey(extendedPrivateKey) {
+
+ // Return promise
+ return new Promise(function(resolve, reject) {
+
+ // Create child identifier
+ var childIdentifier = new Identifier().getChild();
+ childIdentifier.getPaths()[1] = 2;
+
+ // Return deriving root key from extended private key, child identifier, and no switch type
+ return Crypto.deriveSecretKey(extendedPrivateKey, new BigNumber(0), childIdentifier, Crypto.SWITCH_TYPE_NONE).then(function(rootKey) {
+
+ // Check if getting secret key from root key was successful
+ var secretKey = Blake2b.compute(Crypto.SECP256K1_SECRET_KEY_LENGTH, rootKey, new Uint8Array([]));
+
+ if(secretKey !== Blake2b.OPERATION_FAILED) {
+
+ // Securely clear root key
+ rootKey.fill(0);
+
+ // Check if secret key is a valid secret key
+ if(Secp256k1Zkp.isValidSecretKey(secretKey) === true) {
+
+ // Resolve the secret key
+ resolve(secretKey);
+ }
+
+ // Otherwise
+ else {
+
+ // Securely clear secret key
+ secretKey.fill(0);
+
+ // Reject error
+ reject("Secret key is not a valid secret key.");
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Securely clear root key
+ rootKey.fill(0);
+
+ // Reject error
+ reject("Getting secret key from root key failed.");
+ }
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Reject error
+ reject(error);
+ });
+ });
+ }
+
+ // AES decrypt
+ static aesDecrypt(encryptedData, key) {
+
+ // Return promise
+ return new Promise(function(resolve, reject) {
+
+ // Return importing the key
+ return crypto["subtle"].importKey("raw", key, {
+
+ // Name
+ "name": Crypto.AES_ALGORITHM
+
+ }, false, [
+
+ // Decrypt
+ "decrypt"
+
+ ]).then(function(cryptoKey) {
+
+ // Return decrypting the encrypted data using the AES key
+ return crypto["subtle"].decrypt({
+
+ // Name
+ "name": Crypto.AES_ALGORITHM,
+
+ // Initialization vector
+ "iv": (new Uint8Array(Crypto.AES_INITIALIZATION_VECTOR_SIZE)).fill(0)
+
+ }, cryptoKey, encryptedData).then(function(decryptedData) {
+
+ // TODO Securely clear cryptoKey
+
+ // Resolve decrypted data
+ resolve(new Uint8Array(decryptedData));
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // TODO Securely clear cryptoKey
+
+ // Reject error
+ reject(error);
+ });
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Reject error
+ reject(error);
+ });
+ });
+ }
+
+ // Switch type none
+ static get SWITCH_TYPE_NONE() {
+
+ // Return switch type none
+ return 0;
+ }
+
+ // Switch type regular
+ static get SWITCH_TYPE_REGULAR() {
+
+ // Return switch type regular
+ return Crypto.SWITCH_TYPE_NONE + 1;
+ }
+
+ // Secp256k1 secret key length
+ static get SECP256K1_SECRET_KEY_LENGTH() {
+
+ // Return secp256k1 secret key length
+ return 32;
+ }
+
+ // Secp256k1 public key length
+ static get SECP256K1_PUBLIC_KEY_LENGTH() {
+
+ // Return secp256k1 public key length
+ return 33;
+ }
+
+ // Blinding factor length
+ static get BLINDING_FACTOR_LENGTH() {
+
+ // Return blinding factor length
+ return 32;
+ }
+
+ // Commit length
+ static get COMMIT_LENGTH() {
+
+ // Return commit length
+ return 33;
+ }
+
+ // Single-signer signature length
+ static get SINGLE_SIGNER_SIGNATURE_LENGTH() {
+
+ // Return single-signer signature length
+ return 64;
+ }
+
+ // Single-signer message length
+ static get SINGLE_SIGNER_MESSAGE_LENGTH() {
+
+ // Return single-signer message length
+ return 32;
+ }
+
+ // Proof length
+ static get PROOF_LENGTH() {
+
+ // Return proof length
+ return 675;
+ }
+
+ // Zero blinding factor
+ static get ZERO_BLINDING_FACTOR() {
+
+ // No zero blinding factor
+ return (new Uint8Array(Crypto.BLINDING_FACTOR_LENGTH)).fill(0);
+ }
+
+ // Zero secret key
+ static get ZERO_SECRET_KEY() {
+
+ // No zero blinding factor
+ return (new Uint8Array(Crypto.SECP256K1_SECRET_KEY_LENGTH)).fill(0);
+ }
+
+ // Ed25519 secret key length
+ static get ED25519_SECRET_KEY_LENGTH() {
+
+ // Return Ed25519 secret key length
+ return 32;
+ }
+
+ // Ed25519 public key length
+ static get ED25519_PUBLIC_KEY_LENGTH() {
+
+ // Return Ed25519 public key length
+ return 32;
+ }
+
+ // X25519 secret key length
+ static get X25519_SECRET_KEY_LENGTH() {
+
+ // Return X25519 secret key length
+ return 32;
+ }
+
+ // X25519 public key length
+ static get X25519_PUBLIC_KEY_LENGTH() {
+
+ // Return X25519 public key length
+ return 32;
+ }
+
+ // Ed25519 signature length
+ static get ED25519_SIGNATURE_LENGTH() {
+
+ // Return Ed25519 signature length
+ return 64;
+ }
+
+ // Maximum message hash signature length
+ static get MAXIMUM_MESSAGE_HASH_SIGNATURE_LENGTH() {
+
+ // Return maximum message hash signature length
+ return 72;
+ }
+
+ // Nonce length
+ static get NONCE_LENGTH() {
+
+ // Return nonce length
+ return 32;
+ }
+
+ // Tau x length
+ static get TAU_X_LENGTH() {
+
+ // Return tau x length
+ return 32;
+ }
+
+ // AES key length
+ static get AES_KEY_LENGTH() {
+
+ // Return AES key length
+ return 32;
+ }
+
+ // Private
+
+ // Derive secret key and chain code
+ static deriveSecretKeyAndChainCode(secretKey, chainCode, path, useBip39 = false) {
+
+ // Return promise
+ return new Promise(function(resolve, reject) {
+
+ // Return creating crypto key from chain code
+ return crypto["subtle"].importKey("raw", chainCode, {
+
+ // Name
+ "name": Crypto.CHAIN_CODE_ENCRYPTION_ALGORITHM,
+
+ // Hash
+ "hash": {
+
+ // Name
+ "name": Crypto.CHAIN_CODE_DIGEST_ALGORITHM
+ }
+ }, false, [
+
+ // Sign
+ "sign"
+
+ ]).then(function(cryptoKey) {
+
+ // Check if path is hardened
+ if(Identifier.isPathHardened(path) === true) {
+
+ // Get hash value from secret key
+ var hashValue = Common.mergeArrays([
+
+ // Zero
+ new Uint8Array([0]),
+
+ // Secret key
+ secretKey
+ ]);
+ }
+
+ // Otherwise
+ else {
+
+ // Check if getting hash value as public key from secret key failed
+ var hashValue = Secp256k1Zkp.publicKeyFromSecretKey(secretKey);
+
+ if(hashValue === Secp256k1Zkp.OPERATION_FAILED) {
+
+ // TODO Securely clear cryptoKey
+
+ // Reject error
+ reject("Getting public key from secret key failed.");
+
+ // Return
+ return;
+ }
+ }
+
+ // Create secret key and chain code
+ var createSecretKeyAndChainCode = function(hashValueCandidate) {
+
+ // Return promise
+ return new Promise(function(resolve, reject) {
+
+ // Append path to hash value candidate
+ var pathBuffer = new ArrayBuffer(Uint32Array["BYTES_PER_ELEMENT"]);
+
+ var pathBufferView = new DataView(pathBuffer);
+ pathBufferView.setUint32(0, path, false);
+
+ var newHashValue = Common.mergeArrays([
+
+ // Hash value candidate
+ hashValueCandidate,
+
+ // Path
+ new Uint8Array(pathBuffer)
+ ]);
+
+ // Securely clear hash value candidate
+ hashValueCandidate.fill(0);
+
+ // Return creating new extended private key from signing the new hash value
+ return crypto["subtle"].sign(Crypto.CHAIN_CODE_ENCRYPTION_ALGORITHM, cryptoKey, newHashValue).then(function(extendedPrivateKey) {
+
+ // Securely clear new hash value
+ newHashValue.fill(0);
+
+ // Get extended private key in correct format
+ extendedPrivateKey = new Uint8Array(extendedPrivateKey);
+
+ // Get new secret key from the extended private key
+ var newSecretKey = extendedPrivateKey.subarray(0, Crypto.SECP256K1_SECRET_KEY_LENGTH);
+
+ // Check if new secret key is a valid secret key or using BIP39 and new secret key is zero
+ if(Secp256k1Zkp.isValidSecretKey(newSecretKey) === true || (useBip39 === true && Common.arraysAreEqualTimingSafe(newSecretKey, Crypto.ZERO_SECRET_KEY) === true)) {
+
+ // Check if adding old secret key to the new secret key was successful
+ var updatedSecretKey = Secp256k1Zkp.secretKeyTweakAdd(newSecretKey, secretKey);
+
+ if(updatedSecretKey !== Secp256k1Zkp.OPERATION_FAILED) {
+
+ // Get updated chain code from the extended private key
+ var updatedChainCode = new Uint8Array(extendedPrivateKey.subarray(Crypto.SECP256K1_SECRET_KEY_LENGTH));
+
+ // Securely clear extended private key
+ extendedPrivateKey.fill(0);
+
+ // Resolve new extended private key
+ resolve([
+
+ // Updated secret key
+ updatedSecretKey,
+
+ // Updated chain code
+ updatedChainCode
+ ]);
+ }
+
+ // Otherwise
+ else {
+
+ // Check if using BIP39
+ if(useBip39 === true) {
+
+ // Set next hash value candidate
+ var nextHashValueCandidate = Common.mergeArrays([
+
+ // One
+ new Uint8Array([1]),
+
+ // New chain code
+ extendedPrivateKey.subarray(Crypto.SECP256K1_SECRET_KEY_LENGTH)
+ ]);
+
+ // Securely clear extended private key
+ extendedPrivateKey.fill(0);
+
+ // Return creating secret key and chain code
+ return createSecretKeyAndChainCode(nextHashValueCandidate).then(function(secretKeyAndChainCode) {
+
+ // Resolve secret key and chain code
+ resolve(secretKeyAndChainCode);
+
+ // Catch error
+ }).catch(function(error) {
+
+ // Reject error
+ reject(error);
+ });
+ }
+
+ // Otherwise
+ else {
+
+ // Securely clear extended private key
+ extendedPrivateKey.fill(0);
+
+ // Reject error
+ reject("Performing secret key tweak add failed.");
+ }
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Check if using BIP39
+ if(useBip39 === true) {
+
+ // Set next hash value candidate
+ var nextHashValueCandidate = Common.mergeArrays([
+
+ // One
+ new Uint8Array([1]),
+
+ // New chain code
+ extendedPrivateKey.subarray(Crypto.SECP256K1_SECRET_KEY_LENGTH)
+ ]);
+
+ // Securely clear extended private key
+ extendedPrivateKey.fill(0);
+
+ // Return creating secret key and chain code
+ return createSecretKeyAndChainCode(nextHashValueCandidate).then(function(secretKeyAndChainCode) {
+
+ // Resolve secret key and chain code
+ resolve(secretKeyAndChainCode);
+
+ // Catch error
+ }).catch(function(error) {
+
+ // Reject error
+ reject(error);
+ });
+ }
+
+ // Otherwise
+ else {
+
+ // Securely clear extended private key
+ extendedPrivateKey.fill(0);
+
+ // Reject error
+ reject("New secret key isn't a valid secret key or using BIP39 and new secret key isn't zero.");
+ }
+ }
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Securely clear new hash value
+ newHashValue.fill(0);
+
+ // Reject error
+ reject("Creating new extended private key failed.");
+ });
+ });
+ };
+
+ // Return creating secret key and chain code
+ return createSecretKeyAndChainCode(hashValue).then(function(secretKeyAndChainCode) {
+
+ // TODO Securely clear cryptoKey
+
+ // Resolve secret key and chain code
+ resolve(secretKeyAndChainCode);
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // TODO Securely clear cryptoKey
+
+ // Reject error
+ reject(error);
+ });
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Reject error
+ reject("Creating crypto key failed.");
+ });
+ });
+ }
+
+ // Chain code length
+ static get CHAIN_CODE_LENGTH() {
+
+ // Return chain code length
+ return 32;
+ }
+
+ // Secret key index
+ static get SECRET_KEY_INDEX() {
+
+ // Return secret key index
+ return 0;
+ }
+
+ // Chain code index
+ static get CHAIN_CODE_INDEX() {
+
+ // Return chain code index
+ return Crypto.SECRET_KEY_INDEX + 1;
+ }
+
+ // Chain code encryption algorithm
+ static get CHAIN_CODE_ENCRYPTION_ALGORITHM() {
+
+ // Return chain code encryption algorithm
+ return "HMAC";
+ }
+
+ // Chain code digest algorithm
+ static get CHAIN_CODE_DIGEST_ALGORITHM() {
+
+ // Return chain code digest algorithm
+ return "SHA-512";
+ }
+
+ // Address key amount
+ static get ADDRESS_KEY_AMOUNT() {
+
+ // Return address key amount
+ return new BigNumber(713);
+ }
+
+ // Address key seed
+ static get ADDRESS_KEY_SEED() {
+
+ // Return address key seed
+ return "Grinbox_seed";
+ }
+
+ // Address key encryption algorithm
+ static get ADDRESS_KEY_ENCRYPTION_ALGORITHM() {
+
+ // Return address key encryption algorithm
+ return "HMAC";
+ }
+
+ // Address key digest algorithm
+ static get ADDRESS_KEY_DIGEST_ALGORITHM() {
+
+ // Return address key digest algorithm
+ return "SHA-512";
+ }
+
+ // AES algorithm
+ static get AES_ALGORITHM() {
+
+ // Return AES algorithm
+ return "AES-CBC";
+ }
+
+ // AES initialization vector size
+ static get AES_INITIALIZATION_VECTOR_SIZE() {
+
+ // Return AES initialization vector size
+ return 16;
+ }
+}
+
+
+// Main function
+
+// Set global object's crypto
+globalThis["Crypto"] = Crypto;
diff --git a/scripts/database.js b/scripts/database.js
new file mode 100755
index 0000000..da0b285
--- /dev/null
+++ b/scripts/database.js
@@ -0,0 +1,1758 @@
+// Use strict
+"use strict";
+
+
+// Classes
+
+// Database class
+class Database {
+
+ // Public
+
+ // Initialize
+ static initialize() {
+
+ // Set instance to invalid
+ Database.instance = Database.INVALID;
+
+ // Set object store locks
+ Database.objectStoreLocks = new Set();
+
+ // Set object store unlock event index
+ Database.objectStoreUnlockEventIndex = 0;
+
+ // Return promise
+ return new Promise(function(resolve, reject) {
+
+ // Try
+ try {
+
+ // Request database
+ var databaseRequest = indexedDB.open(Database.NAME, Database.VERSION);
+ }
+
+ // Catch errors
+ catch(error) {
+
+ // Reject error
+ reject("Failed to create and/or open database.");
+
+ // Return
+ return;
+ }
+
+ // Database request on error
+ databaseRequest["onerror"] = function() {
+
+ // Reject
+ reject();
+ };
+
+ // Database request on upgrade needed
+ databaseRequest["onupgradeneeded"] = function(event) {
+
+ // Get database
+ var database = event["target"]["result"];
+
+ // Get database transaction
+ var databaseTransaction = event["target"]["transaction"];
+
+ // Get current version
+ var currentVersion = event["oldVersion"];
+
+ // Database on error
+ database["onerror"] = function() {
+
+ // Reject
+ reject();
+ };
+
+ // Check if new databases exists
+ if(typeof Database.newDatabases !== "undefined") {
+
+ // Go through all new databases
+ for(var i = 0; i < Database.newDatabases["length"]; ++i)
+
+ // Create new database
+ Database.newDatabases[i](database, currentVersion, databaseTransaction);
+ }
+ };
+
+ // Database request on success
+ databaseRequest["onsuccess"] = function(event) {
+
+ // Set instance
+ Database.instance = event["target"]["result"];
+
+ // Check if critical initialization functions don't exist
+ if(typeof Database.criticalInitializationFunctions === "undefined")
+
+ // Create critical initialzation functions
+ Database.criticalInitializationFunctions = [];
+
+ // Run all critical initialization functions
+ Promise.all(Database.criticalInitializationFunctions.map(function(criticalInitializationFunction) {
+
+ // Return performing critical initialization function
+ return criticalInitializationFunction();
+
+ })).then(function() {
+
+ // Check if initialization functions don't exist
+ if(typeof Database.initializationFunctions === "undefined")
+
+ // Create initialization functions
+ Database.initializationFunctions = [];
+
+ // Run all initialization functions
+ Promise.all(Database.initializationFunctions.map(function(initializationFunction) {
+
+ // Return performing initialization function
+ return initializationFunction();
+
+ })).then(function() {
+
+ // Resolve
+ resolve();
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Reject
+ reject();
+ });
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Reject
+ reject();
+ });
+ };
+ });
+ }
+
+ // Create database
+ static createDatabase(newDatabase) {
+
+ // Check if new databases don't exist
+ if(typeof Database.newDatabases === "undefined")
+
+ // Create new databases
+ Database.newDatabases = [];
+
+ // Append new database to list
+ Database.newDatabases.push(newDatabase);
+ }
+
+ // Once initialized
+ static onceInitialized(initializationFunction, isCritical = false) {
+
+ // Check if critical
+ if(isCritical === true) {
+
+ // Check if critical initialization functions don't exist
+ if(typeof Database.criticalInitializationFunctions === "undefined")
+
+ // Create critical initialization functions
+ Database.criticalInitializationFunctions = [];
+
+ // Append critical initialization function to list
+ Database.criticalInitializationFunctions.push(initializationFunction);
+ }
+
+ // Otherwise
+ else {
+
+ // Check if initialization functions don't exist
+ if(typeof Database.initializationFunctions === "undefined")
+
+ // Create initialization functions
+ Database.initializationFunctions = [];
+
+ // Append initialization function to list
+ Database.initializationFunctions.push(initializationFunction);
+ }
+ }
+
+ // Create transaction
+ static createTransaction(objectStoreNames, type = Database.READ_MODE, durability = Database.RELAXED_DURABILITY, autocomplete = true) {
+
+ // Check if object stores names isn't an array
+ if(Array.isArray(objectStoreNames) === false) {
+
+ // Make object store names an array
+ objectStoreNames = [objectStoreNames];
+ }
+
+ // Initialize locked object stores
+ var lockedObjectStores = [];
+
+ // Return promise
+ return new Promise(function(resolve, reject) {
+
+ // Return locking all specified object stores
+ return Promise.all(objectStoreNames.map(function(objectStoreName) {
+
+ // Return promise
+ return new Promise(function(resolve, reject) {
+
+ // Check if object store is locked
+ if(Database.objectStoreLocks.has(objectStoreName) === true) {
+
+ // Get current unlock event index
+ var index = Database.objectStoreUnlockEventIndex++;
+
+ // Check if current unlock event index is at the max safe integer
+ if(index === Number.MAX_SAFE_INTEGER)
+
+ // Reset unlock event index
+ Database.objectStoreUnlockEventIndex = 0;
+
+ // Database object store unlock index event
+ $(Database.instance).on(Database.OBJECT_STORE_UNLOCK_EVENT + "." + index.toFixed(), function(event, unlockedObjectStoreName) {
+
+ // Check if object store is unlocked
+ if(objectStoreName === unlockedObjectStoreName && Database.objectStoreLocks.has(objectStoreName) === false) {
+
+ // Turn off database object store unlock index event
+ $(Database.instance).off(Database.OBJECT_STORE_UNLOCK_EVENT + "." + index.toFixed());
+
+ // Lock object store
+ Database.objectStoreLocks.add(objectStoreName);
+
+ // Append locked object store to list
+ lockedObjectStores.push(objectStoreName);
+
+ // Resolve
+ resolve();
+ }
+ });
+ }
+
+ // Otherwise
+ else {
+
+ // Lock object store
+ Database.objectStoreLocks.add(objectStoreName);
+
+ // Append locked object store to list
+ lockedObjectStores.push(objectStoreName);
+
+ // Resolve
+ resolve();
+ }
+ });
+ })).then(function() {
+
+ // Try
+ try {
+
+ // Create transaction
+ var transaction = Database.instance.transaction(objectStoreNames, type, {
+
+ // Durability
+ "durability": durability
+ });
+ }
+
+ // Catch errors
+ catch(error) {
+
+ // Go through all locked object stores
+ for(var i = 0; i < lockedObjectStores["length"]; ++i) {
+
+ // Get object store
+ var objectStoreName = lockedObjectStores[i];
+
+ // Check if object store is locked
+ if(Database.objectStoreLocks.has(objectStoreName) === true) {
+
+ // Unlock object store
+ Database.objectStoreLocks.delete(objectStoreName);
+
+ // Trigger object store unlock event
+ $(Database.instance).trigger(Database.OBJECT_STORE_UNLOCK_EVENT, objectStoreName);
+ }
+ }
+
+ // Reject
+ reject();
+
+ // Return
+ return;
+ }
+
+ // Check if autocomplete
+ if(autocomplete === true) {
+
+ // Transaction complete, abort, or error event
+ $(transaction).on("complete abort error", function() {
+
+ // Unlock locked object stores
+ Database.unlockObjectStores(lockedObjectStores);
+ });
+ }
+
+ // Create database transaction
+ var databaseTransaction = new DatabaseTransaction(transaction, lockedObjectStores, autocomplete);
+
+ // Resolve database transaction
+ resolve(databaseTransaction);
+ });
+ });
+ }
+
+ // Commit transaction
+ static commitTransaction(transaction) {
+
+ // Return promise
+ return new Promise(function(resolve, reject) {
+
+ // Check if transaction is completed
+ if(transaction.isCompleted() === true) {
+
+ // Check if transaction isn't autocomplete
+ if(transaction.getAutocomplete() === false) {
+
+ // Unlock transaction's object stores
+ Database.unlockObjectStores(transaction.getObjectStoreNames());
+ }
+
+ // Resolve
+ resolve();
+ }
+
+ // Otherwise check if transaction is aborted
+ else if(transaction.isAborted() === true) {
+
+ // Check if transaction isn't autocomplete
+ if(transaction.getAutocomplete() === false) {
+
+ // Unlock transaction's object stores
+ Database.unlockObjectStores(transaction.getObjectStoreNames());
+ }
+
+ // Reject
+ reject();
+ }
+
+ // Otherwise
+ else {
+
+ // Transaction on complete
+ transaction.getTransaction()["oncomplete"] = function() {
+
+ // Check if transaction isn't autocomplete
+ if(transaction.getAutocomplete() === false) {
+
+ // Unlock transaction's object stores
+ Database.unlockObjectStores(transaction.getObjectStoreNames());
+ }
+
+ // Resolve
+ resolve();
+ };
+
+ // Transaction on error
+ transaction.getTransaction()["onerror"] = function() {
+
+ // Check if transaction isn't autocomplete
+ if(transaction.getAutocomplete() === false) {
+
+ // Unlock transaction's object stores
+ Database.unlockObjectStores(transaction.getObjectStoreNames());
+ }
+
+ // Reject
+ reject();
+ };
+
+ // Transaction on abort
+ transaction.getTransaction()["onabort"] = function() {
+
+ // Check if transaction isn't autocomplete
+ if(transaction.getAutocomplete() === false) {
+
+ // Unlock transaction's object stores
+ Database.unlockObjectStores(transaction.getObjectStoreNames());
+ }
+
+ // Reject
+ reject();
+ };
+
+ // Try
+ try {
+
+ // Commit transaction
+ transaction.getTransaction().commit();
+ }
+
+ // Catch errors
+ catch(error) {
+
+ }
+ }
+ });
+ }
+
+ // Cancel transaction
+ static abortTransaction(transaction) {
+
+ // Return promise
+ return new Promise(function(resolve, reject) {
+
+ // Check if transaction is aborted
+ if(transaction.isAborted() === true) {
+
+ // Check if transaction isn't autocomplete
+ if(transaction.getAutocomplete() === false) {
+
+ // Unlock transaction's object stores
+ Database.unlockObjectStores(transaction.getObjectStoreNames());
+ }
+
+ // Resolve
+ resolve();
+ }
+
+ // Otherwise check if transaction is completed
+ else if(transaction.isCompleted() === true) {
+
+ // Check if transaction isn't autocomplete
+ if(transaction.getAutocomplete() === false) {
+
+ // Unlock transaction's object stores
+ Database.unlockObjectStores(transaction.getObjectStoreNames());
+ }
+
+ // Reject
+ reject();
+ }
+
+ // Otherwise
+ else {
+
+ // Transaction on abort
+ transaction.getTransaction()["onabort"] = function() {
+
+ // Check if transaction isn't autocomplete
+ if(transaction.getAutocomplete() === false) {
+
+ // Unlock transaction's object stores
+ Database.unlockObjectStores(transaction.getObjectStoreNames());
+ }
+
+ // Resolve
+ resolve();
+ };
+
+ // Transaction on error
+ transaction.getTransaction()["onerror"] = function() {
+
+ // Check if transaction isn't autocomplete
+ if(transaction.getAutocomplete() === false) {
+
+ // Unlock transaction's object stores
+ Database.unlockObjectStores(transaction.getObjectStoreNames());
+ }
+
+ // Reject
+ reject();
+ };
+
+ // Transaction on complete
+ transaction.getTransaction()["oncomplete"] = function() {
+
+ // Check if transaction isn't autocomplete
+ if(transaction.getAutocomplete() === false) {
+
+ // Unlock transaction's object stores
+ Database.unlockObjectStores(transaction.getObjectStoreNames());
+ }
+
+ // Reject
+ reject();
+ };
+
+ // Try
+ try {
+
+ // Abort transaction
+ transaction.getTransaction().abort();
+ }
+
+ // Catch errors
+ catch(error) {
+
+ }
+ }
+ });
+ }
+
+ // Get count
+ static getCount(objectStoreName, index = Database.NO_INDEX, range = Database.NO_RANGE, transaction = Database.CREATE_NEW_TRANSACTION) {
+
+ // Return promise
+ return new Promise(function(resolve, reject) {
+
+ // Check if instance is invalid
+ if(Database.instance === Database.INVALID) {
+
+ // Reject error
+ reject("Invalid database.");
+
+ // Return
+ return;
+ }
+
+ // Initialize count
+ var count;
+
+ // Get if creating a new transaction
+ var createNewTransaction = transaction === Database.CREATE_NEW_TRANSACTION;
+
+ // Get current transaction
+ var getCurrentTransaction = new Promise(function(resolve, reject) {
+
+ // Check if creating a new transaction
+ if(createNewTransaction === true) {
+
+ // Return creating a transaction
+ return Database.createTransaction(objectStoreName, Database.READ_MODE).then(function(transaction) {
+
+ // Resolve transaction
+ resolve(transaction);
+
+ // Catch error
+ }).catch(function(error) {
+
+ // Reject error
+ reject(error);
+ });
+ }
+
+ // Otherwise
+ else {
+
+ // Resolve transaction
+ resolve(transaction);
+ }
+ });
+
+ // Return getting current transaction
+ return getCurrentTransaction.then(function(currentTransaction) {
+
+ // Check if creating a new transaction
+ if(createNewTransaction === true) {
+
+ // Current transaction on complete
+ currentTransaction.getTransaction()["oncomplete"] = function() {
+
+ // Resolve count
+ resolve(count);
+ };
+ }
+
+ // Current transaction on error
+ currentTransaction.getTransaction()["onerror"] = function(error) {
+
+ // Reject
+ reject();
+ };
+
+ // Current transaction on abort
+ currentTransaction.getTransaction()["onabort"] = function() {
+
+ // Reject
+ reject();
+ };
+
+ // Get the object store
+ var objectStore = currentTransaction.getTransaction().objectStore(objectStoreName);
+
+ // Create count request to get count from the object store
+ var countRequest = (index === Database.NO_INDEX) ? objectStore.count(range) : objectStore.index(index).count(range);
+
+ // Count request on success
+ countRequest["onsuccess"] = function(event) {
+
+ // Get result
+ var result = event["target"]["result"];
+
+ // Get count from result
+ count = result;
+
+ // Check if not creating a new transaction
+ if(createNewTransaction === false)
+
+ // Resolve count
+ resolve(count);
+ };
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Reject
+ reject();
+ });
+ });
+ }
+
+ // Get key paths
+ static getKeyPaths(objectStoreName, transaction = Database.CREATE_NEW_TRANSACTION) {
+
+ // Return promise
+ return new Promise(function(resolve, reject) {
+
+ // Check if instance is invalid
+ if(Database.instance === Database.INVALID) {
+
+ // Reject error
+ reject("Invalid database.");
+
+ // Return
+ return;
+ }
+
+ // Initialize key paths
+ var keyPaths = [];
+
+ // Get if creating a new transaction
+ var createNewTransaction = transaction === Database.CREATE_NEW_TRANSACTION;
+
+ // Get current transaction
+ var getCurrentTransaction = new Promise(function(resolve, reject) {
+
+ // Check if creating a new transaction
+ if(createNewTransaction === true) {
+
+ // Return creating a transaction
+ return Database.createTransaction(objectStoreName, Database.READ_MODE).then(function(transaction) {
+
+ // Resolve transaction
+ resolve(transaction);
+
+ // Catch error
+ }).catch(function(error) {
+
+ // Reject error
+ reject(error);
+ });
+ }
+
+ // Otherwise
+ else {
+
+ // Resolve transaction
+ resolve(transaction);
+ }
+ });
+
+ // Return getting current transaction
+ return getCurrentTransaction.then(function(currentTransaction) {
+
+ // Check if creating a new transaction
+ if(createNewTransaction === true) {
+
+ // Current transaction on complete
+ currentTransaction.getTransaction()["oncomplete"] = function() {
+
+ // Resolve key paths
+ resolve(keyPaths);
+ };
+ }
+
+ // Current transaction on error
+ currentTransaction.getTransaction()["onerror"] = function() {
+
+ // Reject
+ reject();
+ };
+
+ // Current transaction on abort
+ currentTransaction.getTransaction()["onabort"] = function() {
+
+ // Reject
+ reject();
+ };
+
+ // Create key cursor request to read from the object store
+ var keyCursorRequest = currentTransaction.getTransaction().objectStore(objectStoreName).openKeyCursor();
+
+ // Key cursor request on success
+ keyCursorRequest["onsuccess"] = function(event) {
+
+ // Get result
+ var result = event["target"]["result"];
+
+ // Check if result exists
+ if(result !== Database.NO_RECORDS_MATCH_CURSOR_RESULT) {
+
+ // Get key path from result
+ var keyPath = result["key"];
+
+ // Append key path to key paths
+ keyPaths.push(result["key"]);
+
+ // Get next result
+ result.continue();
+ }
+
+ // Otherwise check if not creating a new transaction
+ else if(createNewTransaction === false)
+
+ // Resolve key paths
+ resolve(keyPaths);
+ };
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Reject
+ reject();
+ });
+ });
+ }
+
+ // Get results
+ static getResults(objectStoreName, startPosition = Database.GET_ALL_RESULTS, count = Database.GET_ALL_RESULTS, index = Database.NO_INDEX, range = Database.NO_RANGE, direction = Database.FORWARD_DIRECTION, transaction = Database.CREATE_NEW_TRANSACTION) {
+
+ // Return promise
+ return new Promise(function(resolve, reject) {
+
+ // Check if instance is invalid
+ if(Database.instance === Database.INVALID) {
+
+ // Reject error
+ reject("Invalid database.");
+
+ // Return
+ return;
+ }
+
+ // Check if start position or count is invalid
+ if(startPosition < 0 || count < 0) {
+
+ // Reject error
+ reject("Invalid parameters.");
+
+ // Return
+ return;
+ }
+
+ // Initialize results
+ var results = [];
+
+ // Check if count is zero
+ if(count === 0) {
+
+ // Resolve results
+ resolve(results);
+
+ // Return
+ return;
+ }
+
+ // Get if creating a new transaction
+ var createNewTransaction = transaction === Database.CREATE_NEW_TRANSACTION;
+
+ // Get current transaction
+ var getCurrentTransaction = new Promise(function(resolve, reject) {
+
+ // Check if creating a new transaction
+ if(createNewTransaction === true) {
+
+ // Return creating a transaction
+ return Database.createTransaction(objectStoreName, Database.READ_MODE).then(function(transaction) {
+
+ // Resolve transaction
+ resolve(transaction);
+
+ // Catch error
+ }).catch(function(error) {
+
+ // Reject error
+ reject(error);
+ });
+ }
+
+ // Otherwise
+ else {
+
+ // Resolve transaction
+ resolve(transaction);
+ }
+ });
+
+ // Return getting current transaction
+ return getCurrentTransaction.then(function(currentTransaction) {
+
+ // Check if creating a new transaction
+ if(createNewTransaction === true) {
+
+ // Current transaction on complete
+ currentTransaction.getTransaction()["oncomplete"] = function() {
+
+ // Resolve results
+ resolve(results);
+ };
+ }
+
+ // Current transaction on error
+ currentTransaction.getTransaction()["onerror"] = function() {
+
+ // Reject
+ reject();
+ };
+
+ // Current transaction on abort
+ currentTransaction.getTransaction()["onabort"] = function() {
+
+ // Reject
+ reject();
+ };
+
+ // Get the object store
+ var objectStore = currentTransaction.getTransaction().objectStore(objectStoreName);
+
+ // Create cursor request to read from the object store
+ var cursorRequest = (index === Database.NO_INDEX) ? objectStore.openKeyCursor(range, direction) : objectStore.index(index).openKeyCursor(range, direction);
+
+ // Cursor request on success
+ var firstResult = true;
+ cursorRequest["onsuccess"] = function(event) {
+
+ // Get result
+ var result = event["target"]["result"];
+
+ // Check if result exists
+ if(result !== Database.NO_RECORDS_MATCH_CURSOR_RESULT) {
+
+ // Check if at the first result
+ if(firstResult === true) {
+
+ // Clear first result
+ firstResult = false;
+
+ // Check if a start position is provided and it's not zero
+ if(startPosition !== Database.GET_ALL_RESULTS && startPosition !== 0) {
+
+ // Advance result to start position
+ result.advance(startPosition);
+
+ // Return
+ return;
+ }
+ }
+
+ // Get key path from result
+ var keyPath = result["primaryKey"];
+
+ // Create get request to read from the object store using the key path
+ var getRequest = objectStore.get(keyPath);
+
+ // Get request on success
+ getRequest["onsuccess"] = function(event) {
+
+ // Get request's result
+ var requestsResult = event["target"]["result"];
+
+ // Check if request's result exists
+ if(requestsResult !== Database.NO_RECORDS_MATCH_GET_RESULT) {
+
+ // Set key path in result
+ requestsResult[Database.KEY_PATH_NAME] = keyPath;
+
+ // Append result to results
+ results.push(requestsResult);
+ }
+
+ // Check if not creating a new transaction, a count is provided, and it's currently at the count
+ if(createNewTransaction === false && count !== Database.GET_ALL_RESULTS && count === 0)
+
+ // Resolve results
+ resolve(results);
+ };
+
+ // Check if a count isn't provided and currently not at the count
+ if(count === Database.GET_ALL_RESULTS || --count > 0)
+
+ // Get next result
+ result.continue();
+ }
+
+ // Otherwise check if not creating a new transaction
+ else if(createNewTransaction === false)
+
+ // Resolve results
+ resolve(results);
+ };
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Reject
+ reject();
+ });
+ });
+ }
+
+ // Get result
+ static getResult(objectStoreName, keyPath, transaction = Database.CREATE_NEW_TRANSACTION) {
+
+ // Return promise
+ return new Promise(function(resolve, reject) {
+
+ // Check if instance is invalid
+ if(Database.instance === Database.INVALID) {
+
+ // Reject error
+ reject("Invalid database.");
+
+ // Return
+ return;
+ }
+
+ // Initialize result
+ var result = Database.RESULT_NOT_FOUND;
+
+ // Get if creating a new transaction
+ var createNewTransaction = transaction === Database.CREATE_NEW_TRANSACTION;
+
+ // Get current transaction
+ var getCurrentTransaction = new Promise(function(resolve, reject) {
+
+ // Check if creating a new transaction
+ if(createNewTransaction === true) {
+
+ // Return creating a transaction
+ return Database.createTransaction(objectStoreName, Database.READ_MODE).then(function(transaction) {
+
+ // Resolve transaction
+ resolve(transaction);
+
+ // Catch error
+ }).catch(function(error) {
+
+ // Reject error
+ reject(error);
+ });
+ }
+
+ // Otherwise
+ else {
+
+ // Resolve transaction
+ resolve(transaction);
+ }
+ });
+
+ // Return getting current transaction
+ return getCurrentTransaction.then(function(currentTransaction) {
+
+ // Check if creating a new transaction
+ if(createNewTransaction === true) {
+
+ // Current transaction on complete
+ currentTransaction.getTransaction()["oncomplete"] = function() {
+
+ // Resolve result
+ resolve(result);
+ };
+ }
+
+ // Current transaction on error
+ currentTransaction.getTransaction()["onerror"] = function() {
+
+ // Reject
+ reject();
+ };
+
+ // Current transaction on abort
+ currentTransaction.getTransaction()["onabort"] = function() {
+
+ // Reject
+ reject();
+ };
+
+ // Create get request to read from the object store using the key path
+ var getRequest = currentTransaction.getTransaction().objectStore(objectStoreName).get(keyPath);
+
+ // Get request on success
+ getRequest["onsuccess"] = function(event) {
+
+ // Get request's result
+ var requestsResult = event["target"]["result"];
+
+ // Check if request's result exists
+ if(requestsResult !== Database.NO_RECORDS_MATCH_GET_RESULT) {
+
+ // Set result to request's result
+ result = requestsResult;
+
+ // Set key path in result
+ result[Database.KEY_PATH_NAME] = keyPath;
+ }
+
+ // Check if not creating a new transaction
+ if(createNewTransaction === false)
+
+ // Resolve result
+ resolve(result);
+ };
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Reject
+ reject();
+ });
+ });
+ }
+
+ // Save results
+ static saveResults(objectStoreName, results, resultsKeyPaths = [], transaction = Database.CREATE_NEW_TRANSACTION, durability = Database.RELAXED_DURABILITY) {
+
+ // Return promise
+ return new Promise(function(resolve, reject) {
+
+ // Check if instance is invalid
+ if(Database.instance === Database.INVALID) {
+
+ // Reject error
+ reject("Invalid database.");
+
+ // Return
+ return;
+ }
+
+ // Initialize new key paths
+ var newKeyPaths = [];
+
+ // Get if creating a new transaction
+ var createNewTransaction = transaction === Database.CREATE_NEW_TRANSACTION;
+
+ // Get current transaction
+ var getCurrentTransaction = new Promise(function(resolve, reject) {
+
+ // Check if creating a new transaction
+ if(createNewTransaction === true) {
+
+ // Return creating a transaction
+ return Database.createTransaction(objectStoreName, Database.READ_AND_WRITE_MODE, durability).then(function(transaction) {
+
+ // Resolve transaction
+ resolve(transaction);
+
+ // Catch error
+ }).catch(function(error) {
+
+ // Reject error
+ reject(error);
+ });
+ }
+
+ // Otherwise
+ else {
+
+ // Resolve transaction
+ resolve(transaction);
+ }
+ });
+
+ // Return getting current transaction
+ return getCurrentTransaction.then(function(currentTransaction) {
+
+ // Check if creating a new transaction
+ if(createNewTransaction === true) {
+
+ // Current transaction on complete
+ currentTransaction.getTransaction()["oncomplete"] = function() {
+
+ // Resolve new key paths
+ resolve(newKeyPaths);
+ };
+ }
+
+ // Current transaction on error
+ currentTransaction.getTransaction()["onerror"] = function() {
+
+ // Reject
+ reject();
+ };
+
+ // Current transaction on abort
+ currentTransaction.getTransaction()["onabort"] = function() {
+
+ // Reject
+ reject();
+ };
+
+ // Get the object store
+ var objectStore = currentTransaction.getTransaction().objectStore(objectStoreName);
+
+ // Go through all result
+ for(var i = 0, j = 0; i < results["length"]; ++i) {
+
+ // Check if result includes a key path
+ if(i < resultsKeyPaths["length"]) {
+
+ // Create put request to write to the object store
+ var putRequest = objectStore.put(results[i], resultsKeyPaths[i]);
+ }
+
+ // Otherwise
+ else {
+
+ // Create put request to write to the object store
+ var putRequest = objectStore.put(results[i]);
+ }
+
+ // Put request on success
+ putRequest["onsuccess"] = function(event) {
+
+ // Get key path from result
+ var keyPath = event["target"]["result"];
+
+ // Appen key path to list
+ newKeyPaths.push(keyPath);
+
+ // Check if not creating a new transaction
+ if(createNewTransaction === false) {
+
+ // Check if all put requests were completed
+ if(++j === results["length"])
+
+ // Resolve new key paths
+ resolve(newKeyPaths);
+ }
+ };
+ }
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Reject
+ reject();
+ });
+ });
+ }
+
+ // Save result
+ static saveResult(objectStoreName, result, keyPath = Database.CREATE_NEW_KEY_PATH, transaction = Database.CREATE_NEW_TRANSACTION, durability = Database.RELAXED_DURABILITY) {
+
+ // Return promise
+ return new Promise(function(resolve, reject) {
+
+ // Check if instance is invalid
+ if(Database.instance === Database.INVALID) {
+
+ // Reject error
+ reject("Invalid database.");
+
+ // Return
+ return;
+ }
+
+ // Initialize new key path
+ var newKeyPath;
+
+ // Get if creating a new transaction
+ var createNewTransaction = transaction === Database.CREATE_NEW_TRANSACTION;
+
+ // Get current transaction
+ var getCurrentTransaction = new Promise(function(resolve, reject) {
+
+ // Check if creating a new transaction
+ if(createNewTransaction === true) {
+
+ // Return creating a transaction
+ return Database.createTransaction(objectStoreName, Database.READ_AND_WRITE_MODE, durability).then(function(transaction) {
+
+ // Resolve transaction
+ resolve(transaction);
+
+ // Catch error
+ }).catch(function(error) {
+
+ // Reject error
+ reject(error);
+ });
+ }
+
+ // Otherwise
+ else {
+
+ // Resolve transaction
+ resolve(transaction);
+ }
+ });
+
+ // Return getting current transaction
+ return getCurrentTransaction.then(function(currentTransaction) {
+
+ // Check if creating a new transaction
+ if(createNewTransaction === true) {
+
+ // Current transaction on complete
+ currentTransaction.getTransaction()["oncomplete"] = function() {
+
+ // Resolve new key path
+ resolve(newKeyPath);
+ };
+ }
+
+ // Current transaction on error
+ currentTransaction.getTransaction()["onerror"] = function() {
+
+ // Reject
+ reject();
+ };
+
+ // Current transaction on abort
+ currentTransaction.getTransaction()["onabort"] = function() {
+
+ // Reject
+ reject();
+ };
+
+ // Create put request to write to the object store
+ var putRequest = currentTransaction.getTransaction().objectStore(objectStoreName).put(result, keyPath);
+
+ // Put request on success
+ putRequest["onsuccess"] = function(event) {
+
+ // Get key path from result
+ var keyPath = event["target"]["result"];
+
+ // Set new key path to key path
+ newKeyPath = keyPath;
+
+ // Check if not creating a new transaction
+ if(createNewTransaction === false)
+
+ // Resolve new key path
+ resolve(newKeyPath);
+ };
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Reject
+ reject();
+ });
+ });
+ }
+
+ // Delete results
+ static deleteResults(objectStoreName, transaction = Database.CREATE_NEW_TRANSACTION, durability = Database.RELAXED_DURABILITY) {
+
+ // Return promise
+ return new Promise(function(resolve, reject) {
+
+ // Check if instance is invalid
+ if(Database.instance === Database.INVALID) {
+
+ // Reject error
+ reject("Invalid database.");
+
+ // Return
+ return;
+ }
+
+ // Get if creating a new transaction
+ var createNewTransaction = transaction === Database.CREATE_NEW_TRANSACTION;
+
+ // Get current transaction
+ var getCurrentTransaction = new Promise(function(resolve, reject) {
+
+ // Check if creating a new transaction
+ if(createNewTransaction === true) {
+
+ // Return creating a transaction
+ return Database.createTransaction(objectStoreName, Database.READ_AND_WRITE_MODE, durability).then(function(transaction) {
+
+ // Resolve transaction
+ resolve(transaction);
+
+ // Catch error
+ }).catch(function(error) {
+
+ // Reject error
+ reject(error);
+ });
+ }
+
+ // Otherwise
+ else {
+
+ // Resolve transaction
+ resolve(transaction);
+ }
+ });
+
+ // Return getting current transaction
+ return getCurrentTransaction.then(function(currentTransaction) {
+
+ // Check if creating a new transaction
+ if(createNewTransaction === true) {
+
+ // Current transaction on complete
+ currentTransaction.getTransaction()["oncomplete"] = function() {
+
+ // Resolve
+ resolve();
+ };
+ }
+
+ // Current transaction on error
+ currentTransaction.getTransaction()["onerror"] = function() {
+
+ // Reject
+ reject();
+ };
+
+ // Current transaction on abort
+ currentTransaction.getTransaction()["onabort"] = function() {
+
+ // Reject
+ reject();
+ };
+
+ // Create clear request to delete everything from the object store
+ var clearRequest = currentTransaction.getTransaction().objectStore(objectStoreName).clear();
+
+ // Check if not creating a new transaction
+ if(createNewTransaction === false) {
+
+ // Clear request on success
+ clearRequest["onsuccess"] = function(event) {
+
+ // Resolve
+ resolve();
+ };
+ }
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Reject
+ reject();
+ });
+ });
+ }
+
+ // Delete results with value
+ static deleteResultsWithValue(objectStoreName, index, range, transaction = Database.CREATE_NEW_TRANSACTION, durability = Database.RELAXED_DURABILITY) {
+
+ // Return promise
+ return new Promise(function(resolve, reject) {
+
+ // Check if instance is invalid
+ if(Database.instance === Database.INVALID) {
+
+ // Reject error
+ reject("Invalid database.");
+
+ // Return
+ return;
+ }
+
+ // Get if creating a new transaction
+ var createNewTransaction = transaction === Database.CREATE_NEW_TRANSACTION;
+
+ // Get current transaction
+ var getCurrentTransaction = new Promise(function(resolve, reject) {
+
+ // Check if creating a new transaction
+ if(createNewTransaction === true) {
+
+ // Return creating a transaction
+ return Database.createTransaction(objectStoreName, Database.READ_AND_WRITE_MODE, durability).then(function(transaction) {
+
+ // Resolve transaction
+ resolve(transaction);
+
+ // Catch error
+ }).catch(function(error) {
+
+ // Reject error
+ reject(error);
+ });
+ }
+
+ // Otherwise
+ else {
+
+ // Resolve transaction
+ resolve(transaction);
+ }
+ });
+
+ // Return getting current transaction
+ return getCurrentTransaction.then(function(currentTransaction) {
+
+ // Check if creating a new transaction
+ if(createNewTransaction === true) {
+
+ // Current transaction on complete
+ currentTransaction.getTransaction()["oncomplete"] = function() {
+
+ // Resolve
+ resolve();
+ };
+ }
+
+ // Current transaction on error
+ currentTransaction.getTransaction()["onerror"] = function() {
+
+ // Reject
+ reject();
+ };
+
+ // Current transaction on abort
+ currentTransaction.getTransaction()["onabort"] = function() {
+
+ // Reject
+ reject();
+ };
+
+ // Get the object store
+ var objectStore = currentTransaction.getTransaction().objectStore(objectStoreName);
+
+ // Create cursor request to write to the object store
+ var cursorRequest = objectStore.index(index).openCursor(range);
+
+ // Cursor request on success
+ cursorRequest["onsuccess"] = function(event) {
+
+ // Get result
+ var result = event["target"]["result"];
+
+ // Check if result exists
+ if(result !== Database.NO_RECORDS_MATCH_CURSOR_RESULT) {
+
+ // Delete result
+ result.delete();
+
+ // Get next result
+ result.continue();
+ }
+
+ // Otherwise check if not creating a new transaction
+ else if(createNewTransaction === false)
+
+ // Resolve
+ resolve();
+ };
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Reject
+ reject();
+ });
+ });
+ }
+
+ // Delete result
+ static deleteResult(objectStoreName, keyPath, transaction = Database.CREATE_NEW_TRANSACTION, durability = Database.RELAXED_DURABILITY) {
+
+ // Return promise
+ return new Promise(function(resolve, reject) {
+
+ // Check if instance is invalid
+ if(Database.instance === Database.INVALID) {
+
+ // Reject error
+ reject("Invalid database.");
+
+ // Return
+ return;
+ }
+
+ // Get if creating a new transaction
+ var createNewTransaction = transaction === Database.CREATE_NEW_TRANSACTION;
+
+ // Get current transaction
+ var getCurrentTransaction = new Promise(function(resolve, reject) {
+
+ // Check if creating a new transaction
+ if(createNewTransaction === true) {
+
+ // Return creating a transaction
+ return Database.createTransaction(objectStoreName, Database.READ_AND_WRITE_MODE, durability).then(function(transaction) {
+
+ // Resolve transaction
+ resolve(transaction);
+
+ // Catch error
+ }).catch(function(error) {
+
+ // Reject error
+ reject(error);
+ });
+ }
+
+ // Otherwise
+ else {
+
+ // Resolve transaction
+ resolve(transaction);
+ }
+ });
+
+ // Return getting current transaction
+ return getCurrentTransaction.then(function(currentTransaction) {
+
+ // Check if creating a new transaction
+ if(createNewTransaction === true) {
+
+ // Current transaction on complete
+ currentTransaction.getTransaction()["oncomplete"] = function() {
+
+ // Resolve
+ resolve();
+ };
+ }
+
+ // Current transaction on error
+ currentTransaction.getTransaction()["onerror"] = function() {
+
+ // Reject
+ reject();
+ };
+
+ // Current transaction on abort
+ currentTransaction.getTransaction()["onabort"] = function() {
+
+ // Reject
+ reject();
+ };
+
+ // Create delete request to delete from the object store using the key path
+ var deleteRequest = currentTransaction.getTransaction().objectStore(objectStoreName).delete(keyPath);
+
+ // Check if not creating a new transaction
+ if(createNewTransaction === false) {
+
+ // Delete request on success
+ deleteRequest["onsuccess"] = function(event) {
+
+ // Resolve
+ resolve();
+ };
+ }
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Reject
+ reject();
+ });
+ });
+ }
+
+ // To key path
+ static toKeyPath(string) {
+
+ // Return string as a key path
+ return string.replace(Database.INVALID_KEY_PATH_PATTERN, Database.INVALID_KEY_PATH_REPLACEMENT);
+ }
+
+ // Result not found
+ static get RESULT_NOT_FOUND() {
+
+ // Return result not found
+ return null;
+ }
+
+ // Create new key path
+ static get CREATE_NEW_KEY_PATH() {
+
+ // Return create new key path
+ return undefined;
+ }
+
+ // Read mode
+ static get READ_MODE() {
+
+ // Return read mode
+ return "readonly";
+ }
+
+ // Read and write mode
+ static get READ_AND_WRITE_MODE() {
+
+ // Return read and write mode
+ return "readwrite";
+ }
+
+ // Key path name
+ static get KEY_PATH_NAME() {
+
+ // Return key path name
+ return "Key Path";
+ }
+
+ // Get all results
+ static get GET_ALL_RESULTS() {
+
+ // Return get all results
+ return null;
+ }
+
+ // No index
+ static get NO_INDEX() {
+
+ // Return no index
+ return null;
+ }
+
+ // Create new transaction
+ static get CREATE_NEW_TRANSACTION() {
+
+ // Return create new transaction
+ return null;
+ }
+
+ // No range
+ static get NO_RANGE() {
+
+ // Return no range
+ return null;
+ }
+
+ // Forward direction
+ static get FORWARD_DIRECTION() {
+
+ // Return forward direction
+ return "next";
+ }
+
+ // Backward direction
+ static get BACKWARD_DIRECTION() {
+
+ // Return backward direction
+ return "prev";
+ }
+
+ // No current version
+ static get NO_CURRENT_VERSION() {
+
+ // Return no current version
+ return 0;
+ }
+
+ // Relaxed durability
+ static get RELAXED_DURABILITY() {
+
+ // Return relaxed durability
+ return "relaxed";
+ }
+
+ // Strict durability
+ static get STRICT_DURABILITY() {
+
+ // Return strict durability
+ return "strict";
+ }
+
+ // Private
+
+ // Unlock object stores
+ static unlockObjectStores(objectStoreNames) {
+
+ // Go through all of the object stores
+ for(var i = 0; i < objectStoreNames["length"]; ++i) {
+
+ // Get object store
+ let objectStoreName = objectStoreNames[i];
+
+ // Check if object store is locked
+ if(Database.objectStoreLocks.has(objectStoreName) === true) {
+
+ // Set timeout
+ setTimeout(function() {
+
+ // Unlock object store
+ Database.objectStoreLocks.delete(objectStoreName);
+
+ // Trigger object store unlock event
+ $(Database.instance).trigger(Database.OBJECT_STORE_UNLOCK_EVENT, objectStoreName);
+ }, 0);
+ }
+ }
+ }
+
+ // Invaid key path pattern
+ static get INVALID_KEY_PATH_PATTERN() {
+
+ // Return invalid key path pattern
+ return /[ ']/gu;
+ }
+
+ // Invalid key path replacement
+ static get INVALID_KEY_PATH_REPLACEMENT() {
+
+ // Return invalid key path replacement
+ return "";
+ }
+
+ // Invalid
+ static get INVALID() {
+
+ // Return invalid
+ return null;
+ }
+
+ // Name
+ static get NAME() {
+
+ // Return name
+ return "Database";
+ }
+
+ // Version one
+ static get VERSION_ONE() {
+
+ // Return version one
+ return 1;
+ }
+
+ // Version
+ static get VERSION() {
+
+ // Return version
+ return 2;
+ }
+
+ // No records match get result
+ static get NO_RECORDS_MATCH_GET_RESULT() {
+
+ // Return no records match get result
+ return undefined;
+ }
+
+ // No records match cursor result
+ static get NO_RECORDS_MATCH_CURSOR_RESULT() {
+
+ // Return no records match cursor result
+ return null;
+ }
+
+ // Object store unlock event
+ static get OBJECT_STORE_UNLOCK_EVENT() {
+
+ // Return object store unlock event
+ return "DatabaseObjectStoreUnlockEvent";
+ }
+}
+
+
+// Main function
+
+// Set global object's database
+globalThis["Database"] = Database;
diff --git a/scripts/database_transaction.js b/scripts/database_transaction.js
new file mode 100755
index 0000000..d0d83a5
--- /dev/null
+++ b/scripts/database_transaction.js
@@ -0,0 +1,87 @@
+// Use strict
+"use strict";
+
+
+// Classes
+
+// Database transaction class
+class DatabaseTransaction {
+
+ // Public
+
+ // Constructor
+ constructor(transaction, objectStoreNames, autocomplete = true) {
+
+ // Set transaction
+ this.transaction = transaction;
+
+ // Set object store names
+ this.objectStoreNames = objectStoreNames;
+
+ // Set autocomplete
+ this.autocomplete = autocomplete;
+
+ // Set completed
+ this.completed = false;
+
+ // Set aborted
+ this.aborted = false;
+
+ // Set self
+ var self = this;
+
+ // Transaction complete event
+ $(this.transaction).on("complete", function() {
+
+ // Set completed
+ self.completed = true;
+
+ // Transaction abort event
+ }).on("abort", function() {
+
+ // Set aborted
+ self.aborted = true;
+ });
+ }
+
+ // Get transaction
+ getTransaction() {
+
+ // Return transaction
+ return this.transaction;
+ }
+
+ // Get object store names
+ getObjectStoreNames() {
+
+ // Return object store names
+ return this.objectStoreNames;
+ }
+
+ // Get autocomplete
+ getAutocomplete() {
+
+ // Return autocomplete
+ return this.autocomplete;
+ }
+
+ // Is completed
+ isCompleted() {
+
+ // Return if completed
+ return this.completed === true;
+ }
+
+ // Is aborted
+ isAborted() {
+
+ // Return if aborted
+ return this.aborted === true;
+ }
+}
+
+
+// Main function
+
+// Set global object's database transaction
+globalThis["DatabaseTransaction"] = DatabaseTransaction;
diff --git a/scripts/emoji.js b/scripts/emoji.js
new file mode 100755
index 0000000..880f47e
--- /dev/null
+++ b/scripts/emoji.js
@@ -0,0 +1,1207 @@
+// Use strict
+"use strict";
+
+
+// Classes
+
+// Emoji class
+class Emoji {
+
+ // Public
+
+ // Encode
+ static encode(text) {
+
+ // Get text as bytes
+ var bytes = (new TextEncoder()).encode(text);
+
+ // Get number of extra bits
+ var numberOfExtraBits = Emoji.NUMBER_BASE - bytes["length"] * Common.BITS_IN_A_BYTE % Emoji.NUMBER_BASE;
+
+ // Create bit writer
+ var bitWriter = new BitWriter();
+
+ // Append number of extra bits to bit writer
+ bitWriter.setBits(numberOfExtraBits, Emoji.NUMBER_BASE);
+
+ // Append extra bits to bit writer
+ bitWriter.setBits(Emoji.EXTRA_BITS_VALUE, numberOfExtraBits);
+
+ // Append bytes to bit writer
+ bitWriter.setBytes(bytes);
+
+ // Create bit reader from bit writer
+ var bitReader = new BitReader(bitWriter.getBytes());
+
+ // Initialize emoji text
+ var emojiText = "";
+
+ // Loop through all bit groups in bit reader
+ while(true) {
+
+ // Try
+ try {
+
+ // Get index from bit group
+ var index = bitReader.getBits(Emoji.NUMBER_BASE);
+
+ // Append glyph at index to emoji text
+ emojiText += Emoji.GLYPHS[index];
+ }
+
+ // Catch errors
+ catch(error) {
+
+ // Break
+ break;
+ }
+ }
+
+ // Return emoji text
+ return emojiText;
+ }
+
+ // Decode
+ static decode(emojiText) {
+
+ // Get emoji text as glyphs
+ var glyphs = Array.from(emojiText);
+
+ // Initialize bit writer
+ var bitWriter = new BitWriter();
+
+ // Initialize number of bits
+ var numberOfBits = 0;
+
+ // Go through all glyphs
+ for(var i = 0; i < glyphs["length"]; ++i) {
+
+ // Check if glyph isn't valid
+ var index = Emoji.GLYPHS.indexOf(glyphs[i]);
+
+ if(index === Common.INDEX_NOT_FOUND) {
+
+ // Throw error
+ throw "Invalid emoji text.";
+ }
+
+ // Append glyphs's index to bit writer
+ bitWriter.setBits(index, Emoji.NUMBER_BASE);
+
+ // Update number of bits
+ numberOfBits += Emoji.NUMBER_BASE;
+ }
+
+ // Create bit reader from bit writer
+ var bitReader = new BitReader(bitWriter.getBytes());
+
+ // Try
+ try {
+
+ // Get number of extra bits from bit reader
+ var numberOfExtraBits = bitReader.getBits(Emoji.NUMBER_BASE);
+ }
+
+ // Catch errors
+ catch(error) {
+
+ // Throw error
+ throw "Invalid emoji text.";
+ }
+
+ // Check if number of extra bits is invalid
+ if(numberOfExtraBits <= 0 || numberOfExtraBits > Emoji.NUMBER_BASE) {
+
+ // Throw error
+ throw "Invalid emoji text.";
+ }
+
+ // Try
+ try {
+
+ // Get extra bits from bit reader
+ var extraBits = bitReader.getBits(numberOfExtraBits);
+ }
+
+ // Catch errors
+ catch(error) {
+
+ // Throw error
+ throw "Invalid emoji text.";
+ }
+
+ // Check if extra bits is invalid
+ if(extraBits !== Emoji.EXTRA_BITS_VALUE) {
+
+ // Throw error
+ throw "Invalid emoji text.";
+ }
+
+ // Check if number of bits is invalid
+ if((numberOfBits - Emoji.NUMBER_BASE - numberOfExtraBits) % Common.BITS_IN_A_BYTE !== 0) {
+
+ // Throw error
+ throw "Invalid emoji text.";
+ }
+
+ // Get remaining bytes from bit reader
+ var bytes = bitReader.getBytes((numberOfBits - Emoji.NUMBER_BASE - numberOfExtraBits) / Common.BITS_IN_A_BYTE);
+
+ // Return bytes as text
+ return (new TextDecoder("utf-8", {"fatal": true})).decode(bytes);
+ }
+
+ // Private
+
+ // Glyphs
+ static get GLYPHS() {
+
+ // Return glyphs
+ return [
+ "🎨",
+ "🎭",
+ "🧵",
+ "🧶",
+ "🪢",
+ "🪡",
+ "🏆",
+ "🥇",
+ "🏅",
+ "🥈",
+ "🥉",
+ "🎈",
+ "🎏",
+ "🎄",
+ "🎊",
+ "🧨",
+ "🎆",
+ "🎃",
+ "🎎",
+ "🎑",
+ "🎉",
+ "🎍",
+ "🧧",
+ "🎀",
+ "🎇",
+ "✨",
+ "🎋",
+ "🎫",
+ "🎐",
+ "🎁",
+ "🎯",
+ "🔮",
+ "🎴",
+ "🎲",
+ "🃏",
+ "🪁",
+ "🪄",
+ "🀄",
+ "🧿",
+ "🪆",
+ "🪅",
+ "🎱",
+ "🧩",
+ "🎰",
+ "🧸",
+ "🎮",
+ "🪀",
+ "🏈",
+ "🏸",
+ "⚾",
+ "🏀",
+ "🎳",
+ "🥊",
+ "🏏",
+ "🥌",
+ "🤿",
+ "🏑",
+ "🎣",
+ "⛳",
+ "🥏",
+ "🥅",
+ "🏒",
+ "🥍",
+ "🥋",
+ "🏓",
+ "🏉",
+ "🎽",
+ "🎿",
+ "🛷",
+ "⚽",
+ "🥎",
+ "🎾",
+ "🏐",
+ "🐸",
+ "🐤",
+ "🐦",
+ "🐔",
+ "🦤",
+ "🦆",
+ "🦅",
+ "🪶",
+ "🦩",
+ "🐥",
+ "🐣",
+ "🦉",
+ "🦜",
+ "🦚",
+ "🐧",
+ "🐓",
+ "🦢",
+ "🦃",
+ "🐜",
+ "🪲",
+ "🐛",
+ "🦋",
+ "🪳",
+ "🦗",
+ "🪰",
+ "🐝",
+ "🐞",
+ "🦠",
+ "🦟",
+ "🦂",
+ "🐌",
+ "🪱",
+ "🦡",
+ "🦇",
+ "🐻",
+ "🦫",
+ "🦬",
+ "🐗",
+ "🐪",
+ "🐈",
+ "🐱",
+ "🐄",
+ "🐮",
+ "🦌",
+ "🐕",
+ "🐶",
+ "🐘",
+ "🐑",
+ "🦊",
+ "🦒",
+ "🐐",
+ "🦍",
+ "🦮",
+ "🐹",
+ "🦔",
+ "🦛",
+ "🐎",
+ "🐴",
+ "🦘",
+ "🐨",
+ "🐆",
+ "🦁",
+ "🦙",
+ "🦣",
+ "🐒",
+ "🐵",
+ "🐁",
+ "🐭",
+ "🦧",
+ "🦦",
+ "🐂",
+ "🐼",
+ "🐾",
+ "🐖",
+ "🐷",
+ "🐽",
+ "🐩",
+ "🐇",
+ "🐰",
+ "🦝",
+ "🐏",
+ "🐀",
+ "🦏",
+ "🦨",
+ "🦥",
+ "🐅",
+ "🐯",
+ "🐫",
+ "🦄",
+ "🐃",
+ "🐺",
+ "🦓",
+ "🐡",
+ "🐬",
+ "🐟",
+ "🐙",
+ "🦭",
+ "🦈",
+ "🐚",
+ "🐳",
+ "🐠",
+ "🐋",
+ "🐊",
+ "🐉",
+ "🐲",
+ "🦎",
+ "🦕",
+ "🐍",
+ "🐢",
+ "🦖",
+ "🌼",
+ "💐",
+ "🌸",
+ "🌺",
+ "🌹",
+ "🌻",
+ "🌷",
+ "💮",
+ "🥀",
+ "🌵",
+ "🌳",
+ "🌲",
+ "🍂",
+ "🍀",
+ "🌿",
+ "🍃",
+ "🍁",
+ "🌴",
+ "🪴",
+ "🌱",
+ "🌾",
+ "🏴",
+ "🏁",
+ "🎌",
+ "🚩",
+ "🏺",
+ "🥢",
+ "🍴",
+ "🔪",
+ "🥄",
+ "🍼",
+ "🍺",
+ "🧃",
+ "🍾",
+ "🧋",
+ "🍻",
+ "🥂",
+ "🍸",
+ "🥤",
+ "🥛",
+ "☕",
+ "🧊",
+ "🧉",
+ "🍶",
+ "🍵",
+ "🫖",
+ "🍹",
+ "🥃",
+ "🍷",
+ "🍱",
+ "🍚",
+ "🍛",
+ "🍡",
+ "🥟",
+ "🍥",
+ "🥠",
+ "🍤",
+ "🥮",
+ "🍢",
+ "🍙",
+ "🍘",
+ "🍠",
+ "🍝",
+ "🍜",
+ "🍣",
+ "🥡",
+ "🍌",
+ "🫐",
+ "🍒",
+ "🥥",
+ "🍇",
+ "🍏",
+ "🥝",
+ "🍋",
+ "🥭",
+ "🍈",
+ "🫒",
+ "🍑",
+ "🍐",
+ "🍍",
+ "🍎",
+ "🍓",
+ "🍊",
+ "🍅",
+ "🍉",
+ "🦀",
+ "🦞",
+ "🦪",
+ "🦐",
+ "🦑",
+ "🥓",
+ "🥯",
+ "🥖",
+ "🥣",
+ "🍞",
+ "🌯",
+ "🧈",
+ "🥫",
+ "🧀",
+ "🍳",
+ "🥐",
+ "🥩",
+ "🥚",
+ "🧆",
+ "🫓",
+ "🫕",
+ "🍟",
+ "🥗",
+ "🍔",
+ "🌭",
+ "🍖",
+ "🥞",
+ "🍕",
+ "🍿",
+ "🍲",
+ "🍗",
+ "🥨",
+ "🧂",
+ "🥪",
+ "🥘",
+ "🥙",
+ "🌮",
+ "🫔",
+ "🧇",
+ "🎂",
+ "🍬",
+ "🍫",
+ "🍪",
+ "🧁",
+ "🍮",
+ "🍩",
+ "🍯",
+ "🍨",
+ "🍭",
+ "🥧",
+ "🍧",
+ "🍰",
+ "🍦",
+ "🥑",
+ "🫑",
+ "🥦",
+ "🥕",
+ "🌰",
+ "🥒",
+ "🌽",
+ "🍆",
+ "🧄",
+ "🥬",
+ "🍄",
+ "🧅",
+ "🥜",
+ "🥔",
+ "📘",
+ "🔖",
+ "📑",
+ "📚",
+ "📕",
+ "📗",
+ "📒",
+ "📰",
+ "📓",
+ "📔",
+ "📖",
+ "📙",
+ "📄",
+ "📃",
+ "📜",
+ "🎒",
+ "🩰",
+ "👙",
+ "🧢",
+ "🩲",
+ "👝",
+ "🧥",
+ "👑",
+ "👗",
+ "🥿",
+ "💎",
+ "👓",
+ "🧤",
+ "🥽",
+ "🎓",
+ "👜",
+ "👠",
+ "🥾",
+ "👖",
+ "👘",
+ "🥼",
+ "💄",
+ "👞",
+ "🪖",
+ "👔",
+ "🩱",
+ "📿",
+ "👛",
+ "💍",
+ "👟",
+ "🦺",
+ "🥻",
+ "🧣",
+ "🩳",
+ "🧦",
+ "🩴",
+ "🎩",
+ "👕",
+ "👢",
+ "👚",
+ "👒",
+ "👡",
+ "🧮",
+ "🔋",
+ "💽",
+ "📀",
+ "🔌",
+ "💾",
+ "💻",
+ "💿",
+ "🧺",
+ "🛁",
+ "🧹",
+ "🪣",
+ "🪑",
+ "🚪",
+ "🛗",
+ "🧯",
+ "🧴",
+ "🪞",
+ "🪤",
+ "🪒",
+ "🧻",
+ "🧷",
+ "🛒",
+ "🚿",
+ "🧼",
+ "🧽",
+ "🚽",
+ "🪥",
+ "🪟",
+ "📷",
+ "📸",
+ "🎬",
+ "🪔",
+ "🔦",
+ "💡",
+ "🔍",
+ "🔎",
+ "🎥",
+ "🏮",
+ "📺",
+ "📼",
+ "📹",
+ "🔑",
+ "🔒",
+ "🔐",
+ "🔏",
+ "🔓",
+ "📪",
+ "📫",
+ "📩",
+ "📧",
+ "📥",
+ "📨",
+ "📭",
+ "📬",
+ "📤",
+ "📦",
+ "📮",
+ "🩹",
+ "🩸",
+ "💊",
+ "🩺",
+ "💉",
+ "💹",
+ "🪙",
+ "💳",
+ "💵",
+ "💶",
+ "💰",
+ "💸",
+ "💷",
+ "🧾",
+ "💴",
+ "🎧",
+ "🎤",
+ "🎵",
+ "🎶",
+ "🎼",
+ "📻",
+ "🪗",
+ "🪕",
+ "🥁",
+ "🎸",
+ "🪘",
+ "🎹",
+ "🎷",
+ "🎺",
+ "🎻",
+ "📊",
+ "💼",
+ "📅",
+ "📇",
+ "📉",
+ "📈",
+ "📋",
+ "📁",
+ "📂",
+ "📎",
+ "📌",
+ "📍",
+ "📏",
+ "📆",
+ "📐",
+ "🚬",
+ "🪦",
+ "🗿",
+ "🪧",
+ "📠",
+ "📱",
+ "📲",
+ "📟",
+ "📞",
+ "🧬",
+ "🔬",
+ "🧫",
+ "📡",
+ "🔭",
+ "🧪",
+ "🔔",
+ "🔕",
+ "📢",
+ "📣",
+ "🔇",
+ "📯",
+ "🔊",
+ "🔈",
+ "🔉",
+ "🪓",
+ "🪃",
+ "🏹",
+ "🪚",
+ "🔨",
+ "🪝",
+ "🪜",
+ "🔗",
+ "🧲",
+ "🔩",
+ "🪛",
+ "🧰",
+ "🔫",
+ "🦯",
+ "🔧",
+ "📝",
+ "🫀",
+ "🦴",
+ "🧠",
+ "👂",
+ "🦻",
+ "👀",
+ "💪",
+ "🦶",
+ "🦵",
+ "🫁",
+ "🦾",
+ "🦿",
+ "👄",
+ "👃",
+ "👅",
+ "🦷",
+ "🤛",
+ "👊",
+ "✊",
+ "🤜",
+ "👎",
+ "👍",
+ "🤚",
+ "✋",
+ "🖖",
+ "👋",
+ "🤙",
+ "🤞",
+ "🤟",
+ "👌",
+ "🤌",
+ "🤏",
+ "🤘",
+ "💅",
+ "🤳",
+ "👇",
+ "👈",
+ "👉",
+ "👆",
+ "🖕",
+ "👏",
+ "🙏",
+ "🤝",
+ "👐",
+ "🤲",
+ "🙌",
+ "👶",
+ "👦",
+ "🧒",
+ "👧",
+ "👨",
+ "🧓",
+ "👴",
+ "👵",
+ "🧑",
+ "🧔",
+ "👱",
+ "👩",
+ "🕺",
+ "👯",
+ "🧗",
+ "💇",
+ "💆",
+ "🧖",
+ "🧎",
+ "🏃",
+ "🧍",
+ "🚶",
+ "💃",
+ "👼",
+ "🧝",
+ "🧚",
+ "🧞",
+ "🧙",
+ "🧜",
+ "🤶",
+ "🎅",
+ "🦸",
+ "🦹",
+ "🧛",
+ "🧟",
+ "🧏",
+ "🙇",
+ "🤦",
+ "🙍",
+ "🙅",
+ "🙆",
+ "🙎",
+ "🙋",
+ "🤷",
+ "💁",
+ "🤱",
+ "👷",
+ "💂",
+ "🥷",
+ "🤵",
+ "👳",
+ "👲",
+ "👰",
+ "👮",
+ "🤰",
+ "🤴",
+ "👸",
+ "🧕",
+ "🏇",
+ "🤼",
+ "🚴",
+ "🤸",
+ "🤺",
+ "🤹",
+ "🚵",
+ "🤾",
+ "🤽",
+ "🚣",
+ "🏄",
+ "🏊",
+ "🏂",
+ "👥",
+ "👤",
+ "👣",
+ "🫂",
+ "💢",
+ "💓",
+ "🖤",
+ "💙",
+ "💣",
+ "💔",
+ "🤎",
+ "💥",
+ "💨",
+ "💫",
+ "💚",
+ "💗",
+ "💟",
+ "💘",
+ "💝",
+ "💯",
+ "💋",
+ "💌",
+ "🧡",
+ "💜",
+ "💞",
+ "💖",
+ "💬",
+ "💦",
+ "💭",
+ "💕",
+ "🤍",
+ "💛",
+ "💤",
+ "😘",
+ "😗",
+ "😚",
+ "😙",
+ "🥰",
+ "😍",
+ "🥲",
+ "🤩",
+ "😧",
+ "😰",
+ "😲",
+ "😖",
+ "😕",
+ "😢",
+ "😞",
+ "😓",
+ "😱",
+ "😮",
+ "😨",
+ "😳",
+ "😦",
+ "😯",
+ "😭",
+ "😣",
+ "🥺",
+ "😥",
+ "🙁",
+ "😫",
+ "😩",
+ "😟",
+ "🥱",
+ "👽",
+ "👾",
+ "🤡",
+ "👻",
+ "👺",
+ "👹",
+ "💩",
+ "🤖",
+ "🧐",
+ "🤓",
+ "😎",
+ "🤭",
+ "🤗",
+ "🤫",
+ "🤔",
+ "🤠",
+ "🥸",
+ "🥳",
+ "😠",
+ "👿",
+ "😤",
+ "🤬",
+ "😡",
+ "💀",
+ "😈",
+ "😑",
+ "😶",
+ "🤨",
+ "🙄",
+ "😬",
+ "🤥",
+ "😐",
+ "😏",
+ "😒",
+ "🤐",
+ "🤤",
+ "😔",
+ "😌",
+ "😴",
+ "😪",
+ "😁",
+ "😂",
+ "😀",
+ "😃",
+ "😄",
+ "😅",
+ "😆",
+ "🤣",
+ "🙂",
+ "😇",
+ "😊",
+ "🙃",
+ "😉",
+ "😋",
+ "😛",
+ "🤑",
+ "😝",
+ "😜",
+ "🤪",
+ "🥶",
+ "🤯",
+ "🤮",
+ "🤕",
+ "😷",
+ "🤒",
+ "🥵",
+ "😵",
+ "🤢",
+ "🤧",
+ "🥴",
+ "🆎",
+ "🆑",
+ "🆒",
+ "🆓",
+ "🆔",
+ "🔤",
+ "🔡",
+ "🔠",
+ "🔢",
+ "🔣",
+ "🉑",
+ "🈸",
+ "🉐",
+ "🈹",
+ "🈚",
+ "🈁",
+ "🈶",
+ "🈵",
+ "🈺",
+ "🈴",
+ "🈲",
+ "🈯",
+ "🈳",
+ "🆕",
+ "🆖",
+ "🆗",
+ "🆘",
+ "🆙",
+ "🆚",
+ "🔙",
+ "🔃",
+ "🔄",
+ "🔚",
+ "🔛",
+ "🔜",
+ "🔝",
+ "📶",
+ "🔆",
+ "🎦",
+ "🔅",
+ "🔽",
+ "⏬",
+ "⏩",
+ "⏪",
+ "⏫",
+ "📴",
+ "🔁",
+ "🔂",
+ "🔀",
+ "🔼",
+ "📳",
+ "💱",
+ "💲",
+ "➗",
+ "➖",
+ "➕",
+ "✅",
+ "❌",
+ "❎",
+ "➰",
+ "➿",
+ "⭕",
+ "🔰",
+ "📛",
+ "🔱",
+ "❗",
+ "❓",
+ "❕",
+ "❔",
+ "🏧",
+ "🚼",
+ "🛄",
+ "🛃",
+ "🛅",
+ "🚮",
+ "🚹",
+ "🛂",
+ "🚰",
+ "🚻",
+ "🚾",
+ "♿",
+ "🚺",
+ "🚸",
+ "🚱",
+ "🚳",
+ "⛔",
+ "🚯",
+ "📵",
+ "🔞",
+ "🚷",
+ "🚭",
+ "🚫",
+ "🏦",
+ "🧱",
+ "🏰",
+ "🏪",
+ "🏬",
+ "🏭",
+ "🏥",
+ "🏨",
+ "🏠",
+ "🏡",
+ "🛖",
+ "🏯",
+ "🏣",
+ "🏩",
+ "🏢",
+ "🏤",
+ "🪨",
+ "🏫",
+ "🗽",
+ "🗼",
+ "💒",
+ "🪵",
+ "🗻",
+ "🌋",
+ "💈",
+ "🌉",
+ "🎠",
+ "🎪",
+ "🌆",
+ "🎡",
+ "🌁",
+ "⛲",
+ "🌃",
+ "🎢",
+ "🌅",
+ "🌄",
+ "🌇",
+ "⛺",
+ "⛪",
+ "🛕",
+ "🕋",
+ "🕌",
+ "🕍",
+ "🌂",
+ "🌙",
+ "🌀",
+ "💧",
+ "🔥",
+ "🌓",
+ "🌛",
+ "🌕",
+ "🌝",
+ "🌟",
+ "⚡",
+ "🌗",
+ "🌜",
+ "🌌",
+ "🌑",
+ "🌚",
+ "🌈",
+ "🪐",
+ "🌠",
+ "⛄",
+ "⭐",
+ "⛅",
+ "🌞",
+ "☔",
+ "🌘",
+ "🌖",
+ "🌊",
+ "🌒",
+ "🌔",
+ "⏰",
+ "🕗",
+ "🕣",
+ "🕚",
+ "🕦",
+ "🕔",
+ "🕠",
+ "🕓",
+ "🕟",
+ "⌛",
+ "⏳",
+ "🕘",
+ "🕤",
+ "🕐",
+ "🕜",
+ "🕖",
+ "🕢",
+ "🕕",
+ "🕡",
+ "🕙",
+ "🕥",
+ "🕒",
+ "🕞",
+ "🕛",
+ "🕧",
+ "🕑",
+ "🕝",
+ "⌚",
+ "🚡",
+ "🛬",
+ "🛫",
+ "🛸",
+ "🚁",
+ "🚠",
+ "🪂",
+ "🚀",
+ "💺",
+ "🚟",
+ "🚑",
+ "🚛",
+ "🚗",
+ "🛺",
+ "🚲",
+ "🚅",
+ "🚌",
+ "🚏",
+ "🚧",
+ "🚚",
+ "🚒",
+ "⛽",
+ "🚄",
+ "🚥",
+ "🛴",
+ "🚈",
+ "🚂",
+ "🦽",
+ "🚇",
+ "🚐",
+ "🚝",
+ "🦼",
+ "🛵",
+ "🚞",
+ "🚘",
+ "🚍",
+ "🚔",
+ "🚖",
+ "🛻",
+ "🚓",
+ "🚨",
+ "🚃",
+ "🛼",
+ "🛹",
+ "🚙",
+ "🚉"
+ ];
+ }
+
+ // Number base
+ static get NUMBER_BASE() {
+
+ // Return number base
+ return Math.ceil(Math.log2(Emoji.GLYPHS["length"]));
+ }
+
+ // Extra bits value
+ static get EXTRA_BITS_VALUE() {
+
+ // Return extra bits value
+ return 0;
+ }
+}
+
+
+// Main function
+
+// Set global object's emoji
+globalThis["Emoji"] = Emoji;
diff --git a/scripts/extension.js b/scripts/extension.js
new file mode 100755
index 0000000..949f88b
--- /dev/null
+++ b/scripts/extension.js
@@ -0,0 +1,410 @@
+// Use strict
+"use strict";
+
+
+// Classes
+
+// Extension class
+class Extension {
+
+ // Public
+
+ // Initialize
+ static initialize() {
+
+ // Get URL parameters
+ var urlParameters = Common.getUrlParameters();
+
+ // Return promise
+ return new Promise(function(resolve, reject) {
+
+ // Check if URL parameters contains a request
+ if("Request" in urlParameters === true) {
+
+ // Set close when done
+ Extension.closeWhenDone = true;
+
+ // Try
+ try {
+
+ // Process URL parameters as a request
+ Extension.processRequest(urlParameters, true);
+ }
+
+ // Catch errors
+ catch(error) {
+
+ // Reject error
+ reject(error);
+
+ // Return
+ return;
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Clear close when done
+ Extension.closeWhenDone = false;
+ }
+
+ // Resolve
+ resolve();
+ });
+ }
+
+ // Get requests
+ static getRequests() {
+
+ // Return requests
+ return Extension.requests;
+ }
+
+ // Allow interrupt on close
+ static allowInterruptOnClose() {
+
+ // Set can interrupt close
+ Extension.canInterruptClose = true;
+ }
+
+ // Prevent interrupt on close
+ static preventInterruptOnClose() {
+
+ // Clear can interrupt close
+ Extension.canInterruptClose = false;
+ }
+
+ // Get close when done
+ static getCloseWhenDone() {
+
+ // Return close when done
+ return Extension.closeWhenDone;
+ }
+
+ // No transaction amount
+ static get NO_TRANSACTION_AMOUNT() {
+
+ // Return no transaction amount
+ return null;
+ }
+
+ // No transaction message
+ static get NO_TRANSACTION_MESSAGE() {
+
+ // Return no transation message
+ return null;
+ }
+
+ // Private
+
+ // Process request
+ static processRequest(request, makeFirstRequest = false) {
+
+ // Check request
+ switch(request["Request"]) {
+
+ // Start transaction
+ case "Start Transaction":
+
+ // Check if recipient address isn't valid
+ if("Recipient Address" in request === false || typeof request["Recipient Address"] !== "string" || request["Recipient Address"]["length"] === 0) {
+
+ // Throw error
+ throw Language.getDefaultTranslation('Invalid recipient address.');
+ }
+
+ // Otherwise check if amount isn't valid
+ else if("Amount" in request === true && request["Amount"] !== Extension.NO_TRANSACTION_AMOUNT && (Common.isNumberString(request["Amount"]) === false || Common.getNumberStringPrecision(request["Amount"]) > Extension.MAXIMUM_AMOUNT_PRECISION || parseFloat(Common.removeTrailingZeros(request["Amount"])) < Extension.MINIMUM_AMOUNT)) {
+
+ // Throw error
+ throw Language.getDefaultTranslation('Invalid amount.');
+ }
+
+ // Otherwise check if message isn't valid
+ else if("Message" in request === true && request["Message"] !== Extension.NO_TRANSACTION_MESSAGE && typeof request["Message"] !== "string") {
+
+ // Throw error
+ throw Language.getDefaultTranslation('Invalid message.');
+ }
+
+ // Otherwise
+ else {
+
+ // Get request information
+ var requestInformation = {
+
+ // Name
+ "Name": SendPaymentSection.NAME,
+
+ // State
+ "State": {
+
+ // Elements states
+ [Section.STATE_ELEMENTS_STATES_NAME]: [
+
+ // Back
+ {
+ "Tag": "BUTTON",
+ "Focused": false,
+ "Value": Section.NO_VALUE
+ },
+
+ // Forward
+ {
+ "Tag": "BUTTON",
+ "Focused": false,
+ "Value": Section.NO_VALUE
+ },
+
+ // Recipient address
+ {
+ "Tag": "INPUT",
+ "Focused": false,
+ "Value": ProtocolHandler.standardizeUrlProtocol(request["Recipient Address"]),
+ "Selection Start": Section.NO_VALUE,
+ "Selection End": Section.NO_VALUE,
+ "Selection Direction": Section.NO_VALUE
+ },
+
+ // Scan
+ {
+ "Tag": "BUTTON",
+ "Focused": false,
+ "Value": Section.NO_VALUE
+ },
+
+ // Wallet
+ {
+ "Tag": "SELECT",
+ "Focused": false,
+ "Value": Section.NO_VALUE
+ },
+
+ // Amount
+ {
+ "Tag": "INPUT",
+ "Focused": false,
+ "Value": ("Amount" in request === true && request["Amount"] !== Extension.NO_TRANSACTION_AMOUNT) ? (new BigNumber(request["Amount"])).toFixed() : Section.NO_VALUE,
+ "Selection Start": Section.NO_VALUE,
+ "Selection End": Section.NO_VALUE,
+ "Selection Direction": Section.NO_VALUE
+ },
+
+ // All
+ {
+ "Tag": "BUTTON",
+ "Focused": false,
+ "Value": Section.NO_VALUE
+ },
+
+ // Value
+ {
+ "Tag": "INPUT",
+ "Focused": false,
+ "Value": Section.NO_VALUE,
+ "Selection Start": Section.NO_VALUE,
+ "Selection End": Section.NO_VALUE,
+ "Selection Direction": Section.NO_VALUE
+ },
+
+ // Base fee
+ {
+ "Tag": "INPUT",
+ "Focused": false,
+ "Value": Section.NO_VALUE,
+ "Selection Start": Section.NO_VALUE,
+ "Selection End": Section.NO_VALUE,
+ "Selection Direction": Section.NO_VALUE
+ },
+
+ // Default base fee
+ {
+ "Tag": "BUTTON",
+ "Focused": false,
+ "Value": Section.NO_VALUE
+ },
+
+ // Message
+ {
+ "Tag": "INPUT",
+ "Focused": false,
+ "Value": ("Message" in request === true && request["Message"] !== Extension.NO_TRANSACTION_MESSAGE) ? request["Message"] : Section.NO_VALUE,
+ "Selection Start": Section.NO_VALUE,
+ "Selection End": Section.NO_VALUE,
+ "Selection Direction": Section.NO_VALUE
+ },
+
+ // Send
+ {
+ "Tag": "BUTTON",
+ "Focused": false,
+ "Value": Section.NO_VALUE
+ },
+
+ // Cancel
+ {
+ "Tag": "BUTTON",
+ "Focused": false,
+ "Value": Section.NO_VALUE
+ }
+ ]
+ }
+ };
+
+ // Check if making first request
+ if(makeFirstRequest === true) {
+
+ // Add request information to beginning of list
+ Extension.requests.unshift(requestInformation);
+ }
+
+ // Otherwise
+ else {
+
+ // Append request information to list
+ Extension.requests.push(requestInformation);
+ }
+
+ // Trigger extension request receive event
+ $(document).trigger(Extension.REQUEST_RECEIVE_EVENT);
+ }
+
+ // Break
+ break;
+
+ // Default
+ default:
+
+ // Throw error
+ throw Language.getDefaultTranslation('Invalid request.');
+ }
+ }
+
+ // Maximum amount precision
+ static get MAXIMUM_AMOUNT_PRECISION() {
+
+ // Return maximum amount precision
+ return Math.log10(Consensus.VALUE_NUMBER_BASE);
+ }
+
+ // Minimum amount
+ static get MINIMUM_AMOUNT() {
+
+ // Return minimum amount
+ return 1 / Consensus.VALUE_NUMBER_BASE;
+ }
+
+ // Request receive event
+ static get REQUEST_RECEIVE_EVENT() {
+
+ // Return request receive event
+ return "ExtensionRequestReceiveEvent";
+ }
+}
+
+
+// Main function
+
+// Set global object's extension
+globalThis["Extension"] = Extension;
+
+// Initialize extension requests
+Extension.requests = [];
+
+// Initialize extension can interrupt close
+Extension.canInterruptClose = false;
+
+// Window before unload event
+$(window).on("beforeunload", function(event) {
+
+ // Check if extension can interrupt close and extension requests exist
+ if(Extension.canInterruptClose === true && Extension.getRequests()["length"] !== 0) {
+
+ // Try
+ try {
+
+ // Prevent default
+ event.preventDefault();
+
+ // Stop propagation
+ event.stopPropagation();
+ event.stopImmediatePropagation();
+
+ // Check if one extension request exists
+ if(Extension.getRequests()["length"] === 1) {
+
+ // Return message
+ return event["originalEvent"]["returnValue"] = Language.getTranslation('Are you sure you want to exit? There\'s a remaining transaction.');
+ }
+
+ // Otherwise
+ else {
+
+ // Return message
+ return event["originalEvent"]["returnValue"] = Language.getTranslation('Are you sure you want to exit? There\'s remaining transactions.');
+ }
+ }
+
+ // Catch errors
+ catch(error) {
+
+ }
+ }
+});
+
+// Check if is a Firefox or Safari extension
+if(Common.isExtension() === true && typeof browser !== "undefined") {
+
+ // Message event
+ browser["runtime"]["onMessage"].addListener(function(request, sender, sendResponse) {
+
+ // Check if request is from the content script
+ if(sender["id"] === browser["runtime"]["id"] && "frameId" in sender === true && typeof request === "object" && request !== null && "Wallet Type" in request === true && "Network Type" in request === true && "Request" in request === true) {
+
+ // Check if request's wallet type is the current wallet type and network type is the current network type
+ if(request["Wallet Type"] === Consensus.walletTypeToText(Consensus.getWalletType()) && request["Network Type"] === Consensus.networkTypeToText(Consensus.getNetworkType())) {
+
+ // Try
+ try {
+
+ // Process extension request
+ Extension.processRequest(request);
+ }
+
+ // Catch errors
+ catch(error) {
+
+ }
+ }
+ }
+ });
+}
+
+// Otherwise check if is a Chrome extension
+else if(Common.isExtension() === true && typeof chrome !== "undefined") {
+
+ // Message event
+ chrome["runtime"]["onMessage"].addListener(function(request, sender, sendResponse) {
+
+ // Check if request is from the content script
+ if(sender["id"] === chrome["runtime"]["id"] && "frameId" in sender === true && typeof request === "object" && request !== null && "Wallet Type" in request === true && "Network Type" in request === true && "Request" in request === true) {
+
+ // Check if request's wallet type is the current wallet type and network type is the current network type
+ if(request["Wallet Type"] === Consensus.walletTypeToText(Consensus.getWalletType()) && request["Network Type"] === Consensus.networkTypeToText(Consensus.getNetworkType())) {
+
+ // Try
+ try {
+
+ // Process extension request
+ Extension.processRequest(request);
+ }
+
+ // Catch errors
+ catch(error) {
+
+ }
+ }
+ }
+ });
+}
diff --git a/scripts/fatal_error.js b/scripts/fatal_error.js
new file mode 100755
index 0000000..b922e89
--- /dev/null
+++ b/scripts/fatal_error.js
@@ -0,0 +1,123 @@
+// Use strict
+"use strict";
+
+
+// Classes
+
+// Fatal error class
+class FatalError {
+
+ // Public
+
+ // Constructor
+ constructor(errorType = FatalError.UNKNOWN_ERROR) {
+
+ // Check if a fatal error hasn't occurred
+ if(FatalError.errorOccurred === false) {
+
+ // Set that a fatal error occurred
+ FatalError.errorOccurred = true;
+
+ // Check error type
+ switch(errorType) {
+
+ // Local storage error
+ case FatalError.LOCAL_STORAGE_ERROR:
+
+ // Log error
+ console.log("A local storage error occurred.");
+
+ // Break
+ break;
+
+ // Database error
+ case FatalError.DATABASE_ERROR:
+
+ // Log error
+ console.log("A database error occurred.");
+
+ // Break
+ break;
+
+ // Unknown error and default
+ case FatalError.UNKNOWN_ERROR:
+ default:
+
+ // Log error
+ console.log("An unknown error occurred.");
+
+ // Break
+ break;
+ }
+
+ // Log stack trace
+ console.trace();
+
+ // Prevent extension from interrupting on close
+ Extension.preventInterruptOnClose();
+
+ // Check if not an extension and not loading from file
+ if(Common.isExtension() === false && location["protocol"] !== Common.FILE_PROTOCOL) {
+
+ // Go to error page
+ location.replace(((location["protocol"] === Common.HTTPS_PROTOCOL) ? Common.HTTPS_PROTOCOL : Common.HTTP_PROTOCOL) + "//" + location["hostname"] + FatalError.ERROR_PAGE_URL);
+ }
+
+ // Otherwise
+ else {
+
+ // Close
+ window.close();
+ }
+ }
+ }
+
+ // Local storage error
+ static get LOCAL_STORAGE_ERROR() {
+
+ // Return local storage error
+ return 0;
+ }
+
+ // Database error
+ static get DATABASE_ERROR() {
+
+ // Return database error
+ return FatalError.LOCAL_STORAGE_ERROR + 1;
+ }
+
+ // Unknown error
+ static get UNKNOWN_ERROR() {
+
+ // Return unknown error
+ return FatalError.DATABASE_ERROR + 1;
+ }
+
+ // Private
+
+ // Error page URL
+ static get ERROR_PAGE_URL() {
+
+ // Return error page URL
+ return "/errors/error.html";
+ }
+}
+
+
+// Main function
+
+// Set global object's fatal error
+globalThis["FatalError"] = FatalError;
+
+// Set fatal error error occurred
+FatalError.errorOccurred = false;
+
+// Window error event
+$(window).on("error", function() {
+
+ // Check if using application error handler
+ if(usingApplicationErrorHandler() === true)
+
+ // Trigger a fatal error
+ new FatalError(FatalError.UNKNOWN_ERROR);
+});
diff --git a/scripts/focus.js b/scripts/focus.js
new file mode 100755
index 0000000..b565f46
--- /dev/null
+++ b/scripts/focus.js
@@ -0,0 +1,240 @@
+// Use strict
+"use strict";
+
+
+// Classes
+
+// Focus class
+class Focus {
+
+ // Public
+
+ // Constructor
+ constructor() {
+
+ // Set focus stack to empty array
+ this.focusStack = [];
+
+ // Set self
+ var self = this;
+
+ // Document input focus event
+ $(document).on("focus", "input", function() {
+
+ // Get input
+ var input = $(this);
+
+ // Check if input isn't having its focus restored
+ if(input.hasClass("focused") === false) {
+
+ // Select input
+ input.select();
+ }
+ });
+
+ // Document key down event
+ $(document).on("keydown", function(event) {
+
+ // Check if is an extension and a popup or is an app
+ if((Common.isExtension() === true && Common.isPopup() === true) || Common.isApp() === true) {
+
+ // Check if key tab is pressed
+ if(event["which"] === "\t".charCodeAt(0)) {
+
+ // Get all focusable elements
+ var focusableElements = $("button:not([tabindex=\"-1\"]):not(:disabled):visible, input:not([tabindex=\"-1\"]):not(:disabled):visible, select:not([tabindex=\"-1\"]):not(:disabled):visible, a:not([tabindex=\"-1\"]):not(:disabled):visible").filter(function() {
+
+ // Return if not hidden
+ return $(this).closest(".hide")["length"] === 0;
+ });
+
+ // Check if elements are focusable
+ if(focusableElements["length"] !== 0) {
+
+ // Check if no element is focused
+ if($(":focus")["length"] === 0) {
+
+ // Check if tabbing forward
+ if(event["shiftKey"] === false) {
+
+ // Focus on first focusable element
+ focusableElements.first().focus();
+ }
+
+ // Otherwise
+ else {
+
+ // Focus on last first focusable element
+ focusableElements.last().focus();
+ }
+
+ // Prevent default;
+ event.preventDefault();
+ }
+
+ // Otherwise check if tabbing forward and focused on the last focusable element
+ else if(event["shiftKey"] === false && focusableElements.last().is(":focus") === true) {
+
+ // Check if other elements are focusable
+ if(focusableElements["length"] > 1) {
+
+ // Focus on first focusable element
+ focusableElements.first().focus();
+ }
+
+ // Prevent default;
+ event.preventDefault();
+ }
+
+ // Otherwise check if tabbing backward and focused on the first focusable element
+ else if(event["shiftKey"] === true && focusableElements.first().is(":focus") === true) {
+
+ // Check if other elements are focusable
+ if(focusableElements["length"] > 1) {
+
+ // Focus on last first focusable element
+ focusableElements.last().focus();
+ }
+
+ // Prevent default;
+ event.preventDefault();
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Prevent default;
+ event.preventDefault();
+ }
+ }
+ }
+ });
+ }
+
+ // Save
+ save(blur) {
+
+ // Get focused element
+ var focusedElement = $(":focus");
+
+ // Check if no elements are focused
+ if(focusedElement["length"] === 0) {
+
+ // Add no focus element to focus stack
+ this.focusStack.push(Focus.NO_FOCUS);
+
+ // Return no focus
+ return Focus.NO_FOCUS;
+ }
+
+ // Otherwise
+ else {
+
+ // Add focused element to the focus stack
+ this.focusStack.push(focusedElement.first());
+
+ // Check if blurring
+ if(blur === true) {
+
+ // Remove focus from element
+ focusedElement.blur();
+
+ // Remove selection
+ Focus.removeSelection();
+ }
+
+ // Return focused element
+ return focusedElement;
+ }
+ }
+
+ // Restore
+ restore(blurIfNoState) {
+
+ // Check if focus stack isn't empty
+ if(this.focusStack["length"] > 0) {
+
+ // Get focused element from the focus stack and remove it from the focus stack
+ var focusedElement = this.focusStack.pop();
+
+ // Check if focused element isn't no focus
+ if(focusedElement !== Focus.NO_FOCUS) {
+
+ // Set that focused element is having its focus restored
+ focusedElement.addClass("focused");
+
+ // Focus on focused element
+ focusedElement.focus();
+
+ // Set that focused element isn't having its focus restored
+ focusedElement.removeClass("focused");
+ }
+
+ // Otherwise check if blurring if no state
+ else if(blurIfNoState === true)
+
+ // Blur focused element
+ $(":focus").blur();
+ }
+ }
+
+ // Delete
+ delete() {
+
+ // Check if focus stack isn't empty
+ if(this.focusStack["length"] > 0)
+
+ // Remove next focused element from the focus stack
+ this.focusStack.pop();
+ }
+
+ // Delete all
+ deleteAll() {
+
+ // Set focus stack to empty array
+ this.focusStack = [];
+ }
+
+ // Remove selection
+ static removeSelection() {
+
+ // Check if get selection is supported
+ if(typeof getSelection !== "undefined") {
+
+ // Get current selection
+ var currentSelection = getSelection();
+
+ // Check if current selection empty is supported
+ if(typeof currentSelection.empty !== "undefined")
+
+ // Empty current selection
+ currentSelection.empty();
+
+ // Otherwise check if current selection remove all ranges is supported
+ else if(typeof currentSelection.removeAllRanges !== "undefined")
+
+ // Remove all ranges from the current selection
+ currentSelection.removeAllRanges();
+ }
+
+ // Otherwise check if selection is supported and selection empty is supported
+ else if(typeof document["selection"] !== "undefined" && typeof document["selection"].empty !== "undefined")
+
+ // Empty selection
+ document["selection"].empty();
+ }
+
+ // No focus
+ static get NO_FOCUS() {
+
+ // Return no focus
+ return null;
+ }
+}
+
+
+// Main function
+
+// Set global object's focus
+globalThis["Focus"] = Focus;
diff --git a/scripts/glMatrix license.txt b/scripts/glMatrix license.txt
new file mode 100755
index 0000000..3a96fca
--- /dev/null
+++ b/scripts/glMatrix license.txt
@@ -0,0 +1,19 @@
+Copyright (c) 2015-2021, Brandon Jones, Colin MacKenzie IV.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
\ No newline at end of file
diff --git a/scripts/glMatrix-3.4.1.js b/scripts/glMatrix-3.4.1.js
new file mode 100755
index 0000000..0ff67be
--- /dev/null
+++ b/scripts/glMatrix-3.4.1.js
@@ -0,0 +1,7860 @@
+
+/*!
+@fileoverview gl-matrix - High performance matrix and vector operations
+@author Brandon Jones
+@author Colin MacKenzie IV
+@version 3.4.1
+
+Copyright (c) 2015-2021, Brandon Jones, Colin MacKenzie IV.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+*/
+(function (global, factory) {
+ typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
+ typeof define === 'function' && define.amd ? define(['exports'], factory) :
+ (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.glMatrix = {}));
+})(this, (function (exports) { 'use strict';
+
+ /**
+ * Common utilities
+ * @module glMatrix
+ */
+ // Configuration Constants
+ var EPSILON = 0.000001;
+ var ARRAY_TYPE = typeof Float32Array !== "undefined" ? Float32Array : Array;
+ var RANDOM = Math.random;
+ var ANGLE_ORDER = "zyx";
+ /**
+ * Sets the type of array used when creating new vectors and matrices
+ *
+ * @param {Float32ArrayConstructor | ArrayConstructor} type Array type, such as Float32Array or Array
+ */
+
+ function setMatrixArrayType(type) {
+ ARRAY_TYPE = type;
+ }
+ var degree = Math.PI / 180;
+ /**
+ * Convert Degree To Radian
+ *
+ * @param {Number} a Angle in Degrees
+ */
+
+ function toRadian(a) {
+ return a * degree;
+ }
+ /**
+ * Tests whether or not the arguments have approximately the same value, within an absolute
+ * or relative tolerance of glMatrix.EPSILON (an absolute tolerance is used for values less
+ * than or equal to 1.0, and a relative tolerance is used for larger values)
+ *
+ * @param {Number} a The first number to test.
+ * @param {Number} b The second number to test.
+ * @returns {Boolean} True if the numbers are approximately equal, false otherwise.
+ */
+
+ function equals$9(a, b) {
+ return Math.abs(a - b) <= EPSILON * Math.max(1.0, Math.abs(a), Math.abs(b));
+ }
+ if (!Math.hypot) Math.hypot = function () {
+ var y = 0,
+ i = arguments.length;
+
+ while (i--) {
+ y += arguments[i] * arguments[i];
+ }
+
+ return Math.sqrt(y);
+ };
+
+ var common = /*#__PURE__*/Object.freeze({
+ __proto__: null,
+ EPSILON: EPSILON,
+ get ARRAY_TYPE () { return ARRAY_TYPE; },
+ RANDOM: RANDOM,
+ ANGLE_ORDER: ANGLE_ORDER,
+ setMatrixArrayType: setMatrixArrayType,
+ toRadian: toRadian,
+ equals: equals$9
+ });
+
+ /**
+ * 2x2 Matrix
+ * @module mat2
+ */
+
+ /**
+ * Creates a new identity mat2
+ *
+ * @returns {mat2} a new 2x2 matrix
+ */
+
+ function create$8() {
+ var out = new ARRAY_TYPE(4);
+
+ if (ARRAY_TYPE != Float32Array) {
+ out[1] = 0;
+ out[2] = 0;
+ }
+
+ out[0] = 1;
+ out[3] = 1;
+ return out;
+ }
+ /**
+ * Creates a new mat2 initialized with values from an existing matrix
+ *
+ * @param {ReadonlyMat2} a matrix to clone
+ * @returns {mat2} a new 2x2 matrix
+ */
+
+ function clone$8(a) {
+ var out = new ARRAY_TYPE(4);
+ out[0] = a[0];
+ out[1] = a[1];
+ out[2] = a[2];
+ out[3] = a[3];
+ return out;
+ }
+ /**
+ * Copy the values from one mat2 to another
+ *
+ * @param {mat2} out the receiving matrix
+ * @param {ReadonlyMat2} a the source matrix
+ * @returns {mat2} out
+ */
+
+ function copy$8(out, a) {
+ out[0] = a[0];
+ out[1] = a[1];
+ out[2] = a[2];
+ out[3] = a[3];
+ return out;
+ }
+ /**
+ * Set a mat2 to the identity matrix
+ *
+ * @param {mat2} out the receiving matrix
+ * @returns {mat2} out
+ */
+
+ function identity$5(out) {
+ out[0] = 1;
+ out[1] = 0;
+ out[2] = 0;
+ out[3] = 1;
+ return out;
+ }
+ /**
+ * Create a new mat2 with the given values
+ *
+ * @param {Number} m00 Component in column 0, row 0 position (index 0)
+ * @param {Number} m01 Component in column 0, row 1 position (index 1)
+ * @param {Number} m10 Component in column 1, row 0 position (index 2)
+ * @param {Number} m11 Component in column 1, row 1 position (index 3)
+ * @returns {mat2} out A new 2x2 matrix
+ */
+
+ function fromValues$8(m00, m01, m10, m11) {
+ var out = new ARRAY_TYPE(4);
+ out[0] = m00;
+ out[1] = m01;
+ out[2] = m10;
+ out[3] = m11;
+ return out;
+ }
+ /**
+ * Set the components of a mat2 to the given values
+ *
+ * @param {mat2} out the receiving matrix
+ * @param {Number} m00 Component in column 0, row 0 position (index 0)
+ * @param {Number} m01 Component in column 0, row 1 position (index 1)
+ * @param {Number} m10 Component in column 1, row 0 position (index 2)
+ * @param {Number} m11 Component in column 1, row 1 position (index 3)
+ * @returns {mat2} out
+ */
+
+ function set$8(out, m00, m01, m10, m11) {
+ out[0] = m00;
+ out[1] = m01;
+ out[2] = m10;
+ out[3] = m11;
+ return out;
+ }
+ /**
+ * Transpose the values of a mat2
+ *
+ * @param {mat2} out the receiving matrix
+ * @param {ReadonlyMat2} a the source matrix
+ * @returns {mat2} out
+ */
+
+ function transpose$2(out, a) {
+ // If we are transposing ourselves we can skip a few steps but have to cache
+ // some values
+ if (out === a) {
+ var a1 = a[1];
+ out[1] = a[2];
+ out[2] = a1;
+ } else {
+ out[0] = a[0];
+ out[1] = a[2];
+ out[2] = a[1];
+ out[3] = a[3];
+ }
+
+ return out;
+ }
+ /**
+ * Inverts a mat2
+ *
+ * @param {mat2} out the receiving matrix
+ * @param {ReadonlyMat2} a the source matrix
+ * @returns {mat2} out
+ */
+
+ function invert$5(out, a) {
+ var a0 = a[0],
+ a1 = a[1],
+ a2 = a[2],
+ a3 = a[3]; // Calculate the determinant
+
+ var det = a0 * a3 - a2 * a1;
+
+ if (!det) {
+ return null;
+ }
+
+ det = 1.0 / det;
+ out[0] = a3 * det;
+ out[1] = -a1 * det;
+ out[2] = -a2 * det;
+ out[3] = a0 * det;
+ return out;
+ }
+ /**
+ * Calculates the adjugate of a mat2
+ *
+ * @param {mat2} out the receiving matrix
+ * @param {ReadonlyMat2} a the source matrix
+ * @returns {mat2} out
+ */
+
+ function adjoint$2(out, a) {
+ // Caching this value is necessary if out == a
+ var a0 = a[0];
+ out[0] = a[3];
+ out[1] = -a[1];
+ out[2] = -a[2];
+ out[3] = a0;
+ return out;
+ }
+ /**
+ * Calculates the determinant of a mat2
+ *
+ * @param {ReadonlyMat2} a the source matrix
+ * @returns {Number} determinant of a
+ */
+
+ function determinant$3(a) {
+ return a[0] * a[3] - a[2] * a[1];
+ }
+ /**
+ * Multiplies two mat2's
+ *
+ * @param {mat2} out the receiving matrix
+ * @param {ReadonlyMat2} a the first operand
+ * @param {ReadonlyMat2} b the second operand
+ * @returns {mat2} out
+ */
+
+ function multiply$8(out, a, b) {
+ var a0 = a[0],
+ a1 = a[1],
+ a2 = a[2],
+ a3 = a[3];
+ var b0 = b[0],
+ b1 = b[1],
+ b2 = b[2],
+ b3 = b[3];
+ out[0] = a0 * b0 + a2 * b1;
+ out[1] = a1 * b0 + a3 * b1;
+ out[2] = a0 * b2 + a2 * b3;
+ out[3] = a1 * b2 + a3 * b3;
+ return out;
+ }
+ /**
+ * Rotates a mat2 by the given angle
+ *
+ * @param {mat2} out the receiving matrix
+ * @param {ReadonlyMat2} a the matrix to rotate
+ * @param {Number} rad the angle to rotate the matrix by
+ * @returns {mat2} out
+ */
+
+ function rotate$4(out, a, rad) {
+ var a0 = a[0],
+ a1 = a[1],
+ a2 = a[2],
+ a3 = a[3];
+ var s = Math.sin(rad);
+ var c = Math.cos(rad);
+ out[0] = a0 * c + a2 * s;
+ out[1] = a1 * c + a3 * s;
+ out[2] = a0 * -s + a2 * c;
+ out[3] = a1 * -s + a3 * c;
+ return out;
+ }
+ /**
+ * Scales the mat2 by the dimensions in the given vec2
+ *
+ * @param {mat2} out the receiving matrix
+ * @param {ReadonlyMat2} a the matrix to rotate
+ * @param {ReadonlyVec2} v the vec2 to scale the matrix by
+ * @returns {mat2} out
+ **/
+
+ function scale$8(out, a, v) {
+ var a0 = a[0],
+ a1 = a[1],
+ a2 = a[2],
+ a3 = a[3];
+ var v0 = v[0],
+ v1 = v[1];
+ out[0] = a0 * v0;
+ out[1] = a1 * v0;
+ out[2] = a2 * v1;
+ out[3] = a3 * v1;
+ return out;
+ }
+ /**
+ * Creates a matrix from a given angle
+ * This is equivalent to (but much faster than):
+ *
+ * mat2.identity(dest);
+ * mat2.rotate(dest, dest, rad);
+ *
+ * @param {mat2} out mat2 receiving operation result
+ * @param {Number} rad the angle to rotate the matrix by
+ * @returns {mat2} out
+ */
+
+ function fromRotation$4(out, rad) {
+ var s = Math.sin(rad);
+ var c = Math.cos(rad);
+ out[0] = c;
+ out[1] = s;
+ out[2] = -s;
+ out[3] = c;
+ return out;
+ }
+ /**
+ * Creates a matrix from a vector scaling
+ * This is equivalent to (but much faster than):
+ *
+ * mat2.identity(dest);
+ * mat2.scale(dest, dest, vec);
+ *
+ * @param {mat2} out mat2 receiving operation result
+ * @param {ReadonlyVec2} v Scaling vector
+ * @returns {mat2} out
+ */
+
+ function fromScaling$3(out, v) {
+ out[0] = v[0];
+ out[1] = 0;
+ out[2] = 0;
+ out[3] = v[1];
+ return out;
+ }
+ /**
+ * Returns a string representation of a mat2
+ *
+ * @param {ReadonlyMat2} a matrix to represent as a string
+ * @returns {String} string representation of the matrix
+ */
+
+ function str$8(a) {
+ return "mat2(" + a[0] + ", " + a[1] + ", " + a[2] + ", " + a[3] + ")";
+ }
+ /**
+ * Returns Frobenius norm of a mat2
+ *
+ * @param {ReadonlyMat2} a the matrix to calculate Frobenius norm of
+ * @returns {Number} Frobenius norm
+ */
+
+ function frob$3(a) {
+ return Math.hypot(a[0], a[1], a[2], a[3]);
+ }
+ /**
+ * Returns L, D and U matrices (Lower triangular, Diagonal and Upper triangular) by factorizing the input matrix
+ * @param {ReadonlyMat2} L the lower triangular matrix
+ * @param {ReadonlyMat2} D the diagonal matrix
+ * @param {ReadonlyMat2} U the upper triangular matrix
+ * @param {ReadonlyMat2} a the input matrix to factorize
+ */
+
+ function LDU(L, D, U, a) {
+ L[2] = a[2] / a[0];
+ U[0] = a[0];
+ U[1] = a[1];
+ U[3] = a[3] - L[2] * U[1];
+ return [L, D, U];
+ }
+ /**
+ * Adds two mat2's
+ *
+ * @param {mat2} out the receiving matrix
+ * @param {ReadonlyMat2} a the first operand
+ * @param {ReadonlyMat2} b the second operand
+ * @returns {mat2} out
+ */
+
+ function add$8(out, a, b) {
+ out[0] = a[0] + b[0];
+ out[1] = a[1] + b[1];
+ out[2] = a[2] + b[2];
+ out[3] = a[3] + b[3];
+ return out;
+ }
+ /**
+ * Subtracts matrix b from matrix a
+ *
+ * @param {mat2} out the receiving matrix
+ * @param {ReadonlyMat2} a the first operand
+ * @param {ReadonlyMat2} b the second operand
+ * @returns {mat2} out
+ */
+
+ function subtract$6(out, a, b) {
+ out[0] = a[0] - b[0];
+ out[1] = a[1] - b[1];
+ out[2] = a[2] - b[2];
+ out[3] = a[3] - b[3];
+ return out;
+ }
+ /**
+ * Returns whether or not the matrices have exactly the same elements in the same position (when compared with ===)
+ *
+ * @param {ReadonlyMat2} a The first matrix.
+ * @param {ReadonlyMat2} b The second matrix.
+ * @returns {Boolean} True if the matrices are equal, false otherwise.
+ */
+
+ function exactEquals$8(a, b) {
+ return a[0] === b[0] && a[1] === b[1] && a[2] === b[2] && a[3] === b[3];
+ }
+ /**
+ * Returns whether or not the matrices have approximately the same elements in the same position.
+ *
+ * @param {ReadonlyMat2} a The first matrix.
+ * @param {ReadonlyMat2} b The second matrix.
+ * @returns {Boolean} True if the matrices are equal, false otherwise.
+ */
+
+ function equals$8(a, b) {
+ var a0 = a[0],
+ a1 = a[1],
+ a2 = a[2],
+ a3 = a[3];
+ var b0 = b[0],
+ b1 = b[1],
+ b2 = b[2],
+ b3 = b[3];
+ return Math.abs(a0 - b0) <= EPSILON * Math.max(1.0, Math.abs(a0), Math.abs(b0)) && Math.abs(a1 - b1) <= EPSILON * Math.max(1.0, Math.abs(a1), Math.abs(b1)) && Math.abs(a2 - b2) <= EPSILON * Math.max(1.0, Math.abs(a2), Math.abs(b2)) && Math.abs(a3 - b3) <= EPSILON * Math.max(1.0, Math.abs(a3), Math.abs(b3));
+ }
+ /**
+ * Multiply each element of the matrix by a scalar.
+ *
+ * @param {mat2} out the receiving matrix
+ * @param {ReadonlyMat2} a the matrix to scale
+ * @param {Number} b amount to scale the matrix's elements by
+ * @returns {mat2} out
+ */
+
+ function multiplyScalar$3(out, a, b) {
+ out[0] = a[0] * b;
+ out[1] = a[1] * b;
+ out[2] = a[2] * b;
+ out[3] = a[3] * b;
+ return out;
+ }
+ /**
+ * Adds two mat2's after multiplying each element of the second operand by a scalar value.
+ *
+ * @param {mat2} out the receiving vector
+ * @param {ReadonlyMat2} a the first operand
+ * @param {ReadonlyMat2} b the second operand
+ * @param {Number} scale the amount to scale b's elements by before adding
+ * @returns {mat2} out
+ */
+
+ function multiplyScalarAndAdd$3(out, a, b, scale) {
+ out[0] = a[0] + b[0] * scale;
+ out[1] = a[1] + b[1] * scale;
+ out[2] = a[2] + b[2] * scale;
+ out[3] = a[3] + b[3] * scale;
+ return out;
+ }
+ /**
+ * Alias for {@link mat2.multiply}
+ * @function
+ */
+
+ var mul$8 = multiply$8;
+ /**
+ * Alias for {@link mat2.subtract}
+ * @function
+ */
+
+ var sub$6 = subtract$6;
+
+ var mat2 = /*#__PURE__*/Object.freeze({
+ __proto__: null,
+ create: create$8,
+ clone: clone$8,
+ copy: copy$8,
+ identity: identity$5,
+ fromValues: fromValues$8,
+ set: set$8,
+ transpose: transpose$2,
+ invert: invert$5,
+ adjoint: adjoint$2,
+ determinant: determinant$3,
+ multiply: multiply$8,
+ rotate: rotate$4,
+ scale: scale$8,
+ fromRotation: fromRotation$4,
+ fromScaling: fromScaling$3,
+ str: str$8,
+ frob: frob$3,
+ LDU: LDU,
+ add: add$8,
+ subtract: subtract$6,
+ exactEquals: exactEquals$8,
+ equals: equals$8,
+ multiplyScalar: multiplyScalar$3,
+ multiplyScalarAndAdd: multiplyScalarAndAdd$3,
+ mul: mul$8,
+ sub: sub$6
+ });
+
+ /**
+ * 2x3 Matrix
+ * @module mat2d
+ * @description
+ * A mat2d contains six elements defined as:
+ *
+ * [a, b,
+ * c, d,
+ * tx, ty]
+ *
+ * This is a short form for the 3x3 matrix:
+ *
+ * [a, b, 0,
+ * c, d, 0,
+ * tx, ty, 1]
+ *
+ * The last column is ignored so the array is shorter and operations are faster.
+ */
+
+ /**
+ * Creates a new identity mat2d
+ *
+ * @returns {mat2d} a new 2x3 matrix
+ */
+
+ function create$7() {
+ var out = new ARRAY_TYPE(6);
+
+ if (ARRAY_TYPE != Float32Array) {
+ out[1] = 0;
+ out[2] = 0;
+ out[4] = 0;
+ out[5] = 0;
+ }
+
+ out[0] = 1;
+ out[3] = 1;
+ return out;
+ }
+ /**
+ * Creates a new mat2d initialized with values from an existing matrix
+ *
+ * @param {ReadonlyMat2d} a matrix to clone
+ * @returns {mat2d} a new 2x3 matrix
+ */
+
+ function clone$7(a) {
+ var out = new ARRAY_TYPE(6);
+ out[0] = a[0];
+ out[1] = a[1];
+ out[2] = a[2];
+ out[3] = a[3];
+ out[4] = a[4];
+ out[5] = a[5];
+ return out;
+ }
+ /**
+ * Copy the values from one mat2d to another
+ *
+ * @param {mat2d} out the receiving matrix
+ * @param {ReadonlyMat2d} a the source matrix
+ * @returns {mat2d} out
+ */
+
+ function copy$7(out, a) {
+ out[0] = a[0];
+ out[1] = a[1];
+ out[2] = a[2];
+ out[3] = a[3];
+ out[4] = a[4];
+ out[5] = a[5];
+ return out;
+ }
+ /**
+ * Set a mat2d to the identity matrix
+ *
+ * @param {mat2d} out the receiving matrix
+ * @returns {mat2d} out
+ */
+
+ function identity$4(out) {
+ out[0] = 1;
+ out[1] = 0;
+ out[2] = 0;
+ out[3] = 1;
+ out[4] = 0;
+ out[5] = 0;
+ return out;
+ }
+ /**
+ * Create a new mat2d with the given values
+ *
+ * @param {Number} a Component A (index 0)
+ * @param {Number} b Component B (index 1)
+ * @param {Number} c Component C (index 2)
+ * @param {Number} d Component D (index 3)
+ * @param {Number} tx Component TX (index 4)
+ * @param {Number} ty Component TY (index 5)
+ * @returns {mat2d} A new mat2d
+ */
+
+ function fromValues$7(a, b, c, d, tx, ty) {
+ var out = new ARRAY_TYPE(6);
+ out[0] = a;
+ out[1] = b;
+ out[2] = c;
+ out[3] = d;
+ out[4] = tx;
+ out[5] = ty;
+ return out;
+ }
+ /**
+ * Set the components of a mat2d to the given values
+ *
+ * @param {mat2d} out the receiving matrix
+ * @param {Number} a Component A (index 0)
+ * @param {Number} b Component B (index 1)
+ * @param {Number} c Component C (index 2)
+ * @param {Number} d Component D (index 3)
+ * @param {Number} tx Component TX (index 4)
+ * @param {Number} ty Component TY (index 5)
+ * @returns {mat2d} out
+ */
+
+ function set$7(out, a, b, c, d, tx, ty) {
+ out[0] = a;
+ out[1] = b;
+ out[2] = c;
+ out[3] = d;
+ out[4] = tx;
+ out[5] = ty;
+ return out;
+ }
+ /**
+ * Inverts a mat2d
+ *
+ * @param {mat2d} out the receiving matrix
+ * @param {ReadonlyMat2d} a the source matrix
+ * @returns {mat2d} out
+ */
+
+ function invert$4(out, a) {
+ var aa = a[0],
+ ab = a[1],
+ ac = a[2],
+ ad = a[3];
+ var atx = a[4],
+ aty = a[5];
+ var det = aa * ad - ab * ac;
+
+ if (!det) {
+ return null;
+ }
+
+ det = 1.0 / det;
+ out[0] = ad * det;
+ out[1] = -ab * det;
+ out[2] = -ac * det;
+ out[3] = aa * det;
+ out[4] = (ac * aty - ad * atx) * det;
+ out[5] = (ab * atx - aa * aty) * det;
+ return out;
+ }
+ /**
+ * Calculates the determinant of a mat2d
+ *
+ * @param {ReadonlyMat2d} a the source matrix
+ * @returns {Number} determinant of a
+ */
+
+ function determinant$2(a) {
+ return a[0] * a[3] - a[1] * a[2];
+ }
+ /**
+ * Multiplies two mat2d's
+ *
+ * @param {mat2d} out the receiving matrix
+ * @param {ReadonlyMat2d} a the first operand
+ * @param {ReadonlyMat2d} b the second operand
+ * @returns {mat2d} out
+ */
+
+ function multiply$7(out, a, b) {
+ var a0 = a[0],
+ a1 = a[1],
+ a2 = a[2],
+ a3 = a[3],
+ a4 = a[4],
+ a5 = a[5];
+ var b0 = b[0],
+ b1 = b[1],
+ b2 = b[2],
+ b3 = b[3],
+ b4 = b[4],
+ b5 = b[5];
+ out[0] = a0 * b0 + a2 * b1;
+ out[1] = a1 * b0 + a3 * b1;
+ out[2] = a0 * b2 + a2 * b3;
+ out[3] = a1 * b2 + a3 * b3;
+ out[4] = a0 * b4 + a2 * b5 + a4;
+ out[5] = a1 * b4 + a3 * b5 + a5;
+ return out;
+ }
+ /**
+ * Rotates a mat2d by the given angle
+ *
+ * @param {mat2d} out the receiving matrix
+ * @param {ReadonlyMat2d} a the matrix to rotate
+ * @param {Number} rad the angle to rotate the matrix by
+ * @returns {mat2d} out
+ */
+
+ function rotate$3(out, a, rad) {
+ var a0 = a[0],
+ a1 = a[1],
+ a2 = a[2],
+ a3 = a[3],
+ a4 = a[4],
+ a5 = a[5];
+ var s = Math.sin(rad);
+ var c = Math.cos(rad);
+ out[0] = a0 * c + a2 * s;
+ out[1] = a1 * c + a3 * s;
+ out[2] = a0 * -s + a2 * c;
+ out[3] = a1 * -s + a3 * c;
+ out[4] = a4;
+ out[5] = a5;
+ return out;
+ }
+ /**
+ * Scales the mat2d by the dimensions in the given vec2
+ *
+ * @param {mat2d} out the receiving matrix
+ * @param {ReadonlyMat2d} a the matrix to translate
+ * @param {ReadonlyVec2} v the vec2 to scale the matrix by
+ * @returns {mat2d} out
+ **/
+
+ function scale$7(out, a, v) {
+ var a0 = a[0],
+ a1 = a[1],
+ a2 = a[2],
+ a3 = a[3],
+ a4 = a[4],
+ a5 = a[5];
+ var v0 = v[0],
+ v1 = v[1];
+ out[0] = a0 * v0;
+ out[1] = a1 * v0;
+ out[2] = a2 * v1;
+ out[3] = a3 * v1;
+ out[4] = a4;
+ out[5] = a5;
+ return out;
+ }
+ /**
+ * Translates the mat2d by the dimensions in the given vec2
+ *
+ * @param {mat2d} out the receiving matrix
+ * @param {ReadonlyMat2d} a the matrix to translate
+ * @param {ReadonlyVec2} v the vec2 to translate the matrix by
+ * @returns {mat2d} out
+ **/
+
+ function translate$3(out, a, v) {
+ var a0 = a[0],
+ a1 = a[1],
+ a2 = a[2],
+ a3 = a[3],
+ a4 = a[4],
+ a5 = a[5];
+ var v0 = v[0],
+ v1 = v[1];
+ out[0] = a0;
+ out[1] = a1;
+ out[2] = a2;
+ out[3] = a3;
+ out[4] = a0 * v0 + a2 * v1 + a4;
+ out[5] = a1 * v0 + a3 * v1 + a5;
+ return out;
+ }
+ /**
+ * Creates a matrix from a given angle
+ * This is equivalent to (but much faster than):
+ *
+ * mat2d.identity(dest);
+ * mat2d.rotate(dest, dest, rad);
+ *
+ * @param {mat2d} out mat2d receiving operation result
+ * @param {Number} rad the angle to rotate the matrix by
+ * @returns {mat2d} out
+ */
+
+ function fromRotation$3(out, rad) {
+ var s = Math.sin(rad),
+ c = Math.cos(rad);
+ out[0] = c;
+ out[1] = s;
+ out[2] = -s;
+ out[3] = c;
+ out[4] = 0;
+ out[5] = 0;
+ return out;
+ }
+ /**
+ * Creates a matrix from a vector scaling
+ * This is equivalent to (but much faster than):
+ *
+ * mat2d.identity(dest);
+ * mat2d.scale(dest, dest, vec);
+ *
+ * @param {mat2d} out mat2d receiving operation result
+ * @param {ReadonlyVec2} v Scaling vector
+ * @returns {mat2d} out
+ */
+
+ function fromScaling$2(out, v) {
+ out[0] = v[0];
+ out[1] = 0;
+ out[2] = 0;
+ out[3] = v[1];
+ out[4] = 0;
+ out[5] = 0;
+ return out;
+ }
+ /**
+ * Creates a matrix from a vector translation
+ * This is equivalent to (but much faster than):
+ *
+ * mat2d.identity(dest);
+ * mat2d.translate(dest, dest, vec);
+ *
+ * @param {mat2d} out mat2d receiving operation result
+ * @param {ReadonlyVec2} v Translation vector
+ * @returns {mat2d} out
+ */
+
+ function fromTranslation$3(out, v) {
+ out[0] = 1;
+ out[1] = 0;
+ out[2] = 0;
+ out[3] = 1;
+ out[4] = v[0];
+ out[5] = v[1];
+ return out;
+ }
+ /**
+ * Returns a string representation of a mat2d
+ *
+ * @param {ReadonlyMat2d} a matrix to represent as a string
+ * @returns {String} string representation of the matrix
+ */
+
+ function str$7(a) {
+ return "mat2d(" + a[0] + ", " + a[1] + ", " + a[2] + ", " + a[3] + ", " + a[4] + ", " + a[5] + ")";
+ }
+ /**
+ * Returns Frobenius norm of a mat2d
+ *
+ * @param {ReadonlyMat2d} a the matrix to calculate Frobenius norm of
+ * @returns {Number} Frobenius norm
+ */
+
+ function frob$2(a) {
+ return Math.hypot(a[0], a[1], a[2], a[3], a[4], a[5], 1);
+ }
+ /**
+ * Adds two mat2d's
+ *
+ * @param {mat2d} out the receiving matrix
+ * @param {ReadonlyMat2d} a the first operand
+ * @param {ReadonlyMat2d} b the second operand
+ * @returns {mat2d} out
+ */
+
+ function add$7(out, a, b) {
+ out[0] = a[0] + b[0];
+ out[1] = a[1] + b[1];
+ out[2] = a[2] + b[2];
+ out[3] = a[3] + b[3];
+ out[4] = a[4] + b[4];
+ out[5] = a[5] + b[5];
+ return out;
+ }
+ /**
+ * Subtracts matrix b from matrix a
+ *
+ * @param {mat2d} out the receiving matrix
+ * @param {ReadonlyMat2d} a the first operand
+ * @param {ReadonlyMat2d} b the second operand
+ * @returns {mat2d} out
+ */
+
+ function subtract$5(out, a, b) {
+ out[0] = a[0] - b[0];
+ out[1] = a[1] - b[1];
+ out[2] = a[2] - b[2];
+ out[3] = a[3] - b[3];
+ out[4] = a[4] - b[4];
+ out[5] = a[5] - b[5];
+ return out;
+ }
+ /**
+ * Multiply each element of the matrix by a scalar.
+ *
+ * @param {mat2d} out the receiving matrix
+ * @param {ReadonlyMat2d} a the matrix to scale
+ * @param {Number} b amount to scale the matrix's elements by
+ * @returns {mat2d} out
+ */
+
+ function multiplyScalar$2(out, a, b) {
+ out[0] = a[0] * b;
+ out[1] = a[1] * b;
+ out[2] = a[2] * b;
+ out[3] = a[3] * b;
+ out[4] = a[4] * b;
+ out[5] = a[5] * b;
+ return out;
+ }
+ /**
+ * Adds two mat2d's after multiplying each element of the second operand by a scalar value.
+ *
+ * @param {mat2d} out the receiving vector
+ * @param {ReadonlyMat2d} a the first operand
+ * @param {ReadonlyMat2d} b the second operand
+ * @param {Number} scale the amount to scale b's elements by before adding
+ * @returns {mat2d} out
+ */
+
+ function multiplyScalarAndAdd$2(out, a, b, scale) {
+ out[0] = a[0] + b[0] * scale;
+ out[1] = a[1] + b[1] * scale;
+ out[2] = a[2] + b[2] * scale;
+ out[3] = a[3] + b[3] * scale;
+ out[4] = a[4] + b[4] * scale;
+ out[5] = a[5] + b[5] * scale;
+ return out;
+ }
+ /**
+ * Returns whether or not the matrices have exactly the same elements in the same position (when compared with ===)
+ *
+ * @param {ReadonlyMat2d} a The first matrix.
+ * @param {ReadonlyMat2d} b The second matrix.
+ * @returns {Boolean} True if the matrices are equal, false otherwise.
+ */
+
+ function exactEquals$7(a, b) {
+ return a[0] === b[0] && a[1] === b[1] && a[2] === b[2] && a[3] === b[3] && a[4] === b[4] && a[5] === b[5];
+ }
+ /**
+ * Returns whether or not the matrices have approximately the same elements in the same position.
+ *
+ * @param {ReadonlyMat2d} a The first matrix.
+ * @param {ReadonlyMat2d} b The second matrix.
+ * @returns {Boolean} True if the matrices are equal, false otherwise.
+ */
+
+ function equals$7(a, b) {
+ var a0 = a[0],
+ a1 = a[1],
+ a2 = a[2],
+ a3 = a[3],
+ a4 = a[4],
+ a5 = a[5];
+ var b0 = b[0],
+ b1 = b[1],
+ b2 = b[2],
+ b3 = b[3],
+ b4 = b[4],
+ b5 = b[5];
+ return Math.abs(a0 - b0) <= EPSILON * Math.max(1.0, Math.abs(a0), Math.abs(b0)) && Math.abs(a1 - b1) <= EPSILON * Math.max(1.0, Math.abs(a1), Math.abs(b1)) && Math.abs(a2 - b2) <= EPSILON * Math.max(1.0, Math.abs(a2), Math.abs(b2)) && Math.abs(a3 - b3) <= EPSILON * Math.max(1.0, Math.abs(a3), Math.abs(b3)) && Math.abs(a4 - b4) <= EPSILON * Math.max(1.0, Math.abs(a4), Math.abs(b4)) && Math.abs(a5 - b5) <= EPSILON * Math.max(1.0, Math.abs(a5), Math.abs(b5));
+ }
+ /**
+ * Alias for {@link mat2d.multiply}
+ * @function
+ */
+
+ var mul$7 = multiply$7;
+ /**
+ * Alias for {@link mat2d.subtract}
+ * @function
+ */
+
+ var sub$5 = subtract$5;
+
+ var mat2d = /*#__PURE__*/Object.freeze({
+ __proto__: null,
+ create: create$7,
+ clone: clone$7,
+ copy: copy$7,
+ identity: identity$4,
+ fromValues: fromValues$7,
+ set: set$7,
+ invert: invert$4,
+ determinant: determinant$2,
+ multiply: multiply$7,
+ rotate: rotate$3,
+ scale: scale$7,
+ translate: translate$3,
+ fromRotation: fromRotation$3,
+ fromScaling: fromScaling$2,
+ fromTranslation: fromTranslation$3,
+ str: str$7,
+ frob: frob$2,
+ add: add$7,
+ subtract: subtract$5,
+ multiplyScalar: multiplyScalar$2,
+ multiplyScalarAndAdd: multiplyScalarAndAdd$2,
+ exactEquals: exactEquals$7,
+ equals: equals$7,
+ mul: mul$7,
+ sub: sub$5
+ });
+
+ /**
+ * 3x3 Matrix
+ * @module mat3
+ */
+
+ /**
+ * Creates a new identity mat3
+ *
+ * @returns {mat3} a new 3x3 matrix
+ */
+
+ function create$6() {
+ var out = new ARRAY_TYPE(9);
+
+ if (ARRAY_TYPE != Float32Array) {
+ out[1] = 0;
+ out[2] = 0;
+ out[3] = 0;
+ out[5] = 0;
+ out[6] = 0;
+ out[7] = 0;
+ }
+
+ out[0] = 1;
+ out[4] = 1;
+ out[8] = 1;
+ return out;
+ }
+ /**
+ * Copies the upper-left 3x3 values into the given mat3.
+ *
+ * @param {mat3} out the receiving 3x3 matrix
+ * @param {ReadonlyMat4} a the source 4x4 matrix
+ * @returns {mat3} out
+ */
+
+ function fromMat4$1(out, a) {
+ out[0] = a[0];
+ out[1] = a[1];
+ out[2] = a[2];
+ out[3] = a[4];
+ out[4] = a[5];
+ out[5] = a[6];
+ out[6] = a[8];
+ out[7] = a[9];
+ out[8] = a[10];
+ return out;
+ }
+ /**
+ * Creates a new mat3 initialized with values from an existing matrix
+ *
+ * @param {ReadonlyMat3} a matrix to clone
+ * @returns {mat3} a new 3x3 matrix
+ */
+
+ function clone$6(a) {
+ var out = new ARRAY_TYPE(9);
+ out[0] = a[0];
+ out[1] = a[1];
+ out[2] = a[2];
+ out[3] = a[3];
+ out[4] = a[4];
+ out[5] = a[5];
+ out[6] = a[6];
+ out[7] = a[7];
+ out[8] = a[8];
+ return out;
+ }
+ /**
+ * Copy the values from one mat3 to another
+ *
+ * @param {mat3} out the receiving matrix
+ * @param {ReadonlyMat3} a the source matrix
+ * @returns {mat3} out
+ */
+
+ function copy$6(out, a) {
+ out[0] = a[0];
+ out[1] = a[1];
+ out[2] = a[2];
+ out[3] = a[3];
+ out[4] = a[4];
+ out[5] = a[5];
+ out[6] = a[6];
+ out[7] = a[7];
+ out[8] = a[8];
+ return out;
+ }
+ /**
+ * Create a new mat3 with the given values
+ *
+ * @param {Number} m00 Component in column 0, row 0 position (index 0)
+ * @param {Number} m01 Component in column 0, row 1 position (index 1)
+ * @param {Number} m02 Component in column 0, row 2 position (index 2)
+ * @param {Number} m10 Component in column 1, row 0 position (index 3)
+ * @param {Number} m11 Component in column 1, row 1 position (index 4)
+ * @param {Number} m12 Component in column 1, row 2 position (index 5)
+ * @param {Number} m20 Component in column 2, row 0 position (index 6)
+ * @param {Number} m21 Component in column 2, row 1 position (index 7)
+ * @param {Number} m22 Component in column 2, row 2 position (index 8)
+ * @returns {mat3} A new mat3
+ */
+
+ function fromValues$6(m00, m01, m02, m10, m11, m12, m20, m21, m22) {
+ var out = new ARRAY_TYPE(9);
+ out[0] = m00;
+ out[1] = m01;
+ out[2] = m02;
+ out[3] = m10;
+ out[4] = m11;
+ out[5] = m12;
+ out[6] = m20;
+ out[7] = m21;
+ out[8] = m22;
+ return out;
+ }
+ /**
+ * Set the components of a mat3 to the given values
+ *
+ * @param {mat3} out the receiving matrix
+ * @param {Number} m00 Component in column 0, row 0 position (index 0)
+ * @param {Number} m01 Component in column 0, row 1 position (index 1)
+ * @param {Number} m02 Component in column 0, row 2 position (index 2)
+ * @param {Number} m10 Component in column 1, row 0 position (index 3)
+ * @param {Number} m11 Component in column 1, row 1 position (index 4)
+ * @param {Number} m12 Component in column 1, row 2 position (index 5)
+ * @param {Number} m20 Component in column 2, row 0 position (index 6)
+ * @param {Number} m21 Component in column 2, row 1 position (index 7)
+ * @param {Number} m22 Component in column 2, row 2 position (index 8)
+ * @returns {mat3} out
+ */
+
+ function set$6(out, m00, m01, m02, m10, m11, m12, m20, m21, m22) {
+ out[0] = m00;
+ out[1] = m01;
+ out[2] = m02;
+ out[3] = m10;
+ out[4] = m11;
+ out[5] = m12;
+ out[6] = m20;
+ out[7] = m21;
+ out[8] = m22;
+ return out;
+ }
+ /**
+ * Set a mat3 to the identity matrix
+ *
+ * @param {mat3} out the receiving matrix
+ * @returns {mat3} out
+ */
+
+ function identity$3(out) {
+ out[0] = 1;
+ out[1] = 0;
+ out[2] = 0;
+ out[3] = 0;
+ out[4] = 1;
+ out[5] = 0;
+ out[6] = 0;
+ out[7] = 0;
+ out[8] = 1;
+ return out;
+ }
+ /**
+ * Transpose the values of a mat3
+ *
+ * @param {mat3} out the receiving matrix
+ * @param {ReadonlyMat3} a the source matrix
+ * @returns {mat3} out
+ */
+
+ function transpose$1(out, a) {
+ // If we are transposing ourselves we can skip a few steps but have to cache some values
+ if (out === a) {
+ var a01 = a[1],
+ a02 = a[2],
+ a12 = a[5];
+ out[1] = a[3];
+ out[2] = a[6];
+ out[3] = a01;
+ out[5] = a[7];
+ out[6] = a02;
+ out[7] = a12;
+ } else {
+ out[0] = a[0];
+ out[1] = a[3];
+ out[2] = a[6];
+ out[3] = a[1];
+ out[4] = a[4];
+ out[5] = a[7];
+ out[6] = a[2];
+ out[7] = a[5];
+ out[8] = a[8];
+ }
+
+ return out;
+ }
+ /**
+ * Inverts a mat3
+ *
+ * @param {mat3} out the receiving matrix
+ * @param {ReadonlyMat3} a the source matrix
+ * @returns {mat3} out
+ */
+
+ function invert$3(out, a) {
+ var a00 = a[0],
+ a01 = a[1],
+ a02 = a[2];
+ var a10 = a[3],
+ a11 = a[4],
+ a12 = a[5];
+ var a20 = a[6],
+ a21 = a[7],
+ a22 = a[8];
+ var b01 = a22 * a11 - a12 * a21;
+ var b11 = -a22 * a10 + a12 * a20;
+ var b21 = a21 * a10 - a11 * a20; // Calculate the determinant
+
+ var det = a00 * b01 + a01 * b11 + a02 * b21;
+
+ if (!det) {
+ return null;
+ }
+
+ det = 1.0 / det;
+ out[0] = b01 * det;
+ out[1] = (-a22 * a01 + a02 * a21) * det;
+ out[2] = (a12 * a01 - a02 * a11) * det;
+ out[3] = b11 * det;
+ out[4] = (a22 * a00 - a02 * a20) * det;
+ out[5] = (-a12 * a00 + a02 * a10) * det;
+ out[6] = b21 * det;
+ out[7] = (-a21 * a00 + a01 * a20) * det;
+ out[8] = (a11 * a00 - a01 * a10) * det;
+ return out;
+ }
+ /**
+ * Calculates the adjugate of a mat3
+ *
+ * @param {mat3} out the receiving matrix
+ * @param {ReadonlyMat3} a the source matrix
+ * @returns {mat3} out
+ */
+
+ function adjoint$1(out, a) {
+ var a00 = a[0],
+ a01 = a[1],
+ a02 = a[2];
+ var a10 = a[3],
+ a11 = a[4],
+ a12 = a[5];
+ var a20 = a[6],
+ a21 = a[7],
+ a22 = a[8];
+ out[0] = a11 * a22 - a12 * a21;
+ out[1] = a02 * a21 - a01 * a22;
+ out[2] = a01 * a12 - a02 * a11;
+ out[3] = a12 * a20 - a10 * a22;
+ out[4] = a00 * a22 - a02 * a20;
+ out[5] = a02 * a10 - a00 * a12;
+ out[6] = a10 * a21 - a11 * a20;
+ out[7] = a01 * a20 - a00 * a21;
+ out[8] = a00 * a11 - a01 * a10;
+ return out;
+ }
+ /**
+ * Calculates the determinant of a mat3
+ *
+ * @param {ReadonlyMat3} a the source matrix
+ * @returns {Number} determinant of a
+ */
+
+ function determinant$1(a) {
+ var a00 = a[0],
+ a01 = a[1],
+ a02 = a[2];
+ var a10 = a[3],
+ a11 = a[4],
+ a12 = a[5];
+ var a20 = a[6],
+ a21 = a[7],
+ a22 = a[8];
+ return a00 * (a22 * a11 - a12 * a21) + a01 * (-a22 * a10 + a12 * a20) + a02 * (a21 * a10 - a11 * a20);
+ }
+ /**
+ * Multiplies two mat3's
+ *
+ * @param {mat3} out the receiving matrix
+ * @param {ReadonlyMat3} a the first operand
+ * @param {ReadonlyMat3} b the second operand
+ * @returns {mat3} out
+ */
+
+ function multiply$6(out, a, b) {
+ var a00 = a[0],
+ a01 = a[1],
+ a02 = a[2];
+ var a10 = a[3],
+ a11 = a[4],
+ a12 = a[5];
+ var a20 = a[6],
+ a21 = a[7],
+ a22 = a[8];
+ var b00 = b[0],
+ b01 = b[1],
+ b02 = b[2];
+ var b10 = b[3],
+ b11 = b[4],
+ b12 = b[5];
+ var b20 = b[6],
+ b21 = b[7],
+ b22 = b[8];
+ out[0] = b00 * a00 + b01 * a10 + b02 * a20;
+ out[1] = b00 * a01 + b01 * a11 + b02 * a21;
+ out[2] = b00 * a02 + b01 * a12 + b02 * a22;
+ out[3] = b10 * a00 + b11 * a10 + b12 * a20;
+ out[4] = b10 * a01 + b11 * a11 + b12 * a21;
+ out[5] = b10 * a02 + b11 * a12 + b12 * a22;
+ out[6] = b20 * a00 + b21 * a10 + b22 * a20;
+ out[7] = b20 * a01 + b21 * a11 + b22 * a21;
+ out[8] = b20 * a02 + b21 * a12 + b22 * a22;
+ return out;
+ }
+ /**
+ * Translate a mat3 by the given vector
+ *
+ * @param {mat3} out the receiving matrix
+ * @param {ReadonlyMat3} a the matrix to translate
+ * @param {ReadonlyVec2} v vector to translate by
+ * @returns {mat3} out
+ */
+
+ function translate$2(out, a, v) {
+ var a00 = a[0],
+ a01 = a[1],
+ a02 = a[2],
+ a10 = a[3],
+ a11 = a[4],
+ a12 = a[5],
+ a20 = a[6],
+ a21 = a[7],
+ a22 = a[8],
+ x = v[0],
+ y = v[1];
+ out[0] = a00;
+ out[1] = a01;
+ out[2] = a02;
+ out[3] = a10;
+ out[4] = a11;
+ out[5] = a12;
+ out[6] = x * a00 + y * a10 + a20;
+ out[7] = x * a01 + y * a11 + a21;
+ out[8] = x * a02 + y * a12 + a22;
+ return out;
+ }
+ /**
+ * Rotates a mat3 by the given angle
+ *
+ * @param {mat3} out the receiving matrix
+ * @param {ReadonlyMat3} a the matrix to rotate
+ * @param {Number} rad the angle to rotate the matrix by
+ * @returns {mat3} out
+ */
+
+ function rotate$2(out, a, rad) {
+ var a00 = a[0],
+ a01 = a[1],
+ a02 = a[2],
+ a10 = a[3],
+ a11 = a[4],
+ a12 = a[5],
+ a20 = a[6],
+ a21 = a[7],
+ a22 = a[8],
+ s = Math.sin(rad),
+ c = Math.cos(rad);
+ out[0] = c * a00 + s * a10;
+ out[1] = c * a01 + s * a11;
+ out[2] = c * a02 + s * a12;
+ out[3] = c * a10 - s * a00;
+ out[4] = c * a11 - s * a01;
+ out[5] = c * a12 - s * a02;
+ out[6] = a20;
+ out[7] = a21;
+ out[8] = a22;
+ return out;
+ }
+ /**
+ * Scales the mat3 by the dimensions in the given vec2
+ *
+ * @param {mat3} out the receiving matrix
+ * @param {ReadonlyMat3} a the matrix to rotate
+ * @param {ReadonlyVec2} v the vec2 to scale the matrix by
+ * @returns {mat3} out
+ **/
+
+ function scale$6(out, a, v) {
+ var x = v[0],
+ y = v[1];
+ out[0] = x * a[0];
+ out[1] = x * a[1];
+ out[2] = x * a[2];
+ out[3] = y * a[3];
+ out[4] = y * a[4];
+ out[5] = y * a[5];
+ out[6] = a[6];
+ out[7] = a[7];
+ out[8] = a[8];
+ return out;
+ }
+ /**
+ * Creates a matrix from a vector translation
+ * This is equivalent to (but much faster than):
+ *
+ * mat3.identity(dest);
+ * mat3.translate(dest, dest, vec);
+ *
+ * @param {mat3} out mat3 receiving operation result
+ * @param {ReadonlyVec2} v Translation vector
+ * @returns {mat3} out
+ */
+
+ function fromTranslation$2(out, v) {
+ out[0] = 1;
+ out[1] = 0;
+ out[2] = 0;
+ out[3] = 0;
+ out[4] = 1;
+ out[5] = 0;
+ out[6] = v[0];
+ out[7] = v[1];
+ out[8] = 1;
+ return out;
+ }
+ /**
+ * Creates a matrix from a given angle
+ * This is equivalent to (but much faster than):
+ *
+ * mat3.identity(dest);
+ * mat3.rotate(dest, dest, rad);
+ *
+ * @param {mat3} out mat3 receiving operation result
+ * @param {Number} rad the angle to rotate the matrix by
+ * @returns {mat3} out
+ */
+
+ function fromRotation$2(out, rad) {
+ var s = Math.sin(rad),
+ c = Math.cos(rad);
+ out[0] = c;
+ out[1] = s;
+ out[2] = 0;
+ out[3] = -s;
+ out[4] = c;
+ out[5] = 0;
+ out[6] = 0;
+ out[7] = 0;
+ out[8] = 1;
+ return out;
+ }
+ /**
+ * Creates a matrix from a vector scaling
+ * This is equivalent to (but much faster than):
+ *
+ * mat3.identity(dest);
+ * mat3.scale(dest, dest, vec);
+ *
+ * @param {mat3} out mat3 receiving operation result
+ * @param {ReadonlyVec2} v Scaling vector
+ * @returns {mat3} out
+ */
+
+ function fromScaling$1(out, v) {
+ out[0] = v[0];
+ out[1] = 0;
+ out[2] = 0;
+ out[3] = 0;
+ out[4] = v[1];
+ out[5] = 0;
+ out[6] = 0;
+ out[7] = 0;
+ out[8] = 1;
+ return out;
+ }
+ /**
+ * Copies the values from a mat2d into a mat3
+ *
+ * @param {mat3} out the receiving matrix
+ * @param {ReadonlyMat2d} a the matrix to copy
+ * @returns {mat3} out
+ **/
+
+ function fromMat2d(out, a) {
+ out[0] = a[0];
+ out[1] = a[1];
+ out[2] = 0;
+ out[3] = a[2];
+ out[4] = a[3];
+ out[5] = 0;
+ out[6] = a[4];
+ out[7] = a[5];
+ out[8] = 1;
+ return out;
+ }
+ /**
+ * Calculates a 3x3 matrix from the given quaternion
+ *
+ * @param {mat3} out mat3 receiving operation result
+ * @param {ReadonlyQuat} q Quaternion to create matrix from
+ *
+ * @returns {mat3} out
+ */
+
+ function fromQuat$1(out, q) {
+ var x = q[0],
+ y = q[1],
+ z = q[2],
+ w = q[3];
+ var x2 = x + x;
+ var y2 = y + y;
+ var z2 = z + z;
+ var xx = x * x2;
+ var yx = y * x2;
+ var yy = y * y2;
+ var zx = z * x2;
+ var zy = z * y2;
+ var zz = z * z2;
+ var wx = w * x2;
+ var wy = w * y2;
+ var wz = w * z2;
+ out[0] = 1 - yy - zz;
+ out[3] = yx - wz;
+ out[6] = zx + wy;
+ out[1] = yx + wz;
+ out[4] = 1 - xx - zz;
+ out[7] = zy - wx;
+ out[2] = zx - wy;
+ out[5] = zy + wx;
+ out[8] = 1 - xx - yy;
+ return out;
+ }
+ /**
+ * Calculates a 3x3 normal matrix (transpose inverse) from the 4x4 matrix
+ *
+ * @param {mat3} out mat3 receiving operation result
+ * @param {ReadonlyMat4} a Mat4 to derive the normal matrix from
+ *
+ * @returns {mat3} out
+ */
+
+ function normalFromMat4(out, a) {
+ var a00 = a[0],
+ a01 = a[1],
+ a02 = a[2],
+ a03 = a[3];
+ var a10 = a[4],
+ a11 = a[5],
+ a12 = a[6],
+ a13 = a[7];
+ var a20 = a[8],
+ a21 = a[9],
+ a22 = a[10],
+ a23 = a[11];
+ var a30 = a[12],
+ a31 = a[13],
+ a32 = a[14],
+ a33 = a[15];
+ var b00 = a00 * a11 - a01 * a10;
+ var b01 = a00 * a12 - a02 * a10;
+ var b02 = a00 * a13 - a03 * a10;
+ var b03 = a01 * a12 - a02 * a11;
+ var b04 = a01 * a13 - a03 * a11;
+ var b05 = a02 * a13 - a03 * a12;
+ var b06 = a20 * a31 - a21 * a30;
+ var b07 = a20 * a32 - a22 * a30;
+ var b08 = a20 * a33 - a23 * a30;
+ var b09 = a21 * a32 - a22 * a31;
+ var b10 = a21 * a33 - a23 * a31;
+ var b11 = a22 * a33 - a23 * a32; // Calculate the determinant
+
+ var det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06;
+
+ if (!det) {
+ return null;
+ }
+
+ det = 1.0 / det;
+ out[0] = (a11 * b11 - a12 * b10 + a13 * b09) * det;
+ out[1] = (a12 * b08 - a10 * b11 - a13 * b07) * det;
+ out[2] = (a10 * b10 - a11 * b08 + a13 * b06) * det;
+ out[3] = (a02 * b10 - a01 * b11 - a03 * b09) * det;
+ out[4] = (a00 * b11 - a02 * b08 + a03 * b07) * det;
+ out[5] = (a01 * b08 - a00 * b10 - a03 * b06) * det;
+ out[6] = (a31 * b05 - a32 * b04 + a33 * b03) * det;
+ out[7] = (a32 * b02 - a30 * b05 - a33 * b01) * det;
+ out[8] = (a30 * b04 - a31 * b02 + a33 * b00) * det;
+ return out;
+ }
+ /**
+ * Generates a 2D projection matrix with the given bounds
+ *
+ * @param {mat3} out mat3 frustum matrix will be written into
+ * @param {number} width Width of your gl context
+ * @param {number} height Height of gl context
+ * @returns {mat3} out
+ */
+
+ function projection(out, width, height) {
+ out[0] = 2 / width;
+ out[1] = 0;
+ out[2] = 0;
+ out[3] = 0;
+ out[4] = -2 / height;
+ out[5] = 0;
+ out[6] = -1;
+ out[7] = 1;
+ out[8] = 1;
+ return out;
+ }
+ /**
+ * Returns a string representation of a mat3
+ *
+ * @param {ReadonlyMat3} a matrix to represent as a string
+ * @returns {String} string representation of the matrix
+ */
+
+ function str$6(a) {
+ return "mat3(" + a[0] + ", " + a[1] + ", " + a[2] + ", " + a[3] + ", " + a[4] + ", " + a[5] + ", " + a[6] + ", " + a[7] + ", " + a[8] + ")";
+ }
+ /**
+ * Returns Frobenius norm of a mat3
+ *
+ * @param {ReadonlyMat3} a the matrix to calculate Frobenius norm of
+ * @returns {Number} Frobenius norm
+ */
+
+ function frob$1(a) {
+ return Math.hypot(a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8]);
+ }
+ /**
+ * Adds two mat3's
+ *
+ * @param {mat3} out the receiving matrix
+ * @param {ReadonlyMat3} a the first operand
+ * @param {ReadonlyMat3} b the second operand
+ * @returns {mat3} out
+ */
+
+ function add$6(out, a, b) {
+ out[0] = a[0] + b[0];
+ out[1] = a[1] + b[1];
+ out[2] = a[2] + b[2];
+ out[3] = a[3] + b[3];
+ out[4] = a[4] + b[4];
+ out[5] = a[5] + b[5];
+ out[6] = a[6] + b[6];
+ out[7] = a[7] + b[7];
+ out[8] = a[8] + b[8];
+ return out;
+ }
+ /**
+ * Subtracts matrix b from matrix a
+ *
+ * @param {mat3} out the receiving matrix
+ * @param {ReadonlyMat3} a the first operand
+ * @param {ReadonlyMat3} b the second operand
+ * @returns {mat3} out
+ */
+
+ function subtract$4(out, a, b) {
+ out[0] = a[0] - b[0];
+ out[1] = a[1] - b[1];
+ out[2] = a[2] - b[2];
+ out[3] = a[3] - b[3];
+ out[4] = a[4] - b[4];
+ out[5] = a[5] - b[5];
+ out[6] = a[6] - b[6];
+ out[7] = a[7] - b[7];
+ out[8] = a[8] - b[8];
+ return out;
+ }
+ /**
+ * Multiply each element of the matrix by a scalar.
+ *
+ * @param {mat3} out the receiving matrix
+ * @param {ReadonlyMat3} a the matrix to scale
+ * @param {Number} b amount to scale the matrix's elements by
+ * @returns {mat3} out
+ */
+
+ function multiplyScalar$1(out, a, b) {
+ out[0] = a[0] * b;
+ out[1] = a[1] * b;
+ out[2] = a[2] * b;
+ out[3] = a[3] * b;
+ out[4] = a[4] * b;
+ out[5] = a[5] * b;
+ out[6] = a[6] * b;
+ out[7] = a[7] * b;
+ out[8] = a[8] * b;
+ return out;
+ }
+ /**
+ * Adds two mat3's after multiplying each element of the second operand by a scalar value.
+ *
+ * @param {mat3} out the receiving vector
+ * @param {ReadonlyMat3} a the first operand
+ * @param {ReadonlyMat3} b the second operand
+ * @param {Number} scale the amount to scale b's elements by before adding
+ * @returns {mat3} out
+ */
+
+ function multiplyScalarAndAdd$1(out, a, b, scale) {
+ out[0] = a[0] + b[0] * scale;
+ out[1] = a[1] + b[1] * scale;
+ out[2] = a[2] + b[2] * scale;
+ out[3] = a[3] + b[3] * scale;
+ out[4] = a[4] + b[4] * scale;
+ out[5] = a[5] + b[5] * scale;
+ out[6] = a[6] + b[6] * scale;
+ out[7] = a[7] + b[7] * scale;
+ out[8] = a[8] + b[8] * scale;
+ return out;
+ }
+ /**
+ * Returns whether or not the matrices have exactly the same elements in the same position (when compared with ===)
+ *
+ * @param {ReadonlyMat3} a The first matrix.
+ * @param {ReadonlyMat3} b The second matrix.
+ * @returns {Boolean} True if the matrices are equal, false otherwise.
+ */
+
+ function exactEquals$6(a, b) {
+ return a[0] === b[0] && a[1] === b[1] && a[2] === b[2] && a[3] === b[3] && a[4] === b[4] && a[5] === b[5] && a[6] === b[6] && a[7] === b[7] && a[8] === b[8];
+ }
+ /**
+ * Returns whether or not the matrices have approximately the same elements in the same position.
+ *
+ * @param {ReadonlyMat3} a The first matrix.
+ * @param {ReadonlyMat3} b The second matrix.
+ * @returns {Boolean} True if the matrices are equal, false otherwise.
+ */
+
+ function equals$6(a, b) {
+ var a0 = a[0],
+ a1 = a[1],
+ a2 = a[2],
+ a3 = a[3],
+ a4 = a[4],
+ a5 = a[5],
+ a6 = a[6],
+ a7 = a[7],
+ a8 = a[8];
+ var b0 = b[0],
+ b1 = b[1],
+ b2 = b[2],
+ b3 = b[3],
+ b4 = b[4],
+ b5 = b[5],
+ b6 = b[6],
+ b7 = b[7],
+ b8 = b[8];
+ return Math.abs(a0 - b0) <= EPSILON * Math.max(1.0, Math.abs(a0), Math.abs(b0)) && Math.abs(a1 - b1) <= EPSILON * Math.max(1.0, Math.abs(a1), Math.abs(b1)) && Math.abs(a2 - b2) <= EPSILON * Math.max(1.0, Math.abs(a2), Math.abs(b2)) && Math.abs(a3 - b3) <= EPSILON * Math.max(1.0, Math.abs(a3), Math.abs(b3)) && Math.abs(a4 - b4) <= EPSILON * Math.max(1.0, Math.abs(a4), Math.abs(b4)) && Math.abs(a5 - b5) <= EPSILON * Math.max(1.0, Math.abs(a5), Math.abs(b5)) && Math.abs(a6 - b6) <= EPSILON * Math.max(1.0, Math.abs(a6), Math.abs(b6)) && Math.abs(a7 - b7) <= EPSILON * Math.max(1.0, Math.abs(a7), Math.abs(b7)) && Math.abs(a8 - b8) <= EPSILON * Math.max(1.0, Math.abs(a8), Math.abs(b8));
+ }
+ /**
+ * Alias for {@link mat3.multiply}
+ * @function
+ */
+
+ var mul$6 = multiply$6;
+ /**
+ * Alias for {@link mat3.subtract}
+ * @function
+ */
+
+ var sub$4 = subtract$4;
+
+ var mat3 = /*#__PURE__*/Object.freeze({
+ __proto__: null,
+ create: create$6,
+ fromMat4: fromMat4$1,
+ clone: clone$6,
+ copy: copy$6,
+ fromValues: fromValues$6,
+ set: set$6,
+ identity: identity$3,
+ transpose: transpose$1,
+ invert: invert$3,
+ adjoint: adjoint$1,
+ determinant: determinant$1,
+ multiply: multiply$6,
+ translate: translate$2,
+ rotate: rotate$2,
+ scale: scale$6,
+ fromTranslation: fromTranslation$2,
+ fromRotation: fromRotation$2,
+ fromScaling: fromScaling$1,
+ fromMat2d: fromMat2d,
+ fromQuat: fromQuat$1,
+ normalFromMat4: normalFromMat4,
+ projection: projection,
+ str: str$6,
+ frob: frob$1,
+ add: add$6,
+ subtract: subtract$4,
+ multiplyScalar: multiplyScalar$1,
+ multiplyScalarAndAdd: multiplyScalarAndAdd$1,
+ exactEquals: exactEquals$6,
+ equals: equals$6,
+ mul: mul$6,
+ sub: sub$4
+ });
+
+ /**
+ * 4x4 Matrix
Format: column-major, when typed out it looks like row-major
The matrices are being post multiplied.
+ * @module mat4
+ */
+
+ /**
+ * Creates a new identity mat4
+ *
+ * @returns {mat4} a new 4x4 matrix
+ */
+
+ function create$5() {
+ var out = new ARRAY_TYPE(16);
+
+ if (ARRAY_TYPE != Float32Array) {
+ out[1] = 0;
+ out[2] = 0;
+ out[3] = 0;
+ out[4] = 0;
+ out[6] = 0;
+ out[7] = 0;
+ out[8] = 0;
+ out[9] = 0;
+ out[11] = 0;
+ out[12] = 0;
+ out[13] = 0;
+ out[14] = 0;
+ }
+
+ out[0] = 1;
+ out[5] = 1;
+ out[10] = 1;
+ out[15] = 1;
+ return out;
+ }
+ /**
+ * Creates a new mat4 initialized with values from an existing matrix
+ *
+ * @param {ReadonlyMat4} a matrix to clone
+ * @returns {mat4} a new 4x4 matrix
+ */
+
+ function clone$5(a) {
+ var out = new ARRAY_TYPE(16);
+ out[0] = a[0];
+ out[1] = a[1];
+ out[2] = a[2];
+ out[3] = a[3];
+ out[4] = a[4];
+ out[5] = a[5];
+ out[6] = a[6];
+ out[7] = a[7];
+ out[8] = a[8];
+ out[9] = a[9];
+ out[10] = a[10];
+ out[11] = a[11];
+ out[12] = a[12];
+ out[13] = a[13];
+ out[14] = a[14];
+ out[15] = a[15];
+ return out;
+ }
+ /**
+ * Copy the values from one mat4 to another
+ *
+ * @param {mat4} out the receiving matrix
+ * @param {ReadonlyMat4} a the source matrix
+ * @returns {mat4} out
+ */
+
+ function copy$5(out, a) {
+ out[0] = a[0];
+ out[1] = a[1];
+ out[2] = a[2];
+ out[3] = a[3];
+ out[4] = a[4];
+ out[5] = a[5];
+ out[6] = a[6];
+ out[7] = a[7];
+ out[8] = a[8];
+ out[9] = a[9];
+ out[10] = a[10];
+ out[11] = a[11];
+ out[12] = a[12];
+ out[13] = a[13];
+ out[14] = a[14];
+ out[15] = a[15];
+ return out;
+ }
+ /**
+ * Create a new mat4 with the given values
+ *
+ * @param {Number} m00 Component in column 0, row 0 position (index 0)
+ * @param {Number} m01 Component in column 0, row 1 position (index 1)
+ * @param {Number} m02 Component in column 0, row 2 position (index 2)
+ * @param {Number} m03 Component in column 0, row 3 position (index 3)
+ * @param {Number} m10 Component in column 1, row 0 position (index 4)
+ * @param {Number} m11 Component in column 1, row 1 position (index 5)
+ * @param {Number} m12 Component in column 1, row 2 position (index 6)
+ * @param {Number} m13 Component in column 1, row 3 position (index 7)
+ * @param {Number} m20 Component in column 2, row 0 position (index 8)
+ * @param {Number} m21 Component in column 2, row 1 position (index 9)
+ * @param {Number} m22 Component in column 2, row 2 position (index 10)
+ * @param {Number} m23 Component in column 2, row 3 position (index 11)
+ * @param {Number} m30 Component in column 3, row 0 position (index 12)
+ * @param {Number} m31 Component in column 3, row 1 position (index 13)
+ * @param {Number} m32 Component in column 3, row 2 position (index 14)
+ * @param {Number} m33 Component in column 3, row 3 position (index 15)
+ * @returns {mat4} A new mat4
+ */
+
+ function fromValues$5(m00, m01, m02, m03, m10, m11, m12, m13, m20, m21, m22, m23, m30, m31, m32, m33) {
+ var out = new ARRAY_TYPE(16);
+ out[0] = m00;
+ out[1] = m01;
+ out[2] = m02;
+ out[3] = m03;
+ out[4] = m10;
+ out[5] = m11;
+ out[6] = m12;
+ out[7] = m13;
+ out[8] = m20;
+ out[9] = m21;
+ out[10] = m22;
+ out[11] = m23;
+ out[12] = m30;
+ out[13] = m31;
+ out[14] = m32;
+ out[15] = m33;
+ return out;
+ }
+ /**
+ * Set the components of a mat4 to the given values
+ *
+ * @param {mat4} out the receiving matrix
+ * @param {Number} m00 Component in column 0, row 0 position (index 0)
+ * @param {Number} m01 Component in column 0, row 1 position (index 1)
+ * @param {Number} m02 Component in column 0, row 2 position (index 2)
+ * @param {Number} m03 Component in column 0, row 3 position (index 3)
+ * @param {Number} m10 Component in column 1, row 0 position (index 4)
+ * @param {Number} m11 Component in column 1, row 1 position (index 5)
+ * @param {Number} m12 Component in column 1, row 2 position (index 6)
+ * @param {Number} m13 Component in column 1, row 3 position (index 7)
+ * @param {Number} m20 Component in column 2, row 0 position (index 8)
+ * @param {Number} m21 Component in column 2, row 1 position (index 9)
+ * @param {Number} m22 Component in column 2, row 2 position (index 10)
+ * @param {Number} m23 Component in column 2, row 3 position (index 11)
+ * @param {Number} m30 Component in column 3, row 0 position (index 12)
+ * @param {Number} m31 Component in column 3, row 1 position (index 13)
+ * @param {Number} m32 Component in column 3, row 2 position (index 14)
+ * @param {Number} m33 Component in column 3, row 3 position (index 15)
+ * @returns {mat4} out
+ */
+
+ function set$5(out, m00, m01, m02, m03, m10, m11, m12, m13, m20, m21, m22, m23, m30, m31, m32, m33) {
+ out[0] = m00;
+ out[1] = m01;
+ out[2] = m02;
+ out[3] = m03;
+ out[4] = m10;
+ out[5] = m11;
+ out[6] = m12;
+ out[7] = m13;
+ out[8] = m20;
+ out[9] = m21;
+ out[10] = m22;
+ out[11] = m23;
+ out[12] = m30;
+ out[13] = m31;
+ out[14] = m32;
+ out[15] = m33;
+ return out;
+ }
+ /**
+ * Set a mat4 to the identity matrix
+ *
+ * @param {mat4} out the receiving matrix
+ * @returns {mat4} out
+ */
+
+ function identity$2(out) {
+ out[0] = 1;
+ out[1] = 0;
+ out[2] = 0;
+ out[3] = 0;
+ out[4] = 0;
+ out[5] = 1;
+ out[6] = 0;
+ out[7] = 0;
+ out[8] = 0;
+ out[9] = 0;
+ out[10] = 1;
+ out[11] = 0;
+ out[12] = 0;
+ out[13] = 0;
+ out[14] = 0;
+ out[15] = 1;
+ return out;
+ }
+ /**
+ * Transpose the values of a mat4
+ *
+ * @param {mat4} out the receiving matrix
+ * @param {ReadonlyMat4} a the source matrix
+ * @returns {mat4} out
+ */
+
+ function transpose(out, a) {
+ // If we are transposing ourselves we can skip a few steps but have to cache some values
+ if (out === a) {
+ var a01 = a[1],
+ a02 = a[2],
+ a03 = a[3];
+ var a12 = a[6],
+ a13 = a[7];
+ var a23 = a[11];
+ out[1] = a[4];
+ out[2] = a[8];
+ out[3] = a[12];
+ out[4] = a01;
+ out[6] = a[9];
+ out[7] = a[13];
+ out[8] = a02;
+ out[9] = a12;
+ out[11] = a[14];
+ out[12] = a03;
+ out[13] = a13;
+ out[14] = a23;
+ } else {
+ out[0] = a[0];
+ out[1] = a[4];
+ out[2] = a[8];
+ out[3] = a[12];
+ out[4] = a[1];
+ out[5] = a[5];
+ out[6] = a[9];
+ out[7] = a[13];
+ out[8] = a[2];
+ out[9] = a[6];
+ out[10] = a[10];
+ out[11] = a[14];
+ out[12] = a[3];
+ out[13] = a[7];
+ out[14] = a[11];
+ out[15] = a[15];
+ }
+
+ return out;
+ }
+ /**
+ * Inverts a mat4
+ *
+ * @param {mat4} out the receiving matrix
+ * @param {ReadonlyMat4} a the source matrix
+ * @returns {mat4} out
+ */
+
+ function invert$2(out, a) {
+ var a00 = a[0],
+ a01 = a[1],
+ a02 = a[2],
+ a03 = a[3];
+ var a10 = a[4],
+ a11 = a[5],
+ a12 = a[6],
+ a13 = a[7];
+ var a20 = a[8],
+ a21 = a[9],
+ a22 = a[10],
+ a23 = a[11];
+ var a30 = a[12],
+ a31 = a[13],
+ a32 = a[14],
+ a33 = a[15];
+ var b00 = a00 * a11 - a01 * a10;
+ var b01 = a00 * a12 - a02 * a10;
+ var b02 = a00 * a13 - a03 * a10;
+ var b03 = a01 * a12 - a02 * a11;
+ var b04 = a01 * a13 - a03 * a11;
+ var b05 = a02 * a13 - a03 * a12;
+ var b06 = a20 * a31 - a21 * a30;
+ var b07 = a20 * a32 - a22 * a30;
+ var b08 = a20 * a33 - a23 * a30;
+ var b09 = a21 * a32 - a22 * a31;
+ var b10 = a21 * a33 - a23 * a31;
+ var b11 = a22 * a33 - a23 * a32; // Calculate the determinant
+
+ var det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06;
+
+ if (!det) {
+ return null;
+ }
+
+ det = 1.0 / det;
+ out[0] = (a11 * b11 - a12 * b10 + a13 * b09) * det;
+ out[1] = (a02 * b10 - a01 * b11 - a03 * b09) * det;
+ out[2] = (a31 * b05 - a32 * b04 + a33 * b03) * det;
+ out[3] = (a22 * b04 - a21 * b05 - a23 * b03) * det;
+ out[4] = (a12 * b08 - a10 * b11 - a13 * b07) * det;
+ out[5] = (a00 * b11 - a02 * b08 + a03 * b07) * det;
+ out[6] = (a32 * b02 - a30 * b05 - a33 * b01) * det;
+ out[7] = (a20 * b05 - a22 * b02 + a23 * b01) * det;
+ out[8] = (a10 * b10 - a11 * b08 + a13 * b06) * det;
+ out[9] = (a01 * b08 - a00 * b10 - a03 * b06) * det;
+ out[10] = (a30 * b04 - a31 * b02 + a33 * b00) * det;
+ out[11] = (a21 * b02 - a20 * b04 - a23 * b00) * det;
+ out[12] = (a11 * b07 - a10 * b09 - a12 * b06) * det;
+ out[13] = (a00 * b09 - a01 * b07 + a02 * b06) * det;
+ out[14] = (a31 * b01 - a30 * b03 - a32 * b00) * det;
+ out[15] = (a20 * b03 - a21 * b01 + a22 * b00) * det;
+ return out;
+ }
+ /**
+ * Calculates the adjugate of a mat4
+ *
+ * @param {mat4} out the receiving matrix
+ * @param {ReadonlyMat4} a the source matrix
+ * @returns {mat4} out
+ */
+
+ function adjoint(out, a) {
+ var a00 = a[0],
+ a01 = a[1],
+ a02 = a[2],
+ a03 = a[3];
+ var a10 = a[4],
+ a11 = a[5],
+ a12 = a[6],
+ a13 = a[7];
+ var a20 = a[8],
+ a21 = a[9],
+ a22 = a[10],
+ a23 = a[11];
+ var a30 = a[12],
+ a31 = a[13],
+ a32 = a[14],
+ a33 = a[15];
+ var b00 = a00 * a11 - a01 * a10;
+ var b01 = a00 * a12 - a02 * a10;
+ var b02 = a00 * a13 - a03 * a10;
+ var b03 = a01 * a12 - a02 * a11;
+ var b04 = a01 * a13 - a03 * a11;
+ var b05 = a02 * a13 - a03 * a12;
+ var b06 = a20 * a31 - a21 * a30;
+ var b07 = a20 * a32 - a22 * a30;
+ var b08 = a20 * a33 - a23 * a30;
+ var b09 = a21 * a32 - a22 * a31;
+ var b10 = a21 * a33 - a23 * a31;
+ var b11 = a22 * a33 - a23 * a32;
+ out[0] = a11 * b11 - a12 * b10 + a13 * b09;
+ out[1] = a02 * b10 - a01 * b11 - a03 * b09;
+ out[2] = a31 * b05 - a32 * b04 + a33 * b03;
+ out[3] = a22 * b04 - a21 * b05 - a23 * b03;
+ out[4] = a12 * b08 - a10 * b11 - a13 * b07;
+ out[5] = a00 * b11 - a02 * b08 + a03 * b07;
+ out[6] = a32 * b02 - a30 * b05 - a33 * b01;
+ out[7] = a20 * b05 - a22 * b02 + a23 * b01;
+ out[8] = a10 * b10 - a11 * b08 + a13 * b06;
+ out[9] = a01 * b08 - a00 * b10 - a03 * b06;
+ out[10] = a30 * b04 - a31 * b02 + a33 * b00;
+ out[11] = a21 * b02 - a20 * b04 - a23 * b00;
+ out[12] = a11 * b07 - a10 * b09 - a12 * b06;
+ out[13] = a00 * b09 - a01 * b07 + a02 * b06;
+ out[14] = a31 * b01 - a30 * b03 - a32 * b00;
+ out[15] = a20 * b03 - a21 * b01 + a22 * b00;
+ return out;
+ }
+ /**
+ * Calculates the determinant of a mat4
+ *
+ * @param {ReadonlyMat4} a the source matrix
+ * @returns {Number} determinant of a
+ */
+
+ function determinant(a) {
+ var a00 = a[0],
+ a01 = a[1],
+ a02 = a[2],
+ a03 = a[3];
+ var a10 = a[4],
+ a11 = a[5],
+ a12 = a[6],
+ a13 = a[7];
+ var a20 = a[8],
+ a21 = a[9],
+ a22 = a[10],
+ a23 = a[11];
+ var a30 = a[12],
+ a31 = a[13],
+ a32 = a[14],
+ a33 = a[15];
+ var b0 = a00 * a11 - a01 * a10;
+ var b1 = a00 * a12 - a02 * a10;
+ var b2 = a01 * a12 - a02 * a11;
+ var b3 = a20 * a31 - a21 * a30;
+ var b4 = a20 * a32 - a22 * a30;
+ var b5 = a21 * a32 - a22 * a31;
+ var b6 = a00 * b5 - a01 * b4 + a02 * b3;
+ var b7 = a10 * b5 - a11 * b4 + a12 * b3;
+ var b8 = a20 * b2 - a21 * b1 + a22 * b0;
+ var b9 = a30 * b2 - a31 * b1 + a32 * b0; // Calculate the determinant
+
+ return a13 * b6 - a03 * b7 + a33 * b8 - a23 * b9;
+ }
+ /**
+ * Multiplies two mat4s
+ *
+ * @param {mat4} out the receiving matrix
+ * @param {ReadonlyMat4} a the first operand
+ * @param {ReadonlyMat4} b the second operand
+ * @returns {mat4} out
+ */
+
+ function multiply$5(out, a, b) {
+ var a00 = a[0],
+ a01 = a[1],
+ a02 = a[2],
+ a03 = a[3];
+ var a10 = a[4],
+ a11 = a[5],
+ a12 = a[6],
+ a13 = a[7];
+ var a20 = a[8],
+ a21 = a[9],
+ a22 = a[10],
+ a23 = a[11];
+ var a30 = a[12],
+ a31 = a[13],
+ a32 = a[14],
+ a33 = a[15]; // Cache only the current line of the second matrix
+
+ var b0 = b[0],
+ b1 = b[1],
+ b2 = b[2],
+ b3 = b[3];
+ out[0] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30;
+ out[1] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31;
+ out[2] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32;
+ out[3] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33;
+ b0 = b[4];
+ b1 = b[5];
+ b2 = b[6];
+ b3 = b[7];
+ out[4] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30;
+ out[5] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31;
+ out[6] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32;
+ out[7] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33;
+ b0 = b[8];
+ b1 = b[9];
+ b2 = b[10];
+ b3 = b[11];
+ out[8] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30;
+ out[9] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31;
+ out[10] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32;
+ out[11] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33;
+ b0 = b[12];
+ b1 = b[13];
+ b2 = b[14];
+ b3 = b[15];
+ out[12] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30;
+ out[13] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31;
+ out[14] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32;
+ out[15] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33;
+ return out;
+ }
+ /**
+ * Translate a mat4 by the given vector
+ *
+ * @param {mat4} out the receiving matrix
+ * @param {ReadonlyMat4} a the matrix to translate
+ * @param {ReadonlyVec3} v vector to translate by
+ * @returns {mat4} out
+ */
+
+ function translate$1(out, a, v) {
+ var x = v[0],
+ y = v[1],
+ z = v[2];
+ var a00, a01, a02, a03;
+ var a10, a11, a12, a13;
+ var a20, a21, a22, a23;
+
+ if (a === out) {
+ out[12] = a[0] * x + a[4] * y + a[8] * z + a[12];
+ out[13] = a[1] * x + a[5] * y + a[9] * z + a[13];
+ out[14] = a[2] * x + a[6] * y + a[10] * z + a[14];
+ out[15] = a[3] * x + a[7] * y + a[11] * z + a[15];
+ } else {
+ a00 = a[0];
+ a01 = a[1];
+ a02 = a[2];
+ a03 = a[3];
+ a10 = a[4];
+ a11 = a[5];
+ a12 = a[6];
+ a13 = a[7];
+ a20 = a[8];
+ a21 = a[9];
+ a22 = a[10];
+ a23 = a[11];
+ out[0] = a00;
+ out[1] = a01;
+ out[2] = a02;
+ out[3] = a03;
+ out[4] = a10;
+ out[5] = a11;
+ out[6] = a12;
+ out[7] = a13;
+ out[8] = a20;
+ out[9] = a21;
+ out[10] = a22;
+ out[11] = a23;
+ out[12] = a00 * x + a10 * y + a20 * z + a[12];
+ out[13] = a01 * x + a11 * y + a21 * z + a[13];
+ out[14] = a02 * x + a12 * y + a22 * z + a[14];
+ out[15] = a03 * x + a13 * y + a23 * z + a[15];
+ }
+
+ return out;
+ }
+ /**
+ * Scales the mat4 by the dimensions in the given vec3 not using vectorization
+ *
+ * @param {mat4} out the receiving matrix
+ * @param {ReadonlyMat4} a the matrix to scale
+ * @param {ReadonlyVec3} v the vec3 to scale the matrix by
+ * @returns {mat4} out
+ **/
+
+ function scale$5(out, a, v) {
+ var x = v[0],
+ y = v[1],
+ z = v[2];
+ out[0] = a[0] * x;
+ out[1] = a[1] * x;
+ out[2] = a[2] * x;
+ out[3] = a[3] * x;
+ out[4] = a[4] * y;
+ out[5] = a[5] * y;
+ out[6] = a[6] * y;
+ out[7] = a[7] * y;
+ out[8] = a[8] * z;
+ out[9] = a[9] * z;
+ out[10] = a[10] * z;
+ out[11] = a[11] * z;
+ out[12] = a[12];
+ out[13] = a[13];
+ out[14] = a[14];
+ out[15] = a[15];
+ return out;
+ }
+ /**
+ * Rotates a mat4 by the given angle around the given axis
+ *
+ * @param {mat4} out the receiving matrix
+ * @param {ReadonlyMat4} a the matrix to rotate
+ * @param {Number} rad the angle to rotate the matrix by
+ * @param {ReadonlyVec3} axis the axis to rotate around
+ * @returns {mat4} out
+ */
+
+ function rotate$1(out, a, rad, axis) {
+ var x = axis[0],
+ y = axis[1],
+ z = axis[2];
+ var len = Math.hypot(x, y, z);
+ var s, c, t;
+ var a00, a01, a02, a03;
+ var a10, a11, a12, a13;
+ var a20, a21, a22, a23;
+ var b00, b01, b02;
+ var b10, b11, b12;
+ var b20, b21, b22;
+
+ if (len < EPSILON) {
+ return null;
+ }
+
+ len = 1 / len;
+ x *= len;
+ y *= len;
+ z *= len;
+ s = Math.sin(rad);
+ c = Math.cos(rad);
+ t = 1 - c;
+ a00 = a[0];
+ a01 = a[1];
+ a02 = a[2];
+ a03 = a[3];
+ a10 = a[4];
+ a11 = a[5];
+ a12 = a[6];
+ a13 = a[7];
+ a20 = a[8];
+ a21 = a[9];
+ a22 = a[10];
+ a23 = a[11]; // Construct the elements of the rotation matrix
+
+ b00 = x * x * t + c;
+ b01 = y * x * t + z * s;
+ b02 = z * x * t - y * s;
+ b10 = x * y * t - z * s;
+ b11 = y * y * t + c;
+ b12 = z * y * t + x * s;
+ b20 = x * z * t + y * s;
+ b21 = y * z * t - x * s;
+ b22 = z * z * t + c; // Perform rotation-specific matrix multiplication
+
+ out[0] = a00 * b00 + a10 * b01 + a20 * b02;
+ out[1] = a01 * b00 + a11 * b01 + a21 * b02;
+ out[2] = a02 * b00 + a12 * b01 + a22 * b02;
+ out[3] = a03 * b00 + a13 * b01 + a23 * b02;
+ out[4] = a00 * b10 + a10 * b11 + a20 * b12;
+ out[5] = a01 * b10 + a11 * b11 + a21 * b12;
+ out[6] = a02 * b10 + a12 * b11 + a22 * b12;
+ out[7] = a03 * b10 + a13 * b11 + a23 * b12;
+ out[8] = a00 * b20 + a10 * b21 + a20 * b22;
+ out[9] = a01 * b20 + a11 * b21 + a21 * b22;
+ out[10] = a02 * b20 + a12 * b21 + a22 * b22;
+ out[11] = a03 * b20 + a13 * b21 + a23 * b22;
+
+ if (a !== out) {
+ // If the source and destination differ, copy the unchanged last row
+ out[12] = a[12];
+ out[13] = a[13];
+ out[14] = a[14];
+ out[15] = a[15];
+ }
+
+ return out;
+ }
+ /**
+ * Rotates a matrix by the given angle around the X axis
+ *
+ * @param {mat4} out the receiving matrix
+ * @param {ReadonlyMat4} a the matrix to rotate
+ * @param {Number} rad the angle to rotate the matrix by
+ * @returns {mat4} out
+ */
+
+ function rotateX$3(out, a, rad) {
+ var s = Math.sin(rad);
+ var c = Math.cos(rad);
+ var a10 = a[4];
+ var a11 = a[5];
+ var a12 = a[6];
+ var a13 = a[7];
+ var a20 = a[8];
+ var a21 = a[9];
+ var a22 = a[10];
+ var a23 = a[11];
+
+ if (a !== out) {
+ // If the source and destination differ, copy the unchanged rows
+ out[0] = a[0];
+ out[1] = a[1];
+ out[2] = a[2];
+ out[3] = a[3];
+ out[12] = a[12];
+ out[13] = a[13];
+ out[14] = a[14];
+ out[15] = a[15];
+ } // Perform axis-specific matrix multiplication
+
+
+ out[4] = a10 * c + a20 * s;
+ out[5] = a11 * c + a21 * s;
+ out[6] = a12 * c + a22 * s;
+ out[7] = a13 * c + a23 * s;
+ out[8] = a20 * c - a10 * s;
+ out[9] = a21 * c - a11 * s;
+ out[10] = a22 * c - a12 * s;
+ out[11] = a23 * c - a13 * s;
+ return out;
+ }
+ /**
+ * Rotates a matrix by the given angle around the Y axis
+ *
+ * @param {mat4} out the receiving matrix
+ * @param {ReadonlyMat4} a the matrix to rotate
+ * @param {Number} rad the angle to rotate the matrix by
+ * @returns {mat4} out
+ */
+
+ function rotateY$3(out, a, rad) {
+ var s = Math.sin(rad);
+ var c = Math.cos(rad);
+ var a00 = a[0];
+ var a01 = a[1];
+ var a02 = a[2];
+ var a03 = a[3];
+ var a20 = a[8];
+ var a21 = a[9];
+ var a22 = a[10];
+ var a23 = a[11];
+
+ if (a !== out) {
+ // If the source and destination differ, copy the unchanged rows
+ out[4] = a[4];
+ out[5] = a[5];
+ out[6] = a[6];
+ out[7] = a[7];
+ out[12] = a[12];
+ out[13] = a[13];
+ out[14] = a[14];
+ out[15] = a[15];
+ } // Perform axis-specific matrix multiplication
+
+
+ out[0] = a00 * c - a20 * s;
+ out[1] = a01 * c - a21 * s;
+ out[2] = a02 * c - a22 * s;
+ out[3] = a03 * c - a23 * s;
+ out[8] = a00 * s + a20 * c;
+ out[9] = a01 * s + a21 * c;
+ out[10] = a02 * s + a22 * c;
+ out[11] = a03 * s + a23 * c;
+ return out;
+ }
+ /**
+ * Rotates a matrix by the given angle around the Z axis
+ *
+ * @param {mat4} out the receiving matrix
+ * @param {ReadonlyMat4} a the matrix to rotate
+ * @param {Number} rad the angle to rotate the matrix by
+ * @returns {mat4} out
+ */
+
+ function rotateZ$3(out, a, rad) {
+ var s = Math.sin(rad);
+ var c = Math.cos(rad);
+ var a00 = a[0];
+ var a01 = a[1];
+ var a02 = a[2];
+ var a03 = a[3];
+ var a10 = a[4];
+ var a11 = a[5];
+ var a12 = a[6];
+ var a13 = a[7];
+
+ if (a !== out) {
+ // If the source and destination differ, copy the unchanged last row
+ out[8] = a[8];
+ out[9] = a[9];
+ out[10] = a[10];
+ out[11] = a[11];
+ out[12] = a[12];
+ out[13] = a[13];
+ out[14] = a[14];
+ out[15] = a[15];
+ } // Perform axis-specific matrix multiplication
+
+
+ out[0] = a00 * c + a10 * s;
+ out[1] = a01 * c + a11 * s;
+ out[2] = a02 * c + a12 * s;
+ out[3] = a03 * c + a13 * s;
+ out[4] = a10 * c - a00 * s;
+ out[5] = a11 * c - a01 * s;
+ out[6] = a12 * c - a02 * s;
+ out[7] = a13 * c - a03 * s;
+ return out;
+ }
+ /**
+ * Creates a matrix from a vector translation
+ * This is equivalent to (but much faster than):
+ *
+ * mat4.identity(dest);
+ * mat4.translate(dest, dest, vec);
+ *
+ * @param {mat4} out mat4 receiving operation result
+ * @param {ReadonlyVec3} v Translation vector
+ * @returns {mat4} out
+ */
+
+ function fromTranslation$1(out, v) {
+ out[0] = 1;
+ out[1] = 0;
+ out[2] = 0;
+ out[3] = 0;
+ out[4] = 0;
+ out[5] = 1;
+ out[6] = 0;
+ out[7] = 0;
+ out[8] = 0;
+ out[9] = 0;
+ out[10] = 1;
+ out[11] = 0;
+ out[12] = v[0];
+ out[13] = v[1];
+ out[14] = v[2];
+ out[15] = 1;
+ return out;
+ }
+ /**
+ * Creates a matrix from a vector scaling
+ * This is equivalent to (but much faster than):
+ *
+ * mat4.identity(dest);
+ * mat4.scale(dest, dest, vec);
+ *
+ * @param {mat4} out mat4 receiving operation result
+ * @param {ReadonlyVec3} v Scaling vector
+ * @returns {mat4} out
+ */
+
+ function fromScaling(out, v) {
+ out[0] = v[0];
+ out[1] = 0;
+ out[2] = 0;
+ out[3] = 0;
+ out[4] = 0;
+ out[5] = v[1];
+ out[6] = 0;
+ out[7] = 0;
+ out[8] = 0;
+ out[9] = 0;
+ out[10] = v[2];
+ out[11] = 0;
+ out[12] = 0;
+ out[13] = 0;
+ out[14] = 0;
+ out[15] = 1;
+ return out;
+ }
+ /**
+ * Creates a matrix from a given angle around a given axis
+ * This is equivalent to (but much faster than):
+ *
+ * mat4.identity(dest);
+ * mat4.rotate(dest, dest, rad, axis);
+ *
+ * @param {mat4} out mat4 receiving operation result
+ * @param {Number} rad the angle to rotate the matrix by
+ * @param {ReadonlyVec3} axis the axis to rotate around
+ * @returns {mat4} out
+ */
+
+ function fromRotation$1(out, rad, axis) {
+ var x = axis[0],
+ y = axis[1],
+ z = axis[2];
+ var len = Math.hypot(x, y, z);
+ var s, c, t;
+
+ if (len < EPSILON) {
+ return null;
+ }
+
+ len = 1 / len;
+ x *= len;
+ y *= len;
+ z *= len;
+ s = Math.sin(rad);
+ c = Math.cos(rad);
+ t = 1 - c; // Perform rotation-specific matrix multiplication
+
+ out[0] = x * x * t + c;
+ out[1] = y * x * t + z * s;
+ out[2] = z * x * t - y * s;
+ out[3] = 0;
+ out[4] = x * y * t - z * s;
+ out[5] = y * y * t + c;
+ out[6] = z * y * t + x * s;
+ out[7] = 0;
+ out[8] = x * z * t + y * s;
+ out[9] = y * z * t - x * s;
+ out[10] = z * z * t + c;
+ out[11] = 0;
+ out[12] = 0;
+ out[13] = 0;
+ out[14] = 0;
+ out[15] = 1;
+ return out;
+ }
+ /**
+ * Creates a matrix from the given angle around the X axis
+ * This is equivalent to (but much faster than):
+ *
+ * mat4.identity(dest);
+ * mat4.rotateX(dest, dest, rad);
+ *
+ * @param {mat4} out mat4 receiving operation result
+ * @param {Number} rad the angle to rotate the matrix by
+ * @returns {mat4} out
+ */
+
+ function fromXRotation(out, rad) {
+ var s = Math.sin(rad);
+ var c = Math.cos(rad); // Perform axis-specific matrix multiplication
+
+ out[0] = 1;
+ out[1] = 0;
+ out[2] = 0;
+ out[3] = 0;
+ out[4] = 0;
+ out[5] = c;
+ out[6] = s;
+ out[7] = 0;
+ out[8] = 0;
+ out[9] = -s;
+ out[10] = c;
+ out[11] = 0;
+ out[12] = 0;
+ out[13] = 0;
+ out[14] = 0;
+ out[15] = 1;
+ return out;
+ }
+ /**
+ * Creates a matrix from the given angle around the Y axis
+ * This is equivalent to (but much faster than):
+ *
+ * mat4.identity(dest);
+ * mat4.rotateY(dest, dest, rad);
+ *
+ * @param {mat4} out mat4 receiving operation result
+ * @param {Number} rad the angle to rotate the matrix by
+ * @returns {mat4} out
+ */
+
+ function fromYRotation(out, rad) {
+ var s = Math.sin(rad);
+ var c = Math.cos(rad); // Perform axis-specific matrix multiplication
+
+ out[0] = c;
+ out[1] = 0;
+ out[2] = -s;
+ out[3] = 0;
+ out[4] = 0;
+ out[5] = 1;
+ out[6] = 0;
+ out[7] = 0;
+ out[8] = s;
+ out[9] = 0;
+ out[10] = c;
+ out[11] = 0;
+ out[12] = 0;
+ out[13] = 0;
+ out[14] = 0;
+ out[15] = 1;
+ return out;
+ }
+ /**
+ * Creates a matrix from the given angle around the Z axis
+ * This is equivalent to (but much faster than):
+ *
+ * mat4.identity(dest);
+ * mat4.rotateZ(dest, dest, rad);
+ *
+ * @param {mat4} out mat4 receiving operation result
+ * @param {Number} rad the angle to rotate the matrix by
+ * @returns {mat4} out
+ */
+
+ function fromZRotation(out, rad) {
+ var s = Math.sin(rad);
+ var c = Math.cos(rad); // Perform axis-specific matrix multiplication
+
+ out[0] = c;
+ out[1] = s;
+ out[2] = 0;
+ out[3] = 0;
+ out[4] = -s;
+ out[5] = c;
+ out[6] = 0;
+ out[7] = 0;
+ out[8] = 0;
+ out[9] = 0;
+ out[10] = 1;
+ out[11] = 0;
+ out[12] = 0;
+ out[13] = 0;
+ out[14] = 0;
+ out[15] = 1;
+ return out;
+ }
+ /**
+ * Creates a matrix from a quaternion rotation and vector translation
+ * This is equivalent to (but much faster than):
+ *
+ * mat4.identity(dest);
+ * mat4.translate(dest, vec);
+ * let quatMat = mat4.create();
+ * quat4.toMat4(quat, quatMat);
+ * mat4.multiply(dest, quatMat);
+ *
+ * @param {mat4} out mat4 receiving operation result
+ * @param {quat4} q Rotation quaternion
+ * @param {ReadonlyVec3} v Translation vector
+ * @returns {mat4} out
+ */
+
+ function fromRotationTranslation$1(out, q, v) {
+ // Quaternion math
+ var x = q[0],
+ y = q[1],
+ z = q[2],
+ w = q[3];
+ var x2 = x + x;
+ var y2 = y + y;
+ var z2 = z + z;
+ var xx = x * x2;
+ var xy = x * y2;
+ var xz = x * z2;
+ var yy = y * y2;
+ var yz = y * z2;
+ var zz = z * z2;
+ var wx = w * x2;
+ var wy = w * y2;
+ var wz = w * z2;
+ out[0] = 1 - (yy + zz);
+ out[1] = xy + wz;
+ out[2] = xz - wy;
+ out[3] = 0;
+ out[4] = xy - wz;
+ out[5] = 1 - (xx + zz);
+ out[6] = yz + wx;
+ out[7] = 0;
+ out[8] = xz + wy;
+ out[9] = yz - wx;
+ out[10] = 1 - (xx + yy);
+ out[11] = 0;
+ out[12] = v[0];
+ out[13] = v[1];
+ out[14] = v[2];
+ out[15] = 1;
+ return out;
+ }
+ /**
+ * Creates a new mat4 from a dual quat.
+ *
+ * @param {mat4} out Matrix
+ * @param {ReadonlyQuat2} a Dual Quaternion
+ * @returns {mat4} mat4 receiving operation result
+ */
+
+ function fromQuat2(out, a) {
+ var translation = new ARRAY_TYPE(3);
+ var bx = -a[0],
+ by = -a[1],
+ bz = -a[2],
+ bw = a[3],
+ ax = a[4],
+ ay = a[5],
+ az = a[6],
+ aw = a[7];
+ var magnitude = bx * bx + by * by + bz * bz + bw * bw; //Only scale if it makes sense
+
+ if (magnitude > 0) {
+ translation[0] = (ax * bw + aw * bx + ay * bz - az * by) * 2 / magnitude;
+ translation[1] = (ay * bw + aw * by + az * bx - ax * bz) * 2 / magnitude;
+ translation[2] = (az * bw + aw * bz + ax * by - ay * bx) * 2 / magnitude;
+ } else {
+ translation[0] = (ax * bw + aw * bx + ay * bz - az * by) * 2;
+ translation[1] = (ay * bw + aw * by + az * bx - ax * bz) * 2;
+ translation[2] = (az * bw + aw * bz + ax * by - ay * bx) * 2;
+ }
+
+ fromRotationTranslation$1(out, a, translation);
+ return out;
+ }
+ /**
+ * Returns the translation vector component of a transformation
+ * matrix. If a matrix is built with fromRotationTranslation,
+ * the returned vector will be the same as the translation vector
+ * originally supplied.
+ * @param {vec3} out Vector to receive translation component
+ * @param {ReadonlyMat4} mat Matrix to be decomposed (input)
+ * @return {vec3} out
+ */
+
+ function getTranslation$1(out, mat) {
+ out[0] = mat[12];
+ out[1] = mat[13];
+ out[2] = mat[14];
+ return out;
+ }
+ /**
+ * Returns the scaling factor component of a transformation
+ * matrix. If a matrix is built with fromRotationTranslationScale
+ * with a normalized Quaternion paramter, the returned vector will be
+ * the same as the scaling vector
+ * originally supplied.
+ * @param {vec3} out Vector to receive scaling factor component
+ * @param {ReadonlyMat4} mat Matrix to be decomposed (input)
+ * @return {vec3} out
+ */
+
+ function getScaling(out, mat) {
+ var m11 = mat[0];
+ var m12 = mat[1];
+ var m13 = mat[2];
+ var m21 = mat[4];
+ var m22 = mat[5];
+ var m23 = mat[6];
+ var m31 = mat[8];
+ var m32 = mat[9];
+ var m33 = mat[10];
+ out[0] = Math.hypot(m11, m12, m13);
+ out[1] = Math.hypot(m21, m22, m23);
+ out[2] = Math.hypot(m31, m32, m33);
+ return out;
+ }
+ /**
+ * Returns a quaternion representing the rotational component
+ * of a transformation matrix. If a matrix is built with
+ * fromRotationTranslation, the returned quaternion will be the
+ * same as the quaternion originally supplied.
+ * @param {quat} out Quaternion to receive the rotation component
+ * @param {ReadonlyMat4} mat Matrix to be decomposed (input)
+ * @return {quat} out
+ */
+
+ function getRotation(out, mat) {
+ var scaling = new ARRAY_TYPE(3);
+ getScaling(scaling, mat);
+ var is1 = 1 / scaling[0];
+ var is2 = 1 / scaling[1];
+ var is3 = 1 / scaling[2];
+ var sm11 = mat[0] * is1;
+ var sm12 = mat[1] * is2;
+ var sm13 = mat[2] * is3;
+ var sm21 = mat[4] * is1;
+ var sm22 = mat[5] * is2;
+ var sm23 = mat[6] * is3;
+ var sm31 = mat[8] * is1;
+ var sm32 = mat[9] * is2;
+ var sm33 = mat[10] * is3;
+ var trace = sm11 + sm22 + sm33;
+ var S = 0;
+
+ if (trace > 0) {
+ S = Math.sqrt(trace + 1.0) * 2;
+ out[3] = 0.25 * S;
+ out[0] = (sm23 - sm32) / S;
+ out[1] = (sm31 - sm13) / S;
+ out[2] = (sm12 - sm21) / S;
+ } else if (sm11 > sm22 && sm11 > sm33) {
+ S = Math.sqrt(1.0 + sm11 - sm22 - sm33) * 2;
+ out[3] = (sm23 - sm32) / S;
+ out[0] = 0.25 * S;
+ out[1] = (sm12 + sm21) / S;
+ out[2] = (sm31 + sm13) / S;
+ } else if (sm22 > sm33) {
+ S = Math.sqrt(1.0 + sm22 - sm11 - sm33) * 2;
+ out[3] = (sm31 - sm13) / S;
+ out[0] = (sm12 + sm21) / S;
+ out[1] = 0.25 * S;
+ out[2] = (sm23 + sm32) / S;
+ } else {
+ S = Math.sqrt(1.0 + sm33 - sm11 - sm22) * 2;
+ out[3] = (sm12 - sm21) / S;
+ out[0] = (sm31 + sm13) / S;
+ out[1] = (sm23 + sm32) / S;
+ out[2] = 0.25 * S;
+ }
+
+ return out;
+ }
+ /**
+ * Decomposes a transformation matrix into its rotation, translation
+ * and scale components. Returns only the rotation component
+ * @param {quat} out_r Quaternion to receive the rotation component
+ * @param {vec3} out_t Vector to receive the translation vector
+ * @param {vec3} out_s Vector to receive the scaling factor
+ * @param {ReadonlyMat4} mat Matrix to be decomposed (input)
+ * @returns {quat} out_r
+ */
+
+ function decompose(out_r, out_t, out_s, mat) {
+ out_t[0] = mat[12];
+ out_t[1] = mat[13];
+ out_t[2] = mat[14];
+ var m11 = mat[0];
+ var m12 = mat[1];
+ var m13 = mat[2];
+ var m21 = mat[4];
+ var m22 = mat[5];
+ var m23 = mat[6];
+ var m31 = mat[8];
+ var m32 = mat[9];
+ var m33 = mat[10];
+ out_s[0] = Math.hypot(m11, m12, m13);
+ out_s[1] = Math.hypot(m21, m22, m23);
+ out_s[2] = Math.hypot(m31, m32, m33);
+ var is1 = 1 / out_s[0];
+ var is2 = 1 / out_s[1];
+ var is3 = 1 / out_s[2];
+ var sm11 = m11 * is1;
+ var sm12 = m12 * is2;
+ var sm13 = m13 * is3;
+ var sm21 = m21 * is1;
+ var sm22 = m22 * is2;
+ var sm23 = m23 * is3;
+ var sm31 = m31 * is1;
+ var sm32 = m32 * is2;
+ var sm33 = m33 * is3;
+ var trace = sm11 + sm22 + sm33;
+ var S = 0;
+
+ if (trace > 0) {
+ S = Math.sqrt(trace + 1.0) * 2;
+ out_r[3] = 0.25 * S;
+ out_r[0] = (sm23 - sm32) / S;
+ out_r[1] = (sm31 - sm13) / S;
+ out_r[2] = (sm12 - sm21) / S;
+ } else if (sm11 > sm22 && sm11 > sm33) {
+ S = Math.sqrt(1.0 + sm11 - sm22 - sm33) * 2;
+ out_r[3] = (sm23 - sm32) / S;
+ out_r[0] = 0.25 * S;
+ out_r[1] = (sm12 + sm21) / S;
+ out_r[2] = (sm31 + sm13) / S;
+ } else if (sm22 > sm33) {
+ S = Math.sqrt(1.0 + sm22 - sm11 - sm33) * 2;
+ out_r[3] = (sm31 - sm13) / S;
+ out_r[0] = (sm12 + sm21) / S;
+ out_r[1] = 0.25 * S;
+ out_r[2] = (sm23 + sm32) / S;
+ } else {
+ S = Math.sqrt(1.0 + sm33 - sm11 - sm22) * 2;
+ out_r[3] = (sm12 - sm21) / S;
+ out_r[0] = (sm31 + sm13) / S;
+ out_r[1] = (sm23 + sm32) / S;
+ out_r[2] = 0.25 * S;
+ }
+
+ return out_r;
+ }
+ /**
+ * Creates a matrix from a quaternion rotation, vector translation and vector scale
+ * This is equivalent to (but much faster than):
+ *
+ * mat4.identity(dest);
+ * mat4.translate(dest, vec);
+ * let quatMat = mat4.create();
+ * quat4.toMat4(quat, quatMat);
+ * mat4.multiply(dest, quatMat);
+ * mat4.scale(dest, scale)
+ *
+ * @param {mat4} out mat4 receiving operation result
+ * @param {quat4} q Rotation quaternion
+ * @param {ReadonlyVec3} v Translation vector
+ * @param {ReadonlyVec3} s Scaling vector
+ * @returns {mat4} out
+ */
+
+ function fromRotationTranslationScale(out, q, v, s) {
+ // Quaternion math
+ var x = q[0],
+ y = q[1],
+ z = q[2],
+ w = q[3];
+ var x2 = x + x;
+ var y2 = y + y;
+ var z2 = z + z;
+ var xx = x * x2;
+ var xy = x * y2;
+ var xz = x * z2;
+ var yy = y * y2;
+ var yz = y * z2;
+ var zz = z * z2;
+ var wx = w * x2;
+ var wy = w * y2;
+ var wz = w * z2;
+ var sx = s[0];
+ var sy = s[1];
+ var sz = s[2];
+ out[0] = (1 - (yy + zz)) * sx;
+ out[1] = (xy + wz) * sx;
+ out[2] = (xz - wy) * sx;
+ out[3] = 0;
+ out[4] = (xy - wz) * sy;
+ out[5] = (1 - (xx + zz)) * sy;
+ out[6] = (yz + wx) * sy;
+ out[7] = 0;
+ out[8] = (xz + wy) * sz;
+ out[9] = (yz - wx) * sz;
+ out[10] = (1 - (xx + yy)) * sz;
+ out[11] = 0;
+ out[12] = v[0];
+ out[13] = v[1];
+ out[14] = v[2];
+ out[15] = 1;
+ return out;
+ }
+ /**
+ * Creates a matrix from a quaternion rotation, vector translation and vector scale, rotating and scaling around the given origin
+ * This is equivalent to (but much faster than):
+ *
+ * mat4.identity(dest);
+ * mat4.translate(dest, vec);
+ * mat4.translate(dest, origin);
+ * let quatMat = mat4.create();
+ * quat4.toMat4(quat, quatMat);
+ * mat4.multiply(dest, quatMat);
+ * mat4.scale(dest, scale)
+ * mat4.translate(dest, negativeOrigin);
+ *
+ * @param {mat4} out mat4 receiving operation result
+ * @param {quat4} q Rotation quaternion
+ * @param {ReadonlyVec3} v Translation vector
+ * @param {ReadonlyVec3} s Scaling vector
+ * @param {ReadonlyVec3} o The origin vector around which to scale and rotate
+ * @returns {mat4} out
+ */
+
+ function fromRotationTranslationScaleOrigin(out, q, v, s, o) {
+ // Quaternion math
+ var x = q[0],
+ y = q[1],
+ z = q[2],
+ w = q[3];
+ var x2 = x + x;
+ var y2 = y + y;
+ var z2 = z + z;
+ var xx = x * x2;
+ var xy = x * y2;
+ var xz = x * z2;
+ var yy = y * y2;
+ var yz = y * z2;
+ var zz = z * z2;
+ var wx = w * x2;
+ var wy = w * y2;
+ var wz = w * z2;
+ var sx = s[0];
+ var sy = s[1];
+ var sz = s[2];
+ var ox = o[0];
+ var oy = o[1];
+ var oz = o[2];
+ var out0 = (1 - (yy + zz)) * sx;
+ var out1 = (xy + wz) * sx;
+ var out2 = (xz - wy) * sx;
+ var out4 = (xy - wz) * sy;
+ var out5 = (1 - (xx + zz)) * sy;
+ var out6 = (yz + wx) * sy;
+ var out8 = (xz + wy) * sz;
+ var out9 = (yz - wx) * sz;
+ var out10 = (1 - (xx + yy)) * sz;
+ out[0] = out0;
+ out[1] = out1;
+ out[2] = out2;
+ out[3] = 0;
+ out[4] = out4;
+ out[5] = out5;
+ out[6] = out6;
+ out[7] = 0;
+ out[8] = out8;
+ out[9] = out9;
+ out[10] = out10;
+ out[11] = 0;
+ out[12] = v[0] + ox - (out0 * ox + out4 * oy + out8 * oz);
+ out[13] = v[1] + oy - (out1 * ox + out5 * oy + out9 * oz);
+ out[14] = v[2] + oz - (out2 * ox + out6 * oy + out10 * oz);
+ out[15] = 1;
+ return out;
+ }
+ /**
+ * Calculates a 4x4 matrix from the given quaternion
+ *
+ * @param {mat4} out mat4 receiving operation result
+ * @param {ReadonlyQuat} q Quaternion to create matrix from
+ *
+ * @returns {mat4} out
+ */
+
+ function fromQuat(out, q) {
+ var x = q[0],
+ y = q[1],
+ z = q[2],
+ w = q[3];
+ var x2 = x + x;
+ var y2 = y + y;
+ var z2 = z + z;
+ var xx = x * x2;
+ var yx = y * x2;
+ var yy = y * y2;
+ var zx = z * x2;
+ var zy = z * y2;
+ var zz = z * z2;
+ var wx = w * x2;
+ var wy = w * y2;
+ var wz = w * z2;
+ out[0] = 1 - yy - zz;
+ out[1] = yx + wz;
+ out[2] = zx - wy;
+ out[3] = 0;
+ out[4] = yx - wz;
+ out[5] = 1 - xx - zz;
+ out[6] = zy + wx;
+ out[7] = 0;
+ out[8] = zx + wy;
+ out[9] = zy - wx;
+ out[10] = 1 - xx - yy;
+ out[11] = 0;
+ out[12] = 0;
+ out[13] = 0;
+ out[14] = 0;
+ out[15] = 1;
+ return out;
+ }
+ /**
+ * Generates a frustum matrix with the given bounds
+ *
+ * @param {mat4} out mat4 frustum matrix will be written into
+ * @param {Number} left Left bound of the frustum
+ * @param {Number} right Right bound of the frustum
+ * @param {Number} bottom Bottom bound of the frustum
+ * @param {Number} top Top bound of the frustum
+ * @param {Number} near Near bound of the frustum
+ * @param {Number} far Far bound of the frustum
+ * @returns {mat4} out
+ */
+
+ function frustum(out, left, right, bottom, top, near, far) {
+ var rl = 1 / (right - left);
+ var tb = 1 / (top - bottom);
+ var nf = 1 / (near - far);
+ out[0] = near * 2 * rl;
+ out[1] = 0;
+ out[2] = 0;
+ out[3] = 0;
+ out[4] = 0;
+ out[5] = near * 2 * tb;
+ out[6] = 0;
+ out[7] = 0;
+ out[8] = (right + left) * rl;
+ out[9] = (top + bottom) * tb;
+ out[10] = (far + near) * nf;
+ out[11] = -1;
+ out[12] = 0;
+ out[13] = 0;
+ out[14] = far * near * 2 * nf;
+ out[15] = 0;
+ return out;
+ }
+ /**
+ * Generates a perspective projection matrix with the given bounds.
+ * The near/far clip planes correspond to a normalized device coordinate Z range of [-1, 1],
+ * which matches WebGL/OpenGL's clip volume.
+ * Passing null/undefined/no value for far will generate infinite projection matrix.
+ *
+ * @param {mat4} out mat4 frustum matrix will be written into
+ * @param {number} fovy Vertical field of view in radians
+ * @param {number} aspect Aspect ratio. typically viewport width/height
+ * @param {number} near Near bound of the frustum
+ * @param {number} far Far bound of the frustum, can be null or Infinity
+ * @returns {mat4} out
+ */
+
+ function perspectiveNO(out, fovy, aspect, near, far) {
+ var f = 1.0 / Math.tan(fovy / 2);
+ out[0] = f / aspect;
+ out[1] = 0;
+ out[2] = 0;
+ out[3] = 0;
+ out[4] = 0;
+ out[5] = f;
+ out[6] = 0;
+ out[7] = 0;
+ out[8] = 0;
+ out[9] = 0;
+ out[11] = -1;
+ out[12] = 0;
+ out[13] = 0;
+ out[15] = 0;
+
+ if (far != null && far !== Infinity) {
+ var nf = 1 / (near - far);
+ out[10] = (far + near) * nf;
+ out[14] = 2 * far * near * nf;
+ } else {
+ out[10] = -1;
+ out[14] = -2 * near;
+ }
+
+ return out;
+ }
+ /**
+ * Alias for {@link mat4.perspectiveNO}
+ * @function
+ */
+
+ var perspective = perspectiveNO;
+ /**
+ * Generates a perspective projection matrix suitable for WebGPU with the given bounds.
+ * The near/far clip planes correspond to a normalized device coordinate Z range of [0, 1],
+ * which matches WebGPU/Vulkan/DirectX/Metal's clip volume.
+ * Passing null/undefined/no value for far will generate infinite projection matrix.
+ *
+ * @param {mat4} out mat4 frustum matrix will be written into
+ * @param {number} fovy Vertical field of view in radians
+ * @param {number} aspect Aspect ratio. typically viewport width/height
+ * @param {number} near Near bound of the frustum
+ * @param {number} far Far bound of the frustum, can be null or Infinity
+ * @returns {mat4} out
+ */
+
+ function perspectiveZO(out, fovy, aspect, near, far) {
+ var f = 1.0 / Math.tan(fovy / 2);
+ out[0] = f / aspect;
+ out[1] = 0;
+ out[2] = 0;
+ out[3] = 0;
+ out[4] = 0;
+ out[5] = f;
+ out[6] = 0;
+ out[7] = 0;
+ out[8] = 0;
+ out[9] = 0;
+ out[11] = -1;
+ out[12] = 0;
+ out[13] = 0;
+ out[15] = 0;
+
+ if (far != null && far !== Infinity) {
+ var nf = 1 / (near - far);
+ out[10] = far * nf;
+ out[14] = far * near * nf;
+ } else {
+ out[10] = -1;
+ out[14] = -near;
+ }
+
+ return out;
+ }
+ /**
+ * Generates a perspective projection matrix with the given field of view.
+ * This is primarily useful for generating projection matrices to be used
+ * with the still experiemental WebVR API.
+ *
+ * @param {mat4} out mat4 frustum matrix will be written into
+ * @param {Object} fov Object containing the following values: upDegrees, downDegrees, leftDegrees, rightDegrees
+ * @param {number} near Near bound of the frustum
+ * @param {number} far Far bound of the frustum
+ * @returns {mat4} out
+ */
+
+ function perspectiveFromFieldOfView(out, fov, near, far) {
+ var upTan = Math.tan(fov.upDegrees * Math.PI / 180.0);
+ var downTan = Math.tan(fov.downDegrees * Math.PI / 180.0);
+ var leftTan = Math.tan(fov.leftDegrees * Math.PI / 180.0);
+ var rightTan = Math.tan(fov.rightDegrees * Math.PI / 180.0);
+ var xScale = 2.0 / (leftTan + rightTan);
+ var yScale = 2.0 / (upTan + downTan);
+ out[0] = xScale;
+ out[1] = 0.0;
+ out[2] = 0.0;
+ out[3] = 0.0;
+ out[4] = 0.0;
+ out[5] = yScale;
+ out[6] = 0.0;
+ out[7] = 0.0;
+ out[8] = -((leftTan - rightTan) * xScale * 0.5);
+ out[9] = (upTan - downTan) * yScale * 0.5;
+ out[10] = far / (near - far);
+ out[11] = -1.0;
+ out[12] = 0.0;
+ out[13] = 0.0;
+ out[14] = far * near / (near - far);
+ out[15] = 0.0;
+ return out;
+ }
+ /**
+ * Generates a orthogonal projection matrix with the given bounds.
+ * The near/far clip planes correspond to a normalized device coordinate Z range of [-1, 1],
+ * which matches WebGL/OpenGL's clip volume.
+ *
+ * @param {mat4} out mat4 frustum matrix will be written into
+ * @param {number} left Left bound of the frustum
+ * @param {number} right Right bound of the frustum
+ * @param {number} bottom Bottom bound of the frustum
+ * @param {number} top Top bound of the frustum
+ * @param {number} near Near bound of the frustum
+ * @param {number} far Far bound of the frustum
+ * @returns {mat4} out
+ */
+
+ function orthoNO(out, left, right, bottom, top, near, far) {
+ var lr = 1 / (left - right);
+ var bt = 1 / (bottom - top);
+ var nf = 1 / (near - far);
+ out[0] = -2 * lr;
+ out[1] = 0;
+ out[2] = 0;
+ out[3] = 0;
+ out[4] = 0;
+ out[5] = -2 * bt;
+ out[6] = 0;
+ out[7] = 0;
+ out[8] = 0;
+ out[9] = 0;
+ out[10] = 2 * nf;
+ out[11] = 0;
+ out[12] = (left + right) * lr;
+ out[13] = (top + bottom) * bt;
+ out[14] = (far + near) * nf;
+ out[15] = 1;
+ return out;
+ }
+ /**
+ * Alias for {@link mat4.orthoNO}
+ * @function
+ */
+
+ var ortho = orthoNO;
+ /**
+ * Generates a orthogonal projection matrix with the given bounds.
+ * The near/far clip planes correspond to a normalized device coordinate Z range of [0, 1],
+ * which matches WebGPU/Vulkan/DirectX/Metal's clip volume.
+ *
+ * @param {mat4} out mat4 frustum matrix will be written into
+ * @param {number} left Left bound of the frustum
+ * @param {number} right Right bound of the frustum
+ * @param {number} bottom Bottom bound of the frustum
+ * @param {number} top Top bound of the frustum
+ * @param {number} near Near bound of the frustum
+ * @param {number} far Far bound of the frustum
+ * @returns {mat4} out
+ */
+
+ function orthoZO(out, left, right, bottom, top, near, far) {
+ var lr = 1 / (left - right);
+ var bt = 1 / (bottom - top);
+ var nf = 1 / (near - far);
+ out[0] = -2 * lr;
+ out[1] = 0;
+ out[2] = 0;
+ out[3] = 0;
+ out[4] = 0;
+ out[5] = -2 * bt;
+ out[6] = 0;
+ out[7] = 0;
+ out[8] = 0;
+ out[9] = 0;
+ out[10] = nf;
+ out[11] = 0;
+ out[12] = (left + right) * lr;
+ out[13] = (top + bottom) * bt;
+ out[14] = near * nf;
+ out[15] = 1;
+ return out;
+ }
+ /**
+ * Generates a look-at matrix with the given eye position, focal point, and up axis.
+ * If you want a matrix that actually makes an object look at another object, you should use targetTo instead.
+ *
+ * @param {mat4} out mat4 frustum matrix will be written into
+ * @param {ReadonlyVec3} eye Position of the viewer
+ * @param {ReadonlyVec3} center Point the viewer is looking at
+ * @param {ReadonlyVec3} up vec3 pointing up
+ * @returns {mat4} out
+ */
+
+ function lookAt(out, eye, center, up) {
+ var x0, x1, x2, y0, y1, y2, z0, z1, z2, len;
+ var eyex = eye[0];
+ var eyey = eye[1];
+ var eyez = eye[2];
+ var upx = up[0];
+ var upy = up[1];
+ var upz = up[2];
+ var centerx = center[0];
+ var centery = center[1];
+ var centerz = center[2];
+
+ if (Math.abs(eyex - centerx) < EPSILON && Math.abs(eyey - centery) < EPSILON && Math.abs(eyez - centerz) < EPSILON) {
+ return identity$2(out);
+ }
+
+ z0 = eyex - centerx;
+ z1 = eyey - centery;
+ z2 = eyez - centerz;
+ len = 1 / Math.hypot(z0, z1, z2);
+ z0 *= len;
+ z1 *= len;
+ z2 *= len;
+ x0 = upy * z2 - upz * z1;
+ x1 = upz * z0 - upx * z2;
+ x2 = upx * z1 - upy * z0;
+ len = Math.hypot(x0, x1, x2);
+
+ if (!len) {
+ x0 = 0;
+ x1 = 0;
+ x2 = 0;
+ } else {
+ len = 1 / len;
+ x0 *= len;
+ x1 *= len;
+ x2 *= len;
+ }
+
+ y0 = z1 * x2 - z2 * x1;
+ y1 = z2 * x0 - z0 * x2;
+ y2 = z0 * x1 - z1 * x0;
+ len = Math.hypot(y0, y1, y2);
+
+ if (!len) {
+ y0 = 0;
+ y1 = 0;
+ y2 = 0;
+ } else {
+ len = 1 / len;
+ y0 *= len;
+ y1 *= len;
+ y2 *= len;
+ }
+
+ out[0] = x0;
+ out[1] = y0;
+ out[2] = z0;
+ out[3] = 0;
+ out[4] = x1;
+ out[5] = y1;
+ out[6] = z1;
+ out[7] = 0;
+ out[8] = x2;
+ out[9] = y2;
+ out[10] = z2;
+ out[11] = 0;
+ out[12] = -(x0 * eyex + x1 * eyey + x2 * eyez);
+ out[13] = -(y0 * eyex + y1 * eyey + y2 * eyez);
+ out[14] = -(z0 * eyex + z1 * eyey + z2 * eyez);
+ out[15] = 1;
+ return out;
+ }
+ /**
+ * Generates a matrix that makes something look at something else.
+ *
+ * @param {mat4} out mat4 frustum matrix will be written into
+ * @param {ReadonlyVec3} eye Position of the viewer
+ * @param {ReadonlyVec3} center Point the viewer is looking at
+ * @param {ReadonlyVec3} up vec3 pointing up
+ * @returns {mat4} out
+ */
+
+ function targetTo(out, eye, target, up) {
+ var eyex = eye[0],
+ eyey = eye[1],
+ eyez = eye[2],
+ upx = up[0],
+ upy = up[1],
+ upz = up[2];
+ var z0 = eyex - target[0],
+ z1 = eyey - target[1],
+ z2 = eyez - target[2];
+ var len = z0 * z0 + z1 * z1 + z2 * z2;
+
+ if (len > 0) {
+ len = 1 / Math.sqrt(len);
+ z0 *= len;
+ z1 *= len;
+ z2 *= len;
+ }
+
+ var x0 = upy * z2 - upz * z1,
+ x1 = upz * z0 - upx * z2,
+ x2 = upx * z1 - upy * z0;
+ len = x0 * x0 + x1 * x1 + x2 * x2;
+
+ if (len > 0) {
+ len = 1 / Math.sqrt(len);
+ x0 *= len;
+ x1 *= len;
+ x2 *= len;
+ }
+
+ out[0] = x0;
+ out[1] = x1;
+ out[2] = x2;
+ out[3] = 0;
+ out[4] = z1 * x2 - z2 * x1;
+ out[5] = z2 * x0 - z0 * x2;
+ out[6] = z0 * x1 - z1 * x0;
+ out[7] = 0;
+ out[8] = z0;
+ out[9] = z1;
+ out[10] = z2;
+ out[11] = 0;
+ out[12] = eyex;
+ out[13] = eyey;
+ out[14] = eyez;
+ out[15] = 1;
+ return out;
+ }
+ /**
+ * Returns a string representation of a mat4
+ *
+ * @param {ReadonlyMat4} a matrix to represent as a string
+ * @returns {String} string representation of the matrix
+ */
+
+ function str$5(a) {
+ return "mat4(" + a[0] + ", " + a[1] + ", " + a[2] + ", " + a[3] + ", " + a[4] + ", " + a[5] + ", " + a[6] + ", " + a[7] + ", " + a[8] + ", " + a[9] + ", " + a[10] + ", " + a[11] + ", " + a[12] + ", " + a[13] + ", " + a[14] + ", " + a[15] + ")";
+ }
+ /**
+ * Returns Frobenius norm of a mat4
+ *
+ * @param {ReadonlyMat4} a the matrix to calculate Frobenius norm of
+ * @returns {Number} Frobenius norm
+ */
+
+ function frob(a) {
+ return Math.hypot(a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], a[11], a[12], a[13], a[14], a[15]);
+ }
+ /**
+ * Adds two mat4's
+ *
+ * @param {mat4} out the receiving matrix
+ * @param {ReadonlyMat4} a the first operand
+ * @param {ReadonlyMat4} b the second operand
+ * @returns {mat4} out
+ */
+
+ function add$5(out, a, b) {
+ out[0] = a[0] + b[0];
+ out[1] = a[1] + b[1];
+ out[2] = a[2] + b[2];
+ out[3] = a[3] + b[3];
+ out[4] = a[4] + b[4];
+ out[5] = a[5] + b[5];
+ out[6] = a[6] + b[6];
+ out[7] = a[7] + b[7];
+ out[8] = a[8] + b[8];
+ out[9] = a[9] + b[9];
+ out[10] = a[10] + b[10];
+ out[11] = a[11] + b[11];
+ out[12] = a[12] + b[12];
+ out[13] = a[13] + b[13];
+ out[14] = a[14] + b[14];
+ out[15] = a[15] + b[15];
+ return out;
+ }
+ /**
+ * Subtracts matrix b from matrix a
+ *
+ * @param {mat4} out the receiving matrix
+ * @param {ReadonlyMat4} a the first operand
+ * @param {ReadonlyMat4} b the second operand
+ * @returns {mat4} out
+ */
+
+ function subtract$3(out, a, b) {
+ out[0] = a[0] - b[0];
+ out[1] = a[1] - b[1];
+ out[2] = a[2] - b[2];
+ out[3] = a[3] - b[3];
+ out[4] = a[4] - b[4];
+ out[5] = a[5] - b[5];
+ out[6] = a[6] - b[6];
+ out[7] = a[7] - b[7];
+ out[8] = a[8] - b[8];
+ out[9] = a[9] - b[9];
+ out[10] = a[10] - b[10];
+ out[11] = a[11] - b[11];
+ out[12] = a[12] - b[12];
+ out[13] = a[13] - b[13];
+ out[14] = a[14] - b[14];
+ out[15] = a[15] - b[15];
+ return out;
+ }
+ /**
+ * Multiply each element of the matrix by a scalar.
+ *
+ * @param {mat4} out the receiving matrix
+ * @param {ReadonlyMat4} a the matrix to scale
+ * @param {Number} b amount to scale the matrix's elements by
+ * @returns {mat4} out
+ */
+
+ function multiplyScalar(out, a, b) {
+ out[0] = a[0] * b;
+ out[1] = a[1] * b;
+ out[2] = a[2] * b;
+ out[3] = a[3] * b;
+ out[4] = a[4] * b;
+ out[5] = a[5] * b;
+ out[6] = a[6] * b;
+ out[7] = a[7] * b;
+ out[8] = a[8] * b;
+ out[9] = a[9] * b;
+ out[10] = a[10] * b;
+ out[11] = a[11] * b;
+ out[12] = a[12] * b;
+ out[13] = a[13] * b;
+ out[14] = a[14] * b;
+ out[15] = a[15] * b;
+ return out;
+ }
+ /**
+ * Adds two mat4's after multiplying each element of the second operand by a scalar value.
+ *
+ * @param {mat4} out the receiving vector
+ * @param {ReadonlyMat4} a the first operand
+ * @param {ReadonlyMat4} b the second operand
+ * @param {Number} scale the amount to scale b's elements by before adding
+ * @returns {mat4} out
+ */
+
+ function multiplyScalarAndAdd(out, a, b, scale) {
+ out[0] = a[0] + b[0] * scale;
+ out[1] = a[1] + b[1] * scale;
+ out[2] = a[2] + b[2] * scale;
+ out[3] = a[3] + b[3] * scale;
+ out[4] = a[4] + b[4] * scale;
+ out[5] = a[5] + b[5] * scale;
+ out[6] = a[6] + b[6] * scale;
+ out[7] = a[7] + b[7] * scale;
+ out[8] = a[8] + b[8] * scale;
+ out[9] = a[9] + b[9] * scale;
+ out[10] = a[10] + b[10] * scale;
+ out[11] = a[11] + b[11] * scale;
+ out[12] = a[12] + b[12] * scale;
+ out[13] = a[13] + b[13] * scale;
+ out[14] = a[14] + b[14] * scale;
+ out[15] = a[15] + b[15] * scale;
+ return out;
+ }
+ /**
+ * Returns whether or not the matrices have exactly the same elements in the same position (when compared with ===)
+ *
+ * @param {ReadonlyMat4} a The first matrix.
+ * @param {ReadonlyMat4} b The second matrix.
+ * @returns {Boolean} True if the matrices are equal, false otherwise.
+ */
+
+ function exactEquals$5(a, b) {
+ return a[0] === b[0] && a[1] === b[1] && a[2] === b[2] && a[3] === b[3] && a[4] === b[4] && a[5] === b[5] && a[6] === b[6] && a[7] === b[7] && a[8] === b[8] && a[9] === b[9] && a[10] === b[10] && a[11] === b[11] && a[12] === b[12] && a[13] === b[13] && a[14] === b[14] && a[15] === b[15];
+ }
+ /**
+ * Returns whether or not the matrices have approximately the same elements in the same position.
+ *
+ * @param {ReadonlyMat4} a The first matrix.
+ * @param {ReadonlyMat4} b The second matrix.
+ * @returns {Boolean} True if the matrices are equal, false otherwise.
+ */
+
+ function equals$5(a, b) {
+ var a0 = a[0],
+ a1 = a[1],
+ a2 = a[2],
+ a3 = a[3];
+ var a4 = a[4],
+ a5 = a[5],
+ a6 = a[6],
+ a7 = a[7];
+ var a8 = a[8],
+ a9 = a[9],
+ a10 = a[10],
+ a11 = a[11];
+ var a12 = a[12],
+ a13 = a[13],
+ a14 = a[14],
+ a15 = a[15];
+ var b0 = b[0],
+ b1 = b[1],
+ b2 = b[2],
+ b3 = b[3];
+ var b4 = b[4],
+ b5 = b[5],
+ b6 = b[6],
+ b7 = b[7];
+ var b8 = b[8],
+ b9 = b[9],
+ b10 = b[10],
+ b11 = b[11];
+ var b12 = b[12],
+ b13 = b[13],
+ b14 = b[14],
+ b15 = b[15];
+ return Math.abs(a0 - b0) <= EPSILON * Math.max(1.0, Math.abs(a0), Math.abs(b0)) && Math.abs(a1 - b1) <= EPSILON * Math.max(1.0, Math.abs(a1), Math.abs(b1)) && Math.abs(a2 - b2) <= EPSILON * Math.max(1.0, Math.abs(a2), Math.abs(b2)) && Math.abs(a3 - b3) <= EPSILON * Math.max(1.0, Math.abs(a3), Math.abs(b3)) && Math.abs(a4 - b4) <= EPSILON * Math.max(1.0, Math.abs(a4), Math.abs(b4)) && Math.abs(a5 - b5) <= EPSILON * Math.max(1.0, Math.abs(a5), Math.abs(b5)) && Math.abs(a6 - b6) <= EPSILON * Math.max(1.0, Math.abs(a6), Math.abs(b6)) && Math.abs(a7 - b7) <= EPSILON * Math.max(1.0, Math.abs(a7), Math.abs(b7)) && Math.abs(a8 - b8) <= EPSILON * Math.max(1.0, Math.abs(a8), Math.abs(b8)) && Math.abs(a9 - b9) <= EPSILON * Math.max(1.0, Math.abs(a9), Math.abs(b9)) && Math.abs(a10 - b10) <= EPSILON * Math.max(1.0, Math.abs(a10), Math.abs(b10)) && Math.abs(a11 - b11) <= EPSILON * Math.max(1.0, Math.abs(a11), Math.abs(b11)) && Math.abs(a12 - b12) <= EPSILON * Math.max(1.0, Math.abs(a12), Math.abs(b12)) && Math.abs(a13 - b13) <= EPSILON * Math.max(1.0, Math.abs(a13), Math.abs(b13)) && Math.abs(a14 - b14) <= EPSILON * Math.max(1.0, Math.abs(a14), Math.abs(b14)) && Math.abs(a15 - b15) <= EPSILON * Math.max(1.0, Math.abs(a15), Math.abs(b15));
+ }
+ /**
+ * Alias for {@link mat4.multiply}
+ * @function
+ */
+
+ var mul$5 = multiply$5;
+ /**
+ * Alias for {@link mat4.subtract}
+ * @function
+ */
+
+ var sub$3 = subtract$3;
+
+ var mat4 = /*#__PURE__*/Object.freeze({
+ __proto__: null,
+ create: create$5,
+ clone: clone$5,
+ copy: copy$5,
+ fromValues: fromValues$5,
+ set: set$5,
+ identity: identity$2,
+ transpose: transpose,
+ invert: invert$2,
+ adjoint: adjoint,
+ determinant: determinant,
+ multiply: multiply$5,
+ translate: translate$1,
+ scale: scale$5,
+ rotate: rotate$1,
+ rotateX: rotateX$3,
+ rotateY: rotateY$3,
+ rotateZ: rotateZ$3,
+ fromTranslation: fromTranslation$1,
+ fromScaling: fromScaling,
+ fromRotation: fromRotation$1,
+ fromXRotation: fromXRotation,
+ fromYRotation: fromYRotation,
+ fromZRotation: fromZRotation,
+ fromRotationTranslation: fromRotationTranslation$1,
+ fromQuat2: fromQuat2,
+ getTranslation: getTranslation$1,
+ getScaling: getScaling,
+ getRotation: getRotation,
+ decompose: decompose,
+ fromRotationTranslationScale: fromRotationTranslationScale,
+ fromRotationTranslationScaleOrigin: fromRotationTranslationScaleOrigin,
+ fromQuat: fromQuat,
+ frustum: frustum,
+ perspectiveNO: perspectiveNO,
+ perspective: perspective,
+ perspectiveZO: perspectiveZO,
+ perspectiveFromFieldOfView: perspectiveFromFieldOfView,
+ orthoNO: orthoNO,
+ ortho: ortho,
+ orthoZO: orthoZO,
+ lookAt: lookAt,
+ targetTo: targetTo,
+ str: str$5,
+ frob: frob,
+ add: add$5,
+ subtract: subtract$3,
+ multiplyScalar: multiplyScalar,
+ multiplyScalarAndAdd: multiplyScalarAndAdd,
+ exactEquals: exactEquals$5,
+ equals: equals$5,
+ mul: mul$5,
+ sub: sub$3
+ });
+
+ /**
+ * 3 Dimensional Vector
+ * @module vec3
+ */
+
+ /**
+ * Creates a new, empty vec3
+ *
+ * @returns {vec3} a new 3D vector
+ */
+
+ function create$4() {
+ var out = new ARRAY_TYPE(3);
+
+ if (ARRAY_TYPE != Float32Array) {
+ out[0] = 0;
+ out[1] = 0;
+ out[2] = 0;
+ }
+
+ return out;
+ }
+ /**
+ * Creates a new vec3 initialized with values from an existing vector
+ *
+ * @param {ReadonlyVec3} a vector to clone
+ * @returns {vec3} a new 3D vector
+ */
+
+ function clone$4(a) {
+ var out = new ARRAY_TYPE(3);
+ out[0] = a[0];
+ out[1] = a[1];
+ out[2] = a[2];
+ return out;
+ }
+ /**
+ * Calculates the length of a vec3
+ *
+ * @param {ReadonlyVec3} a vector to calculate length of
+ * @returns {Number} length of a
+ */
+
+ function length$4(a) {
+ var x = a[0];
+ var y = a[1];
+ var z = a[2];
+ return Math.hypot(x, y, z);
+ }
+ /**
+ * Creates a new vec3 initialized with the given values
+ *
+ * @param {Number} x X component
+ * @param {Number} y Y component
+ * @param {Number} z Z component
+ * @returns {vec3} a new 3D vector
+ */
+
+ function fromValues$4(x, y, z) {
+ var out = new ARRAY_TYPE(3);
+ out[0] = x;
+ out[1] = y;
+ out[2] = z;
+ return out;
+ }
+ /**
+ * Copy the values from one vec3 to another
+ *
+ * @param {vec3} out the receiving vector
+ * @param {ReadonlyVec3} a the source vector
+ * @returns {vec3} out
+ */
+
+ function copy$4(out, a) {
+ out[0] = a[0];
+ out[1] = a[1];
+ out[2] = a[2];
+ return out;
+ }
+ /**
+ * Set the components of a vec3 to the given values
+ *
+ * @param {vec3} out the receiving vector
+ * @param {Number} x X component
+ * @param {Number} y Y component
+ * @param {Number} z Z component
+ * @returns {vec3} out
+ */
+
+ function set$4(out, x, y, z) {
+ out[0] = x;
+ out[1] = y;
+ out[2] = z;
+ return out;
+ }
+ /**
+ * Adds two vec3's
+ *
+ * @param {vec3} out the receiving vector
+ * @param {ReadonlyVec3} a the first operand
+ * @param {ReadonlyVec3} b the second operand
+ * @returns {vec3} out
+ */
+
+ function add$4(out, a, b) {
+ out[0] = a[0] + b[0];
+ out[1] = a[1] + b[1];
+ out[2] = a[2] + b[2];
+ return out;
+ }
+ /**
+ * Subtracts vector b from vector a
+ *
+ * @param {vec3} out the receiving vector
+ * @param {ReadonlyVec3} a the first operand
+ * @param {ReadonlyVec3} b the second operand
+ * @returns {vec3} out
+ */
+
+ function subtract$2(out, a, b) {
+ out[0] = a[0] - b[0];
+ out[1] = a[1] - b[1];
+ out[2] = a[2] - b[2];
+ return out;
+ }
+ /**
+ * Multiplies two vec3's
+ *
+ * @param {vec3} out the receiving vector
+ * @param {ReadonlyVec3} a the first operand
+ * @param {ReadonlyVec3} b the second operand
+ * @returns {vec3} out
+ */
+
+ function multiply$4(out, a, b) {
+ out[0] = a[0] * b[0];
+ out[1] = a[1] * b[1];
+ out[2] = a[2] * b[2];
+ return out;
+ }
+ /**
+ * Divides two vec3's
+ *
+ * @param {vec3} out the receiving vector
+ * @param {ReadonlyVec3} a the first operand
+ * @param {ReadonlyVec3} b the second operand
+ * @returns {vec3} out
+ */
+
+ function divide$2(out, a, b) {
+ out[0] = a[0] / b[0];
+ out[1] = a[1] / b[1];
+ out[2] = a[2] / b[2];
+ return out;
+ }
+ /**
+ * Math.ceil the components of a vec3
+ *
+ * @param {vec3} out the receiving vector
+ * @param {ReadonlyVec3} a vector to ceil
+ * @returns {vec3} out
+ */
+
+ function ceil$2(out, a) {
+ out[0] = Math.ceil(a[0]);
+ out[1] = Math.ceil(a[1]);
+ out[2] = Math.ceil(a[2]);
+ return out;
+ }
+ /**
+ * Math.floor the components of a vec3
+ *
+ * @param {vec3} out the receiving vector
+ * @param {ReadonlyVec3} a vector to floor
+ * @returns {vec3} out
+ */
+
+ function floor$2(out, a) {
+ out[0] = Math.floor(a[0]);
+ out[1] = Math.floor(a[1]);
+ out[2] = Math.floor(a[2]);
+ return out;
+ }
+ /**
+ * Returns the minimum of two vec3's
+ *
+ * @param {vec3} out the receiving vector
+ * @param {ReadonlyVec3} a the first operand
+ * @param {ReadonlyVec3} b the second operand
+ * @returns {vec3} out
+ */
+
+ function min$2(out, a, b) {
+ out[0] = Math.min(a[0], b[0]);
+ out[1] = Math.min(a[1], b[1]);
+ out[2] = Math.min(a[2], b[2]);
+ return out;
+ }
+ /**
+ * Returns the maximum of two vec3's
+ *
+ * @param {vec3} out the receiving vector
+ * @param {ReadonlyVec3} a the first operand
+ * @param {ReadonlyVec3} b the second operand
+ * @returns {vec3} out
+ */
+
+ function max$2(out, a, b) {
+ out[0] = Math.max(a[0], b[0]);
+ out[1] = Math.max(a[1], b[1]);
+ out[2] = Math.max(a[2], b[2]);
+ return out;
+ }
+ /**
+ * Math.round the components of a vec3
+ *
+ * @param {vec3} out the receiving vector
+ * @param {ReadonlyVec3} a vector to round
+ * @returns {vec3} out
+ */
+
+ function round$2(out, a) {
+ out[0] = Math.round(a[0]);
+ out[1] = Math.round(a[1]);
+ out[2] = Math.round(a[2]);
+ return out;
+ }
+ /**
+ * Scales a vec3 by a scalar number
+ *
+ * @param {vec3} out the receiving vector
+ * @param {ReadonlyVec3} a the vector to scale
+ * @param {Number} b amount to scale the vector by
+ * @returns {vec3} out
+ */
+
+ function scale$4(out, a, b) {
+ out[0] = a[0] * b;
+ out[1] = a[1] * b;
+ out[2] = a[2] * b;
+ return out;
+ }
+ /**
+ * Adds two vec3's after scaling the second operand by a scalar value
+ *
+ * @param {vec3} out the receiving vector
+ * @param {ReadonlyVec3} a the first operand
+ * @param {ReadonlyVec3} b the second operand
+ * @param {Number} scale the amount to scale b by before adding
+ * @returns {vec3} out
+ */
+
+ function scaleAndAdd$2(out, a, b, scale) {
+ out[0] = a[0] + b[0] * scale;
+ out[1] = a[1] + b[1] * scale;
+ out[2] = a[2] + b[2] * scale;
+ return out;
+ }
+ /**
+ * Calculates the euclidian distance between two vec3's
+ *
+ * @param {ReadonlyVec3} a the first operand
+ * @param {ReadonlyVec3} b the second operand
+ * @returns {Number} distance between a and b
+ */
+
+ function distance$2(a, b) {
+ var x = b[0] - a[0];
+ var y = b[1] - a[1];
+ var z = b[2] - a[2];
+ return Math.hypot(x, y, z);
+ }
+ /**
+ * Calculates the squared euclidian distance between two vec3's
+ *
+ * @param {ReadonlyVec3} a the first operand
+ * @param {ReadonlyVec3} b the second operand
+ * @returns {Number} squared distance between a and b
+ */
+
+ function squaredDistance$2(a, b) {
+ var x = b[0] - a[0];
+ var y = b[1] - a[1];
+ var z = b[2] - a[2];
+ return x * x + y * y + z * z;
+ }
+ /**
+ * Calculates the squared length of a vec3
+ *
+ * @param {ReadonlyVec3} a vector to calculate squared length of
+ * @returns {Number} squared length of a
+ */
+
+ function squaredLength$4(a) {
+ var x = a[0];
+ var y = a[1];
+ var z = a[2];
+ return x * x + y * y + z * z;
+ }
+ /**
+ * Negates the components of a vec3
+ *
+ * @param {vec3} out the receiving vector
+ * @param {ReadonlyVec3} a vector to negate
+ * @returns {vec3} out
+ */
+
+ function negate$2(out, a) {
+ out[0] = -a[0];
+ out[1] = -a[1];
+ out[2] = -a[2];
+ return out;
+ }
+ /**
+ * Returns the inverse of the components of a vec3
+ *
+ * @param {vec3} out the receiving vector
+ * @param {ReadonlyVec3} a vector to invert
+ * @returns {vec3} out
+ */
+
+ function inverse$2(out, a) {
+ out[0] = 1.0 / a[0];
+ out[1] = 1.0 / a[1];
+ out[2] = 1.0 / a[2];
+ return out;
+ }
+ /**
+ * Normalize a vec3
+ *
+ * @param {vec3} out the receiving vector
+ * @param {ReadonlyVec3} a vector to normalize
+ * @returns {vec3} out
+ */
+
+ function normalize$4(out, a) {
+ var x = a[0];
+ var y = a[1];
+ var z = a[2];
+ var len = x * x + y * y + z * z;
+
+ if (len > 0) {
+ //TODO: evaluate use of glm_invsqrt here?
+ len = 1 / Math.sqrt(len);
+ }
+
+ out[0] = a[0] * len;
+ out[1] = a[1] * len;
+ out[2] = a[2] * len;
+ return out;
+ }
+ /**
+ * Calculates the dot product of two vec3's
+ *
+ * @param {ReadonlyVec3} a the first operand
+ * @param {ReadonlyVec3} b the second operand
+ * @returns {Number} dot product of a and b
+ */
+
+ function dot$4(a, b) {
+ return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
+ }
+ /**
+ * Computes the cross product of two vec3's
+ *
+ * @param {vec3} out the receiving vector
+ * @param {ReadonlyVec3} a the first operand
+ * @param {ReadonlyVec3} b the second operand
+ * @returns {vec3} out
+ */
+
+ function cross$2(out, a, b) {
+ var ax = a[0],
+ ay = a[1],
+ az = a[2];
+ var bx = b[0],
+ by = b[1],
+ bz = b[2];
+ out[0] = ay * bz - az * by;
+ out[1] = az * bx - ax * bz;
+ out[2] = ax * by - ay * bx;
+ return out;
+ }
+ /**
+ * Performs a linear interpolation between two vec3's
+ *
+ * @param {vec3} out the receiving vector
+ * @param {ReadonlyVec3} a the first operand
+ * @param {ReadonlyVec3} b the second operand
+ * @param {Number} t interpolation amount, in the range [0-1], between the two inputs
+ * @returns {vec3} out
+ */
+
+ function lerp$4(out, a, b, t) {
+ var ax = a[0];
+ var ay = a[1];
+ var az = a[2];
+ out[0] = ax + t * (b[0] - ax);
+ out[1] = ay + t * (b[1] - ay);
+ out[2] = az + t * (b[2] - az);
+ return out;
+ }
+ /**
+ * Performs a spherical linear interpolation between two vec3's
+ *
+ * @param {vec3} out the receiving vector
+ * @param {ReadonlyVec3} a the first operand
+ * @param {ReadonlyVec3} b the second operand
+ * @param {Number} t interpolation amount, in the range [0-1], between the two inputs
+ * @returns {vec3} out
+ */
+
+ function slerp$1(out, a, b, t) {
+ var angle = Math.acos(Math.min(Math.max(dot$4(a, b), -1), 1));
+ var sinTotal = Math.sin(angle);
+ var ratioA = Math.sin((1 - t) * angle) / sinTotal;
+ var ratioB = Math.sin(t * angle) / sinTotal;
+ out[0] = ratioA * a[0] + ratioB * b[0];
+ out[1] = ratioA * a[1] + ratioB * b[1];
+ out[2] = ratioA * a[2] + ratioB * b[2];
+ return out;
+ }
+ /**
+ * Performs a hermite interpolation with two control points
+ *
+ * @param {vec3} out the receiving vector
+ * @param {ReadonlyVec3} a the first operand
+ * @param {ReadonlyVec3} b the second operand
+ * @param {ReadonlyVec3} c the third operand
+ * @param {ReadonlyVec3} d the fourth operand
+ * @param {Number} t interpolation amount, in the range [0-1], between the two inputs
+ * @returns {vec3} out
+ */
+
+ function hermite(out, a, b, c, d, t) {
+ var factorTimes2 = t * t;
+ var factor1 = factorTimes2 * (2 * t - 3) + 1;
+ var factor2 = factorTimes2 * (t - 2) + t;
+ var factor3 = factorTimes2 * (t - 1);
+ var factor4 = factorTimes2 * (3 - 2 * t);
+ out[0] = a[0] * factor1 + b[0] * factor2 + c[0] * factor3 + d[0] * factor4;
+ out[1] = a[1] * factor1 + b[1] * factor2 + c[1] * factor3 + d[1] * factor4;
+ out[2] = a[2] * factor1 + b[2] * factor2 + c[2] * factor3 + d[2] * factor4;
+ return out;
+ }
+ /**
+ * Performs a bezier interpolation with two control points
+ *
+ * @param {vec3} out the receiving vector
+ * @param {ReadonlyVec3} a the first operand
+ * @param {ReadonlyVec3} b the second operand
+ * @param {ReadonlyVec3} c the third operand
+ * @param {ReadonlyVec3} d the fourth operand
+ * @param {Number} t interpolation amount, in the range [0-1], between the two inputs
+ * @returns {vec3} out
+ */
+
+ function bezier(out, a, b, c, d, t) {
+ var inverseFactor = 1 - t;
+ var inverseFactorTimesTwo = inverseFactor * inverseFactor;
+ var factorTimes2 = t * t;
+ var factor1 = inverseFactorTimesTwo * inverseFactor;
+ var factor2 = 3 * t * inverseFactorTimesTwo;
+ var factor3 = 3 * factorTimes2 * inverseFactor;
+ var factor4 = factorTimes2 * t;
+ out[0] = a[0] * factor1 + b[0] * factor2 + c[0] * factor3 + d[0] * factor4;
+ out[1] = a[1] * factor1 + b[1] * factor2 + c[1] * factor3 + d[1] * factor4;
+ out[2] = a[2] * factor1 + b[2] * factor2 + c[2] * factor3 + d[2] * factor4;
+ return out;
+ }
+ /**
+ * Generates a random vector with the given scale
+ *
+ * @param {vec3} out the receiving vector
+ * @param {Number} [scale] Length of the resulting vector. If omitted, a unit vector will be returned
+ * @returns {vec3} out
+ */
+
+ function random$3(out, scale) {
+ scale = scale || 1.0;
+ var r = RANDOM() * 2.0 * Math.PI;
+ var z = RANDOM() * 2.0 - 1.0;
+ var zScale = Math.sqrt(1.0 - z * z) * scale;
+ out[0] = Math.cos(r) * zScale;
+ out[1] = Math.sin(r) * zScale;
+ out[2] = z * scale;
+ return out;
+ }
+ /**
+ * Transforms the vec3 with a mat4.
+ * 4th vector component is implicitly '1'
+ *
+ * @param {vec3} out the receiving vector
+ * @param {ReadonlyVec3} a the vector to transform
+ * @param {ReadonlyMat4} m matrix to transform with
+ * @returns {vec3} out
+ */
+
+ function transformMat4$2(out, a, m) {
+ var x = a[0],
+ y = a[1],
+ z = a[2];
+ var w = m[3] * x + m[7] * y + m[11] * z + m[15];
+ w = w || 1.0;
+ out[0] = (m[0] * x + m[4] * y + m[8] * z + m[12]) / w;
+ out[1] = (m[1] * x + m[5] * y + m[9] * z + m[13]) / w;
+ out[2] = (m[2] * x + m[6] * y + m[10] * z + m[14]) / w;
+ return out;
+ }
+ /**
+ * Transforms the vec3 with a mat3.
+ *
+ * @param {vec3} out the receiving vector
+ * @param {ReadonlyVec3} a the vector to transform
+ * @param {ReadonlyMat3} m the 3x3 matrix to transform with
+ * @returns {vec3} out
+ */
+
+ function transformMat3$1(out, a, m) {
+ var x = a[0],
+ y = a[1],
+ z = a[2];
+ out[0] = x * m[0] + y * m[3] + z * m[6];
+ out[1] = x * m[1] + y * m[4] + z * m[7];
+ out[2] = x * m[2] + y * m[5] + z * m[8];
+ return out;
+ }
+ /**
+ * Transforms the vec3 with a quat
+ * Can also be used for dual quaternions. (Multiply it with the real part)
+ *
+ * @param {vec3} out the receiving vector
+ * @param {ReadonlyVec3} a the vector to transform
+ * @param {ReadonlyQuat} q quaternion to transform with
+ * @returns {vec3} out
+ */
+
+ function transformQuat$1(out, a, q) {
+ // benchmarks: https://jsperf.com/quaternion-transform-vec3-implementations-fixed
+ var qx = q[0],
+ qy = q[1],
+ qz = q[2],
+ qw = q[3];
+ var x = a[0],
+ y = a[1],
+ z = a[2]; // var qvec = [qx, qy, qz];
+ // var uv = vec3.cross([], qvec, a);
+
+ var uvx = qy * z - qz * y,
+ uvy = qz * x - qx * z,
+ uvz = qx * y - qy * x; // var uuv = vec3.cross([], qvec, uv);
+
+ var uuvx = qy * uvz - qz * uvy,
+ uuvy = qz * uvx - qx * uvz,
+ uuvz = qx * uvy - qy * uvx; // vec3.scale(uv, uv, 2 * w);
+
+ var w2 = qw * 2;
+ uvx *= w2;
+ uvy *= w2;
+ uvz *= w2; // vec3.scale(uuv, uuv, 2);
+
+ uuvx *= 2;
+ uuvy *= 2;
+ uuvz *= 2; // return vec3.add(out, a, vec3.add(out, uv, uuv));
+
+ out[0] = x + uvx + uuvx;
+ out[1] = y + uvy + uuvy;
+ out[2] = z + uvz + uuvz;
+ return out;
+ }
+ /**
+ * Rotate a 3D vector around the x-axis
+ * @param {vec3} out The receiving vec3
+ * @param {ReadonlyVec3} a The vec3 point to rotate
+ * @param {ReadonlyVec3} b The origin of the rotation
+ * @param {Number} rad The angle of rotation in radians
+ * @returns {vec3} out
+ */
+
+ function rotateX$2(out, a, b, rad) {
+ var p = [],
+ r = []; //Translate point to the origin
+
+ p[0] = a[0] - b[0];
+ p[1] = a[1] - b[1];
+ p[2] = a[2] - b[2]; //perform rotation
+
+ r[0] = p[0];
+ r[1] = p[1] * Math.cos(rad) - p[2] * Math.sin(rad);
+ r[2] = p[1] * Math.sin(rad) + p[2] * Math.cos(rad); //translate to correct position
+
+ out[0] = r[0] + b[0];
+ out[1] = r[1] + b[1];
+ out[2] = r[2] + b[2];
+ return out;
+ }
+ /**
+ * Rotate a 3D vector around the y-axis
+ * @param {vec3} out The receiving vec3
+ * @param {ReadonlyVec3} a The vec3 point to rotate
+ * @param {ReadonlyVec3} b The origin of the rotation
+ * @param {Number} rad The angle of rotation in radians
+ * @returns {vec3} out
+ */
+
+ function rotateY$2(out, a, b, rad) {
+ var p = [],
+ r = []; //Translate point to the origin
+
+ p[0] = a[0] - b[0];
+ p[1] = a[1] - b[1];
+ p[2] = a[2] - b[2]; //perform rotation
+
+ r[0] = p[2] * Math.sin(rad) + p[0] * Math.cos(rad);
+ r[1] = p[1];
+ r[2] = p[2] * Math.cos(rad) - p[0] * Math.sin(rad); //translate to correct position
+
+ out[0] = r[0] + b[0];
+ out[1] = r[1] + b[1];
+ out[2] = r[2] + b[2];
+ return out;
+ }
+ /**
+ * Rotate a 3D vector around the z-axis
+ * @param {vec3} out The receiving vec3
+ * @param {ReadonlyVec3} a The vec3 point to rotate
+ * @param {ReadonlyVec3} b The origin of the rotation
+ * @param {Number} rad The angle of rotation in radians
+ * @returns {vec3} out
+ */
+
+ function rotateZ$2(out, a, b, rad) {
+ var p = [],
+ r = []; //Translate point to the origin
+
+ p[0] = a[0] - b[0];
+ p[1] = a[1] - b[1];
+ p[2] = a[2] - b[2]; //perform rotation
+
+ r[0] = p[0] * Math.cos(rad) - p[1] * Math.sin(rad);
+ r[1] = p[0] * Math.sin(rad) + p[1] * Math.cos(rad);
+ r[2] = p[2]; //translate to correct position
+
+ out[0] = r[0] + b[0];
+ out[1] = r[1] + b[1];
+ out[2] = r[2] + b[2];
+ return out;
+ }
+ /**
+ * Get the angle between two 3D vectors
+ * @param {ReadonlyVec3} a The first operand
+ * @param {ReadonlyVec3} b The second operand
+ * @returns {Number} The angle in radians
+ */
+
+ function angle$1(a, b) {
+ var ax = a[0],
+ ay = a[1],
+ az = a[2],
+ bx = b[0],
+ by = b[1],
+ bz = b[2],
+ mag = Math.sqrt((ax * ax + ay * ay + az * az) * (bx * bx + by * by + bz * bz)),
+ cosine = mag && dot$4(a, b) / mag;
+ return Math.acos(Math.min(Math.max(cosine, -1), 1));
+ }
+ /**
+ * Set the components of a vec3 to zero
+ *
+ * @param {vec3} out the receiving vector
+ * @returns {vec3} out
+ */
+
+ function zero$2(out) {
+ out[0] = 0.0;
+ out[1] = 0.0;
+ out[2] = 0.0;
+ return out;
+ }
+ /**
+ * Returns a string representation of a vector
+ *
+ * @param {ReadonlyVec3} a vector to represent as a string
+ * @returns {String} string representation of the vector
+ */
+
+ function str$4(a) {
+ return "vec3(" + a[0] + ", " + a[1] + ", " + a[2] + ")";
+ }
+ /**
+ * Returns whether or not the vectors have exactly the same elements in the same position (when compared with ===)
+ *
+ * @param {ReadonlyVec3} a The first vector.
+ * @param {ReadonlyVec3} b The second vector.
+ * @returns {Boolean} True if the vectors are equal, false otherwise.
+ */
+
+ function exactEquals$4(a, b) {
+ return a[0] === b[0] && a[1] === b[1] && a[2] === b[2];
+ }
+ /**
+ * Returns whether or not the vectors have approximately the same elements in the same position.
+ *
+ * @param {ReadonlyVec3} a The first vector.
+ * @param {ReadonlyVec3} b The second vector.
+ * @returns {Boolean} True if the vectors are equal, false otherwise.
+ */
+
+ function equals$4(a, b) {
+ var a0 = a[0],
+ a1 = a[1],
+ a2 = a[2];
+ var b0 = b[0],
+ b1 = b[1],
+ b2 = b[2];
+ return Math.abs(a0 - b0) <= EPSILON * Math.max(1.0, Math.abs(a0), Math.abs(b0)) && Math.abs(a1 - b1) <= EPSILON * Math.max(1.0, Math.abs(a1), Math.abs(b1)) && Math.abs(a2 - b2) <= EPSILON * Math.max(1.0, Math.abs(a2), Math.abs(b2));
+ }
+ /**
+ * Alias for {@link vec3.subtract}
+ * @function
+ */
+
+ var sub$2 = subtract$2;
+ /**
+ * Alias for {@link vec3.multiply}
+ * @function
+ */
+
+ var mul$4 = multiply$4;
+ /**
+ * Alias for {@link vec3.divide}
+ * @function
+ */
+
+ var div$2 = divide$2;
+ /**
+ * Alias for {@link vec3.distance}
+ * @function
+ */
+
+ var dist$2 = distance$2;
+ /**
+ * Alias for {@link vec3.squaredDistance}
+ * @function
+ */
+
+ var sqrDist$2 = squaredDistance$2;
+ /**
+ * Alias for {@link vec3.length}
+ * @function
+ */
+
+ var len$4 = length$4;
+ /**
+ * Alias for {@link vec3.squaredLength}
+ * @function
+ */
+
+ var sqrLen$4 = squaredLength$4;
+ /**
+ * Perform some operation over an array of vec3s.
+ *
+ * @param {Array} a the array of vectors to iterate over
+ * @param {Number} stride Number of elements between the start of each vec3. If 0 assumes tightly packed
+ * @param {Number} offset Number of elements to skip at the beginning of the array
+ * @param {Number} count Number of vec3s to iterate over. If 0 iterates over entire array
+ * @param {Function} fn Function to call for each vector in the array
+ * @param {Object} [arg] additional argument to pass to fn
+ * @returns {Array} a
+ * @function
+ */
+
+ var forEach$2 = function () {
+ var vec = create$4();
+ return function (a, stride, offset, count, fn, arg) {
+ var i, l;
+
+ if (!stride) {
+ stride = 3;
+ }
+
+ if (!offset) {
+ offset = 0;
+ }
+
+ if (count) {
+ l = Math.min(count * stride + offset, a.length);
+ } else {
+ l = a.length;
+ }
+
+ for (i = offset; i < l; i += stride) {
+ vec[0] = a[i];
+ vec[1] = a[i + 1];
+ vec[2] = a[i + 2];
+ fn(vec, vec, arg);
+ a[i] = vec[0];
+ a[i + 1] = vec[1];
+ a[i + 2] = vec[2];
+ }
+
+ return a;
+ };
+ }();
+
+ var vec3 = /*#__PURE__*/Object.freeze({
+ __proto__: null,
+ create: create$4,
+ clone: clone$4,
+ length: length$4,
+ fromValues: fromValues$4,
+ copy: copy$4,
+ set: set$4,
+ add: add$4,
+ subtract: subtract$2,
+ multiply: multiply$4,
+ divide: divide$2,
+ ceil: ceil$2,
+ floor: floor$2,
+ min: min$2,
+ max: max$2,
+ round: round$2,
+ scale: scale$4,
+ scaleAndAdd: scaleAndAdd$2,
+ distance: distance$2,
+ squaredDistance: squaredDistance$2,
+ squaredLength: squaredLength$4,
+ negate: negate$2,
+ inverse: inverse$2,
+ normalize: normalize$4,
+ dot: dot$4,
+ cross: cross$2,
+ lerp: lerp$4,
+ slerp: slerp$1,
+ hermite: hermite,
+ bezier: bezier,
+ random: random$3,
+ transformMat4: transformMat4$2,
+ transformMat3: transformMat3$1,
+ transformQuat: transformQuat$1,
+ rotateX: rotateX$2,
+ rotateY: rotateY$2,
+ rotateZ: rotateZ$2,
+ angle: angle$1,
+ zero: zero$2,
+ str: str$4,
+ exactEquals: exactEquals$4,
+ equals: equals$4,
+ sub: sub$2,
+ mul: mul$4,
+ div: div$2,
+ dist: dist$2,
+ sqrDist: sqrDist$2,
+ len: len$4,
+ sqrLen: sqrLen$4,
+ forEach: forEach$2
+ });
+
+ /**
+ * 4 Dimensional Vector
+ * @module vec4
+ */
+
+ /**
+ * Creates a new, empty vec4
+ *
+ * @returns {vec4} a new 4D vector
+ */
+
+ function create$3() {
+ var out = new ARRAY_TYPE(4);
+
+ if (ARRAY_TYPE != Float32Array) {
+ out[0] = 0;
+ out[1] = 0;
+ out[2] = 0;
+ out[3] = 0;
+ }
+
+ return out;
+ }
+ /**
+ * Creates a new vec4 initialized with values from an existing vector
+ *
+ * @param {ReadonlyVec4} a vector to clone
+ * @returns {vec4} a new 4D vector
+ */
+
+ function clone$3(a) {
+ var out = new ARRAY_TYPE(4);
+ out[0] = a[0];
+ out[1] = a[1];
+ out[2] = a[2];
+ out[3] = a[3];
+ return out;
+ }
+ /**
+ * Creates a new vec4 initialized with the given values
+ *
+ * @param {Number} x X component
+ * @param {Number} y Y component
+ * @param {Number} z Z component
+ * @param {Number} w W component
+ * @returns {vec4} a new 4D vector
+ */
+
+ function fromValues$3(x, y, z, w) {
+ var out = new ARRAY_TYPE(4);
+ out[0] = x;
+ out[1] = y;
+ out[2] = z;
+ out[3] = w;
+ return out;
+ }
+ /**
+ * Copy the values from one vec4 to another
+ *
+ * @param {vec4} out the receiving vector
+ * @param {ReadonlyVec4} a the source vector
+ * @returns {vec4} out
+ */
+
+ function copy$3(out, a) {
+ out[0] = a[0];
+ out[1] = a[1];
+ out[2] = a[2];
+ out[3] = a[3];
+ return out;
+ }
+ /**
+ * Set the components of a vec4 to the given values
+ *
+ * @param {vec4} out the receiving vector
+ * @param {Number} x X component
+ * @param {Number} y Y component
+ * @param {Number} z Z component
+ * @param {Number} w W component
+ * @returns {vec4} out
+ */
+
+ function set$3(out, x, y, z, w) {
+ out[0] = x;
+ out[1] = y;
+ out[2] = z;
+ out[3] = w;
+ return out;
+ }
+ /**
+ * Adds two vec4's
+ *
+ * @param {vec4} out the receiving vector
+ * @param {ReadonlyVec4} a the first operand
+ * @param {ReadonlyVec4} b the second operand
+ * @returns {vec4} out
+ */
+
+ function add$3(out, a, b) {
+ out[0] = a[0] + b[0];
+ out[1] = a[1] + b[1];
+ out[2] = a[2] + b[2];
+ out[3] = a[3] + b[3];
+ return out;
+ }
+ /**
+ * Subtracts vector b from vector a
+ *
+ * @param {vec4} out the receiving vector
+ * @param {ReadonlyVec4} a the first operand
+ * @param {ReadonlyVec4} b the second operand
+ * @returns {vec4} out
+ */
+
+ function subtract$1(out, a, b) {
+ out[0] = a[0] - b[0];
+ out[1] = a[1] - b[1];
+ out[2] = a[2] - b[2];
+ out[3] = a[3] - b[3];
+ return out;
+ }
+ /**
+ * Multiplies two vec4's
+ *
+ * @param {vec4} out the receiving vector
+ * @param {ReadonlyVec4} a the first operand
+ * @param {ReadonlyVec4} b the second operand
+ * @returns {vec4} out
+ */
+
+ function multiply$3(out, a, b) {
+ out[0] = a[0] * b[0];
+ out[1] = a[1] * b[1];
+ out[2] = a[2] * b[2];
+ out[3] = a[3] * b[3];
+ return out;
+ }
+ /**
+ * Divides two vec4's
+ *
+ * @param {vec4} out the receiving vector
+ * @param {ReadonlyVec4} a the first operand
+ * @param {ReadonlyVec4} b the second operand
+ * @returns {vec4} out
+ */
+
+ function divide$1(out, a, b) {
+ out[0] = a[0] / b[0];
+ out[1] = a[1] / b[1];
+ out[2] = a[2] / b[2];
+ out[3] = a[3] / b[3];
+ return out;
+ }
+ /**
+ * Math.ceil the components of a vec4
+ *
+ * @param {vec4} out the receiving vector
+ * @param {ReadonlyVec4} a vector to ceil
+ * @returns {vec4} out
+ */
+
+ function ceil$1(out, a) {
+ out[0] = Math.ceil(a[0]);
+ out[1] = Math.ceil(a[1]);
+ out[2] = Math.ceil(a[2]);
+ out[3] = Math.ceil(a[3]);
+ return out;
+ }
+ /**
+ * Math.floor the components of a vec4
+ *
+ * @param {vec4} out the receiving vector
+ * @param {ReadonlyVec4} a vector to floor
+ * @returns {vec4} out
+ */
+
+ function floor$1(out, a) {
+ out[0] = Math.floor(a[0]);
+ out[1] = Math.floor(a[1]);
+ out[2] = Math.floor(a[2]);
+ out[3] = Math.floor(a[3]);
+ return out;
+ }
+ /**
+ * Returns the minimum of two vec4's
+ *
+ * @param {vec4} out the receiving vector
+ * @param {ReadonlyVec4} a the first operand
+ * @param {ReadonlyVec4} b the second operand
+ * @returns {vec4} out
+ */
+
+ function min$1(out, a, b) {
+ out[0] = Math.min(a[0], b[0]);
+ out[1] = Math.min(a[1], b[1]);
+ out[2] = Math.min(a[2], b[2]);
+ out[3] = Math.min(a[3], b[3]);
+ return out;
+ }
+ /**
+ * Returns the maximum of two vec4's
+ *
+ * @param {vec4} out the receiving vector
+ * @param {ReadonlyVec4} a the first operand
+ * @param {ReadonlyVec4} b the second operand
+ * @returns {vec4} out
+ */
+
+ function max$1(out, a, b) {
+ out[0] = Math.max(a[0], b[0]);
+ out[1] = Math.max(a[1], b[1]);
+ out[2] = Math.max(a[2], b[2]);
+ out[3] = Math.max(a[3], b[3]);
+ return out;
+ }
+ /**
+ * Math.round the components of a vec4
+ *
+ * @param {vec4} out the receiving vector
+ * @param {ReadonlyVec4} a vector to round
+ * @returns {vec4} out
+ */
+
+ function round$1(out, a) {
+ out[0] = Math.round(a[0]);
+ out[1] = Math.round(a[1]);
+ out[2] = Math.round(a[2]);
+ out[3] = Math.round(a[3]);
+ return out;
+ }
+ /**
+ * Scales a vec4 by a scalar number
+ *
+ * @param {vec4} out the receiving vector
+ * @param {ReadonlyVec4} a the vector to scale
+ * @param {Number} b amount to scale the vector by
+ * @returns {vec4} out
+ */
+
+ function scale$3(out, a, b) {
+ out[0] = a[0] * b;
+ out[1] = a[1] * b;
+ out[2] = a[2] * b;
+ out[3] = a[3] * b;
+ return out;
+ }
+ /**
+ * Adds two vec4's after scaling the second operand by a scalar value
+ *
+ * @param {vec4} out the receiving vector
+ * @param {ReadonlyVec4} a the first operand
+ * @param {ReadonlyVec4} b the second operand
+ * @param {Number} scale the amount to scale b by before adding
+ * @returns {vec4} out
+ */
+
+ function scaleAndAdd$1(out, a, b, scale) {
+ out[0] = a[0] + b[0] * scale;
+ out[1] = a[1] + b[1] * scale;
+ out[2] = a[2] + b[2] * scale;
+ out[3] = a[3] + b[3] * scale;
+ return out;
+ }
+ /**
+ * Calculates the euclidian distance between two vec4's
+ *
+ * @param {ReadonlyVec4} a the first operand
+ * @param {ReadonlyVec4} b the second operand
+ * @returns {Number} distance between a and b
+ */
+
+ function distance$1(a, b) {
+ var x = b[0] - a[0];
+ var y = b[1] - a[1];
+ var z = b[2] - a[2];
+ var w = b[3] - a[3];
+ return Math.hypot(x, y, z, w);
+ }
+ /**
+ * Calculates the squared euclidian distance between two vec4's
+ *
+ * @param {ReadonlyVec4} a the first operand
+ * @param {ReadonlyVec4} b the second operand
+ * @returns {Number} squared distance between a and b
+ */
+
+ function squaredDistance$1(a, b) {
+ var x = b[0] - a[0];
+ var y = b[1] - a[1];
+ var z = b[2] - a[2];
+ var w = b[3] - a[3];
+ return x * x + y * y + z * z + w * w;
+ }
+ /**
+ * Calculates the length of a vec4
+ *
+ * @param {ReadonlyVec4} a vector to calculate length of
+ * @returns {Number} length of a
+ */
+
+ function length$3(a) {
+ var x = a[0];
+ var y = a[1];
+ var z = a[2];
+ var w = a[3];
+ return Math.hypot(x, y, z, w);
+ }
+ /**
+ * Calculates the squared length of a vec4
+ *
+ * @param {ReadonlyVec4} a vector to calculate squared length of
+ * @returns {Number} squared length of a
+ */
+
+ function squaredLength$3(a) {
+ var x = a[0];
+ var y = a[1];
+ var z = a[2];
+ var w = a[3];
+ return x * x + y * y + z * z + w * w;
+ }
+ /**
+ * Negates the components of a vec4
+ *
+ * @param {vec4} out the receiving vector
+ * @param {ReadonlyVec4} a vector to negate
+ * @returns {vec4} out
+ */
+
+ function negate$1(out, a) {
+ out[0] = -a[0];
+ out[1] = -a[1];
+ out[2] = -a[2];
+ out[3] = -a[3];
+ return out;
+ }
+ /**
+ * Returns the inverse of the components of a vec4
+ *
+ * @param {vec4} out the receiving vector
+ * @param {ReadonlyVec4} a vector to invert
+ * @returns {vec4} out
+ */
+
+ function inverse$1(out, a) {
+ out[0] = 1.0 / a[0];
+ out[1] = 1.0 / a[1];
+ out[2] = 1.0 / a[2];
+ out[3] = 1.0 / a[3];
+ return out;
+ }
+ /**
+ * Normalize a vec4
+ *
+ * @param {vec4} out the receiving vector
+ * @param {ReadonlyVec4} a vector to normalize
+ * @returns {vec4} out
+ */
+
+ function normalize$3(out, a) {
+ var x = a[0];
+ var y = a[1];
+ var z = a[2];
+ var w = a[3];
+ var len = x * x + y * y + z * z + w * w;
+
+ if (len > 0) {
+ len = 1 / Math.sqrt(len);
+ }
+
+ out[0] = x * len;
+ out[1] = y * len;
+ out[2] = z * len;
+ out[3] = w * len;
+ return out;
+ }
+ /**
+ * Calculates the dot product of two vec4's
+ *
+ * @param {ReadonlyVec4} a the first operand
+ * @param {ReadonlyVec4} b the second operand
+ * @returns {Number} dot product of a and b
+ */
+
+ function dot$3(a, b) {
+ return a[0] * b[0] + a[1] * b[1] + a[2] * b[2] + a[3] * b[3];
+ }
+ /**
+ * Returns the cross-product of three vectors in a 4-dimensional space
+ *
+ * @param {ReadonlyVec4} result the receiving vector
+ * @param {ReadonlyVec4} U the first vector
+ * @param {ReadonlyVec4} V the second vector
+ * @param {ReadonlyVec4} W the third vector
+ * @returns {vec4} result
+ */
+
+ function cross$1(out, u, v, w) {
+ var A = v[0] * w[1] - v[1] * w[0],
+ B = v[0] * w[2] - v[2] * w[0],
+ C = v[0] * w[3] - v[3] * w[0],
+ D = v[1] * w[2] - v[2] * w[1],
+ E = v[1] * w[3] - v[3] * w[1],
+ F = v[2] * w[3] - v[3] * w[2];
+ var G = u[0];
+ var H = u[1];
+ var I = u[2];
+ var J = u[3];
+ out[0] = H * F - I * E + J * D;
+ out[1] = -(G * F) + I * C - J * B;
+ out[2] = G * E - H * C + J * A;
+ out[3] = -(G * D) + H * B - I * A;
+ return out;
+ }
+ /**
+ * Performs a linear interpolation between two vec4's
+ *
+ * @param {vec4} out the receiving vector
+ * @param {ReadonlyVec4} a the first operand
+ * @param {ReadonlyVec4} b the second operand
+ * @param {Number} t interpolation amount, in the range [0-1], between the two inputs
+ * @returns {vec4} out
+ */
+
+ function lerp$3(out, a, b, t) {
+ var ax = a[0];
+ var ay = a[1];
+ var az = a[2];
+ var aw = a[3];
+ out[0] = ax + t * (b[0] - ax);
+ out[1] = ay + t * (b[1] - ay);
+ out[2] = az + t * (b[2] - az);
+ out[3] = aw + t * (b[3] - aw);
+ return out;
+ }
+ /**
+ * Generates a random vector with the given scale
+ *
+ * @param {vec4} out the receiving vector
+ * @param {Number} [scale] Length of the resulting vector. If omitted, a unit vector will be returned
+ * @returns {vec4} out
+ */
+
+ function random$2(out, scale) {
+ scale = scale || 1.0; // Marsaglia, George. Choosing a Point from the Surface of a
+ // Sphere. Ann. Math. Statist. 43 (1972), no. 2, 645--646.
+ // http://projecteuclid.org/euclid.aoms/1177692644;
+
+ var v1, v2, v3, v4;
+ var s1, s2;
+
+ do {
+ v1 = RANDOM() * 2 - 1;
+ v2 = RANDOM() * 2 - 1;
+ s1 = v1 * v1 + v2 * v2;
+ } while (s1 >= 1);
+
+ do {
+ v3 = RANDOM() * 2 - 1;
+ v4 = RANDOM() * 2 - 1;
+ s2 = v3 * v3 + v4 * v4;
+ } while (s2 >= 1);
+
+ var d = Math.sqrt((1 - s1) / s2);
+ out[0] = scale * v1;
+ out[1] = scale * v2;
+ out[2] = scale * v3 * d;
+ out[3] = scale * v4 * d;
+ return out;
+ }
+ /**
+ * Transforms the vec4 with a mat4.
+ *
+ * @param {vec4} out the receiving vector
+ * @param {ReadonlyVec4} a the vector to transform
+ * @param {ReadonlyMat4} m matrix to transform with
+ * @returns {vec4} out
+ */
+
+ function transformMat4$1(out, a, m) {
+ var x = a[0],
+ y = a[1],
+ z = a[2],
+ w = a[3];
+ out[0] = m[0] * x + m[4] * y + m[8] * z + m[12] * w;
+ out[1] = m[1] * x + m[5] * y + m[9] * z + m[13] * w;
+ out[2] = m[2] * x + m[6] * y + m[10] * z + m[14] * w;
+ out[3] = m[3] * x + m[7] * y + m[11] * z + m[15] * w;
+ return out;
+ }
+ /**
+ * Transforms the vec4 with a quat
+ *
+ * @param {vec4} out the receiving vector
+ * @param {ReadonlyVec4} a the vector to transform
+ * @param {ReadonlyQuat} q quaternion to transform with
+ * @returns {vec4} out
+ */
+
+ function transformQuat(out, a, q) {
+ var x = a[0],
+ y = a[1],
+ z = a[2];
+ var qx = q[0],
+ qy = q[1],
+ qz = q[2],
+ qw = q[3]; // calculate quat * vec
+
+ var ix = qw * x + qy * z - qz * y;
+ var iy = qw * y + qz * x - qx * z;
+ var iz = qw * z + qx * y - qy * x;
+ var iw = -qx * x - qy * y - qz * z; // calculate result * inverse quat
+
+ out[0] = ix * qw + iw * -qx + iy * -qz - iz * -qy;
+ out[1] = iy * qw + iw * -qy + iz * -qx - ix * -qz;
+ out[2] = iz * qw + iw * -qz + ix * -qy - iy * -qx;
+ out[3] = a[3];
+ return out;
+ }
+ /**
+ * Set the components of a vec4 to zero
+ *
+ * @param {vec4} out the receiving vector
+ * @returns {vec4} out
+ */
+
+ function zero$1(out) {
+ out[0] = 0.0;
+ out[1] = 0.0;
+ out[2] = 0.0;
+ out[3] = 0.0;
+ return out;
+ }
+ /**
+ * Returns a string representation of a vector
+ *
+ * @param {ReadonlyVec4} a vector to represent as a string
+ * @returns {String} string representation of the vector
+ */
+
+ function str$3(a) {
+ return "vec4(" + a[0] + ", " + a[1] + ", " + a[2] + ", " + a[3] + ")";
+ }
+ /**
+ * Returns whether or not the vectors have exactly the same elements in the same position (when compared with ===)
+ *
+ * @param {ReadonlyVec4} a The first vector.
+ * @param {ReadonlyVec4} b The second vector.
+ * @returns {Boolean} True if the vectors are equal, false otherwise.
+ */
+
+ function exactEquals$3(a, b) {
+ return a[0] === b[0] && a[1] === b[1] && a[2] === b[2] && a[3] === b[3];
+ }
+ /**
+ * Returns whether or not the vectors have approximately the same elements in the same position.
+ *
+ * @param {ReadonlyVec4} a The first vector.
+ * @param {ReadonlyVec4} b The second vector.
+ * @returns {Boolean} True if the vectors are equal, false otherwise.
+ */
+
+ function equals$3(a, b) {
+ var a0 = a[0],
+ a1 = a[1],
+ a2 = a[2],
+ a3 = a[3];
+ var b0 = b[0],
+ b1 = b[1],
+ b2 = b[2],
+ b3 = b[3];
+ return Math.abs(a0 - b0) <= EPSILON * Math.max(1.0, Math.abs(a0), Math.abs(b0)) && Math.abs(a1 - b1) <= EPSILON * Math.max(1.0, Math.abs(a1), Math.abs(b1)) && Math.abs(a2 - b2) <= EPSILON * Math.max(1.0, Math.abs(a2), Math.abs(b2)) && Math.abs(a3 - b3) <= EPSILON * Math.max(1.0, Math.abs(a3), Math.abs(b3));
+ }
+ /**
+ * Alias for {@link vec4.subtract}
+ * @function
+ */
+
+ var sub$1 = subtract$1;
+ /**
+ * Alias for {@link vec4.multiply}
+ * @function
+ */
+
+ var mul$3 = multiply$3;
+ /**
+ * Alias for {@link vec4.divide}
+ * @function
+ */
+
+ var div$1 = divide$1;
+ /**
+ * Alias for {@link vec4.distance}
+ * @function
+ */
+
+ var dist$1 = distance$1;
+ /**
+ * Alias for {@link vec4.squaredDistance}
+ * @function
+ */
+
+ var sqrDist$1 = squaredDistance$1;
+ /**
+ * Alias for {@link vec4.length}
+ * @function
+ */
+
+ var len$3 = length$3;
+ /**
+ * Alias for {@link vec4.squaredLength}
+ * @function
+ */
+
+ var sqrLen$3 = squaredLength$3;
+ /**
+ * Perform some operation over an array of vec4s.
+ *
+ * @param {Array} a the array of vectors to iterate over
+ * @param {Number} stride Number of elements between the start of each vec4. If 0 assumes tightly packed
+ * @param {Number} offset Number of elements to skip at the beginning of the array
+ * @param {Number} count Number of vec4s to iterate over. If 0 iterates over entire array
+ * @param {Function} fn Function to call for each vector in the array
+ * @param {Object} [arg] additional argument to pass to fn
+ * @returns {Array} a
+ * @function
+ */
+
+ var forEach$1 = function () {
+ var vec = create$3();
+ return function (a, stride, offset, count, fn, arg) {
+ var i, l;
+
+ if (!stride) {
+ stride = 4;
+ }
+
+ if (!offset) {
+ offset = 0;
+ }
+
+ if (count) {
+ l = Math.min(count * stride + offset, a.length);
+ } else {
+ l = a.length;
+ }
+
+ for (i = offset; i < l; i += stride) {
+ vec[0] = a[i];
+ vec[1] = a[i + 1];
+ vec[2] = a[i + 2];
+ vec[3] = a[i + 3];
+ fn(vec, vec, arg);
+ a[i] = vec[0];
+ a[i + 1] = vec[1];
+ a[i + 2] = vec[2];
+ a[i + 3] = vec[3];
+ }
+
+ return a;
+ };
+ }();
+
+ var vec4 = /*#__PURE__*/Object.freeze({
+ __proto__: null,
+ create: create$3,
+ clone: clone$3,
+ fromValues: fromValues$3,
+ copy: copy$3,
+ set: set$3,
+ add: add$3,
+ subtract: subtract$1,
+ multiply: multiply$3,
+ divide: divide$1,
+ ceil: ceil$1,
+ floor: floor$1,
+ min: min$1,
+ max: max$1,
+ round: round$1,
+ scale: scale$3,
+ scaleAndAdd: scaleAndAdd$1,
+ distance: distance$1,
+ squaredDistance: squaredDistance$1,
+ length: length$3,
+ squaredLength: squaredLength$3,
+ negate: negate$1,
+ inverse: inverse$1,
+ normalize: normalize$3,
+ dot: dot$3,
+ cross: cross$1,
+ lerp: lerp$3,
+ random: random$2,
+ transformMat4: transformMat4$1,
+ transformQuat: transformQuat,
+ zero: zero$1,
+ str: str$3,
+ exactEquals: exactEquals$3,
+ equals: equals$3,
+ sub: sub$1,
+ mul: mul$3,
+ div: div$1,
+ dist: dist$1,
+ sqrDist: sqrDist$1,
+ len: len$3,
+ sqrLen: sqrLen$3,
+ forEach: forEach$1
+ });
+
+ /**
+ * Quaternion in the format XYZW
+ * @module quat
+ */
+
+ /**
+ * Creates a new identity quat
+ *
+ * @returns {quat} a new quaternion
+ */
+
+ function create$2() {
+ var out = new ARRAY_TYPE(4);
+
+ if (ARRAY_TYPE != Float32Array) {
+ out[0] = 0;
+ out[1] = 0;
+ out[2] = 0;
+ }
+
+ out[3] = 1;
+ return out;
+ }
+ /**
+ * Set a quat to the identity quaternion
+ *
+ * @param {quat} out the receiving quaternion
+ * @returns {quat} out
+ */
+
+ function identity$1(out) {
+ out[0] = 0;
+ out[1] = 0;
+ out[2] = 0;
+ out[3] = 1;
+ return out;
+ }
+ /**
+ * Sets a quat from the given angle and rotation axis,
+ * then returns it.
+ *
+ * @param {quat} out the receiving quaternion
+ * @param {ReadonlyVec3} axis the axis around which to rotate
+ * @param {Number} rad the angle in radians
+ * @returns {quat} out
+ **/
+
+ function setAxisAngle(out, axis, rad) {
+ rad = rad * 0.5;
+ var s = Math.sin(rad);
+ out[0] = s * axis[0];
+ out[1] = s * axis[1];
+ out[2] = s * axis[2];
+ out[3] = Math.cos(rad);
+ return out;
+ }
+ /**
+ * Gets the rotation axis and angle for a given
+ * quaternion. If a quaternion is created with
+ * setAxisAngle, this method will return the same
+ * values as providied in the original parameter list
+ * OR functionally equivalent values.
+ * Example: The quaternion formed by axis [0, 0, 1] and
+ * angle -90 is the same as the quaternion formed by
+ * [0, 0, 1] and 270. This method favors the latter.
+ * @param {vec3} out_axis Vector receiving the axis of rotation
+ * @param {ReadonlyQuat} q Quaternion to be decomposed
+ * @return {Number} Angle, in radians, of the rotation
+ */
+
+ function getAxisAngle(out_axis, q) {
+ var rad = Math.acos(q[3]) * 2.0;
+ var s = Math.sin(rad / 2.0);
+
+ if (s > EPSILON) {
+ out_axis[0] = q[0] / s;
+ out_axis[1] = q[1] / s;
+ out_axis[2] = q[2] / s;
+ } else {
+ // If s is zero, return any axis (no rotation - axis does not matter)
+ out_axis[0] = 1;
+ out_axis[1] = 0;
+ out_axis[2] = 0;
+ }
+
+ return rad;
+ }
+ /**
+ * Gets the angular distance between two unit quaternions
+ *
+ * @param {ReadonlyQuat} a Origin unit quaternion
+ * @param {ReadonlyQuat} b Destination unit quaternion
+ * @return {Number} Angle, in radians, between the two quaternions
+ */
+
+ function getAngle(a, b) {
+ var dotproduct = dot$2(a, b);
+ return Math.acos(2 * dotproduct * dotproduct - 1);
+ }
+ /**
+ * Multiplies two quat's
+ *
+ * @param {quat} out the receiving quaternion
+ * @param {ReadonlyQuat} a the first operand
+ * @param {ReadonlyQuat} b the second operand
+ * @returns {quat} out
+ */
+
+ function multiply$2(out, a, b) {
+ var ax = a[0],
+ ay = a[1],
+ az = a[2],
+ aw = a[3];
+ var bx = b[0],
+ by = b[1],
+ bz = b[2],
+ bw = b[3];
+ out[0] = ax * bw + aw * bx + ay * bz - az * by;
+ out[1] = ay * bw + aw * by + az * bx - ax * bz;
+ out[2] = az * bw + aw * bz + ax * by - ay * bx;
+ out[3] = aw * bw - ax * bx - ay * by - az * bz;
+ return out;
+ }
+ /**
+ * Rotates a quaternion by the given angle about the X axis
+ *
+ * @param {quat} out quat receiving operation result
+ * @param {ReadonlyQuat} a quat to rotate
+ * @param {number} rad angle (in radians) to rotate
+ * @returns {quat} out
+ */
+
+ function rotateX$1(out, a, rad) {
+ rad *= 0.5;
+ var ax = a[0],
+ ay = a[1],
+ az = a[2],
+ aw = a[3];
+ var bx = Math.sin(rad),
+ bw = Math.cos(rad);
+ out[0] = ax * bw + aw * bx;
+ out[1] = ay * bw + az * bx;
+ out[2] = az * bw - ay * bx;
+ out[3] = aw * bw - ax * bx;
+ return out;
+ }
+ /**
+ * Rotates a quaternion by the given angle about the Y axis
+ *
+ * @param {quat} out quat receiving operation result
+ * @param {ReadonlyQuat} a quat to rotate
+ * @param {number} rad angle (in radians) to rotate
+ * @returns {quat} out
+ */
+
+ function rotateY$1(out, a, rad) {
+ rad *= 0.5;
+ var ax = a[0],
+ ay = a[1],
+ az = a[2],
+ aw = a[3];
+ var by = Math.sin(rad),
+ bw = Math.cos(rad);
+ out[0] = ax * bw - az * by;
+ out[1] = ay * bw + aw * by;
+ out[2] = az * bw + ax * by;
+ out[3] = aw * bw - ay * by;
+ return out;
+ }
+ /**
+ * Rotates a quaternion by the given angle about the Z axis
+ *
+ * @param {quat} out quat receiving operation result
+ * @param {ReadonlyQuat} a quat to rotate
+ * @param {number} rad angle (in radians) to rotate
+ * @returns {quat} out
+ */
+
+ function rotateZ$1(out, a, rad) {
+ rad *= 0.5;
+ var ax = a[0],
+ ay = a[1],
+ az = a[2],
+ aw = a[3];
+ var bz = Math.sin(rad),
+ bw = Math.cos(rad);
+ out[0] = ax * bw + ay * bz;
+ out[1] = ay * bw - ax * bz;
+ out[2] = az * bw + aw * bz;
+ out[3] = aw * bw - az * bz;
+ return out;
+ }
+ /**
+ * Calculates the W component of a quat from the X, Y, and Z components.
+ * Assumes that quaternion is 1 unit in length.
+ * Any existing W component will be ignored.
+ *
+ * @param {quat} out the receiving quaternion
+ * @param {ReadonlyQuat} a quat to calculate W component of
+ * @returns {quat} out
+ */
+
+ function calculateW(out, a) {
+ var x = a[0],
+ y = a[1],
+ z = a[2];
+ out[0] = x;
+ out[1] = y;
+ out[2] = z;
+ out[3] = Math.sqrt(Math.abs(1.0 - x * x - y * y - z * z));
+ return out;
+ }
+ /**
+ * Calculate the exponential of a unit quaternion.
+ *
+ * @param {quat} out the receiving quaternion
+ * @param {ReadonlyQuat} a quat to calculate the exponential of
+ * @returns {quat} out
+ */
+
+ function exp(out, a) {
+ var x = a[0],
+ y = a[1],
+ z = a[2],
+ w = a[3];
+ var r = Math.sqrt(x * x + y * y + z * z);
+ var et = Math.exp(w);
+ var s = r > 0 ? et * Math.sin(r) / r : 0;
+ out[0] = x * s;
+ out[1] = y * s;
+ out[2] = z * s;
+ out[3] = et * Math.cos(r);
+ return out;
+ }
+ /**
+ * Calculate the natural logarithm of a unit quaternion.
+ *
+ * @param {quat} out the receiving quaternion
+ * @param {ReadonlyQuat} a quat to calculate the exponential of
+ * @returns {quat} out
+ */
+
+ function ln(out, a) {
+ var x = a[0],
+ y = a[1],
+ z = a[2],
+ w = a[3];
+ var r = Math.sqrt(x * x + y * y + z * z);
+ var t = r > 0 ? Math.atan2(r, w) / r : 0;
+ out[0] = x * t;
+ out[1] = y * t;
+ out[2] = z * t;
+ out[3] = 0.5 * Math.log(x * x + y * y + z * z + w * w);
+ return out;
+ }
+ /**
+ * Calculate the scalar power of a unit quaternion.
+ *
+ * @param {quat} out the receiving quaternion
+ * @param {ReadonlyQuat} a quat to calculate the exponential of
+ * @param {Number} b amount to scale the quaternion by
+ * @returns {quat} out
+ */
+
+ function pow(out, a, b) {
+ ln(out, a);
+ scale$2(out, out, b);
+ exp(out, out);
+ return out;
+ }
+ /**
+ * Performs a spherical linear interpolation between two quat
+ *
+ * @param {quat} out the receiving quaternion
+ * @param {ReadonlyQuat} a the first operand
+ * @param {ReadonlyQuat} b the second operand
+ * @param {Number} t interpolation amount, in the range [0-1], between the two inputs
+ * @returns {quat} out
+ */
+
+ function slerp(out, a, b, t) {
+ // benchmarks:
+ // http://jsperf.com/quaternion-slerp-implementations
+ var ax = a[0],
+ ay = a[1],
+ az = a[2],
+ aw = a[3];
+ var bx = b[0],
+ by = b[1],
+ bz = b[2],
+ bw = b[3];
+ var omega, cosom, sinom, scale0, scale1; // calc cosine
+
+ cosom = ax * bx + ay * by + az * bz + aw * bw; // adjust signs (if necessary)
+
+ if (cosom < 0.0) {
+ cosom = -cosom;
+ bx = -bx;
+ by = -by;
+ bz = -bz;
+ bw = -bw;
+ } // calculate coefficients
+
+
+ if (1.0 - cosom > EPSILON) {
+ // standard case (slerp)
+ omega = Math.acos(cosom);
+ sinom = Math.sin(omega);
+ scale0 = Math.sin((1.0 - t) * omega) / sinom;
+ scale1 = Math.sin(t * omega) / sinom;
+ } else {
+ // "from" and "to" quaternions are very close
+ // ... so we can do a linear interpolation
+ scale0 = 1.0 - t;
+ scale1 = t;
+ } // calculate final values
+
+
+ out[0] = scale0 * ax + scale1 * bx;
+ out[1] = scale0 * ay + scale1 * by;
+ out[2] = scale0 * az + scale1 * bz;
+ out[3] = scale0 * aw + scale1 * bw;
+ return out;
+ }
+ /**
+ * Generates a random unit quaternion
+ *
+ * @param {quat} out the receiving quaternion
+ * @returns {quat} out
+ */
+
+ function random$1(out) {
+ // Implementation of http://planning.cs.uiuc.edu/node198.html
+ // TODO: Calling random 3 times is probably not the fastest solution
+ var u1 = RANDOM();
+ var u2 = RANDOM();
+ var u3 = RANDOM();
+ var sqrt1MinusU1 = Math.sqrt(1 - u1);
+ var sqrtU1 = Math.sqrt(u1);
+ out[0] = sqrt1MinusU1 * Math.sin(2.0 * Math.PI * u2);
+ out[1] = sqrt1MinusU1 * Math.cos(2.0 * Math.PI * u2);
+ out[2] = sqrtU1 * Math.sin(2.0 * Math.PI * u3);
+ out[3] = sqrtU1 * Math.cos(2.0 * Math.PI * u3);
+ return out;
+ }
+ /**
+ * Calculates the inverse of a quat
+ *
+ * @param {quat} out the receiving quaternion
+ * @param {ReadonlyQuat} a quat to calculate inverse of
+ * @returns {quat} out
+ */
+
+ function invert$1(out, a) {
+ var a0 = a[0],
+ a1 = a[1],
+ a2 = a[2],
+ a3 = a[3];
+ var dot = a0 * a0 + a1 * a1 + a2 * a2 + a3 * a3;
+ var invDot = dot ? 1.0 / dot : 0; // TODO: Would be faster to return [0,0,0,0] immediately if dot == 0
+
+ out[0] = -a0 * invDot;
+ out[1] = -a1 * invDot;
+ out[2] = -a2 * invDot;
+ out[3] = a3 * invDot;
+ return out;
+ }
+ /**
+ * Calculates the conjugate of a quat
+ * If the quaternion is normalized, this function is faster than quat.inverse and produces the same result.
+ *
+ * @param {quat} out the receiving quaternion
+ * @param {ReadonlyQuat} a quat to calculate conjugate of
+ * @returns {quat} out
+ */
+
+ function conjugate$1(out, a) {
+ out[0] = -a[0];
+ out[1] = -a[1];
+ out[2] = -a[2];
+ out[3] = a[3];
+ return out;
+ }
+ /**
+ * Creates a quaternion from the given 3x3 rotation matrix.
+ *
+ * NOTE: The resultant quaternion is not normalized, so you should be sure
+ * to renormalize the quaternion yourself where necessary.
+ *
+ * @param {quat} out the receiving quaternion
+ * @param {ReadonlyMat3} m rotation matrix
+ * @returns {quat} out
+ * @function
+ */
+
+ function fromMat3(out, m) {
+ // Algorithm in Ken Shoemake's article in 1987 SIGGRAPH course notes
+ // article "Quaternion Calculus and Fast Animation".
+ var fTrace = m[0] + m[4] + m[8];
+ var fRoot;
+
+ if (fTrace > 0.0) {
+ // |w| > 1/2, may as well choose w > 1/2
+ fRoot = Math.sqrt(fTrace + 1.0); // 2w
+
+ out[3] = 0.5 * fRoot;
+ fRoot = 0.5 / fRoot; // 1/(4w)
+
+ out[0] = (m[5] - m[7]) * fRoot;
+ out[1] = (m[6] - m[2]) * fRoot;
+ out[2] = (m[1] - m[3]) * fRoot;
+ } else {
+ // |w| <= 1/2
+ var i = 0;
+ if (m[4] > m[0]) i = 1;
+ if (m[8] > m[i * 3 + i]) i = 2;
+ var j = (i + 1) % 3;
+ var k = (i + 2) % 3;
+ fRoot = Math.sqrt(m[i * 3 + i] - m[j * 3 + j] - m[k * 3 + k] + 1.0);
+ out[i] = 0.5 * fRoot;
+ fRoot = 0.5 / fRoot;
+ out[3] = (m[j * 3 + k] - m[k * 3 + j]) * fRoot;
+ out[j] = (m[j * 3 + i] + m[i * 3 + j]) * fRoot;
+ out[k] = (m[k * 3 + i] + m[i * 3 + k]) * fRoot;
+ }
+
+ return out;
+ }
+ /**
+ * Creates a quaternion from the given euler angle x, y, z using the provided intrinsic order for the conversion.
+ *
+ * @param {quat} out the receiving quaternion
+ * @param {x} x Angle to rotate around X axis in degrees.
+ * @param {y} y Angle to rotate around Y axis in degrees.
+ * @param {z} z Angle to rotate around Z axis in degrees.
+ * @param {'zyx'|'xyz'|'yxz'|'yzx'|'zxy'|'zyx'} order Intrinsic order for conversion, default is zyx.
+ * @returns {quat} out
+ * @function
+ */
+
+ function fromEuler(out, x, y, z) {
+ var order = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : ANGLE_ORDER;
+ var halfToRad = Math.PI / 360;
+ x *= halfToRad;
+ z *= halfToRad;
+ y *= halfToRad;
+ var sx = Math.sin(x);
+ var cx = Math.cos(x);
+ var sy = Math.sin(y);
+ var cy = Math.cos(y);
+ var sz = Math.sin(z);
+ var cz = Math.cos(z);
+
+ switch (order) {
+ case "xyz":
+ out[0] = sx * cy * cz + cx * sy * sz;
+ out[1] = cx * sy * cz - sx * cy * sz;
+ out[2] = cx * cy * sz + sx * sy * cz;
+ out[3] = cx * cy * cz - sx * sy * sz;
+ break;
+
+ case "xzy":
+ out[0] = sx * cy * cz - cx * sy * sz;
+ out[1] = cx * sy * cz - sx * cy * sz;
+ out[2] = cx * cy * sz + sx * sy * cz;
+ out[3] = cx * cy * cz + sx * sy * sz;
+ break;
+
+ case "yxz":
+ out[0] = sx * cy * cz + cx * sy * sz;
+ out[1] = cx * sy * cz - sx * cy * sz;
+ out[2] = cx * cy * sz - sx * sy * cz;
+ out[3] = cx * cy * cz + sx * sy * sz;
+ break;
+
+ case "yzx":
+ out[0] = sx * cy * cz + cx * sy * sz;
+ out[1] = cx * sy * cz + sx * cy * sz;
+ out[2] = cx * cy * sz - sx * sy * cz;
+ out[3] = cx * cy * cz - sx * sy * sz;
+ break;
+
+ case "zxy":
+ out[0] = sx * cy * cz - cx * sy * sz;
+ out[1] = cx * sy * cz + sx * cy * sz;
+ out[2] = cx * cy * sz + sx * sy * cz;
+ out[3] = cx * cy * cz - sx * sy * sz;
+ break;
+
+ case "zyx":
+ out[0] = sx * cy * cz - cx * sy * sz;
+ out[1] = cx * sy * cz + sx * cy * sz;
+ out[2] = cx * cy * sz - sx * sy * cz;
+ out[3] = cx * cy * cz + sx * sy * sz;
+ break;
+
+ default:
+ throw new Error('Unknown angle order ' + order);
+ }
+
+ return out;
+ }
+ /**
+ * Returns a string representation of a quaternion
+ *
+ * @param {ReadonlyQuat} a vector to represent as a string
+ * @returns {String} string representation of the vector
+ */
+
+ function str$2(a) {
+ return "quat(" + a[0] + ", " + a[1] + ", " + a[2] + ", " + a[3] + ")";
+ }
+ /**
+ * Creates a new quat initialized with values from an existing quaternion
+ *
+ * @param {ReadonlyQuat} a quaternion to clone
+ * @returns {quat} a new quaternion
+ * @function
+ */
+
+ var clone$2 = clone$3;
+ /**
+ * Creates a new quat initialized with the given values
+ *
+ * @param {Number} x X component
+ * @param {Number} y Y component
+ * @param {Number} z Z component
+ * @param {Number} w W component
+ * @returns {quat} a new quaternion
+ * @function
+ */
+
+ var fromValues$2 = fromValues$3;
+ /**
+ * Copy the values from one quat to another
+ *
+ * @param {quat} out the receiving quaternion
+ * @param {ReadonlyQuat} a the source quaternion
+ * @returns {quat} out
+ * @function
+ */
+
+ var copy$2 = copy$3;
+ /**
+ * Set the components of a quat to the given values
+ *
+ * @param {quat} out the receiving quaternion
+ * @param {Number} x X component
+ * @param {Number} y Y component
+ * @param {Number} z Z component
+ * @param {Number} w W component
+ * @returns {quat} out
+ * @function
+ */
+
+ var set$2 = set$3;
+ /**
+ * Adds two quat's
+ *
+ * @param {quat} out the receiving quaternion
+ * @param {ReadonlyQuat} a the first operand
+ * @param {ReadonlyQuat} b the second operand
+ * @returns {quat} out
+ * @function
+ */
+
+ var add$2 = add$3;
+ /**
+ * Alias for {@link quat.multiply}
+ * @function
+ */
+
+ var mul$2 = multiply$2;
+ /**
+ * Scales a quat by a scalar number
+ *
+ * @param {quat} out the receiving vector
+ * @param {ReadonlyQuat} a the vector to scale
+ * @param {Number} b amount to scale the vector by
+ * @returns {quat} out
+ * @function
+ */
+
+ var scale$2 = scale$3;
+ /**
+ * Calculates the dot product of two quat's
+ *
+ * @param {ReadonlyQuat} a the first operand
+ * @param {ReadonlyQuat} b the second operand
+ * @returns {Number} dot product of a and b
+ * @function
+ */
+
+ var dot$2 = dot$3;
+ /**
+ * Performs a linear interpolation between two quat's
+ *
+ * @param {quat} out the receiving quaternion
+ * @param {ReadonlyQuat} a the first operand
+ * @param {ReadonlyQuat} b the second operand
+ * @param {Number} t interpolation amount, in the range [0-1], between the two inputs
+ * @returns {quat} out
+ * @function
+ */
+
+ var lerp$2 = lerp$3;
+ /**
+ * Calculates the length of a quat
+ *
+ * @param {ReadonlyQuat} a vector to calculate length of
+ * @returns {Number} length of a
+ */
+
+ var length$2 = length$3;
+ /**
+ * Alias for {@link quat.length}
+ * @function
+ */
+
+ var len$2 = length$2;
+ /**
+ * Calculates the squared length of a quat
+ *
+ * @param {ReadonlyQuat} a vector to calculate squared length of
+ * @returns {Number} squared length of a
+ * @function
+ */
+
+ var squaredLength$2 = squaredLength$3;
+ /**
+ * Alias for {@link quat.squaredLength}
+ * @function
+ */
+
+ var sqrLen$2 = squaredLength$2;
+ /**
+ * Normalize a quat
+ *
+ * @param {quat} out the receiving quaternion
+ * @param {ReadonlyQuat} a quaternion to normalize
+ * @returns {quat} out
+ * @function
+ */
+
+ var normalize$2 = normalize$3;
+ /**
+ * Returns whether or not the quaternions have exactly the same elements in the same position (when compared with ===)
+ *
+ * @param {ReadonlyQuat} a The first quaternion.
+ * @param {ReadonlyQuat} b The second quaternion.
+ * @returns {Boolean} True if the vectors are equal, false otherwise.
+ */
+
+ var exactEquals$2 = exactEquals$3;
+ /**
+ * Returns whether or not the quaternions point approximately to the same direction.
+ *
+ * Both quaternions are assumed to be unit length.
+ *
+ * @param {ReadonlyQuat} a The first unit quaternion.
+ * @param {ReadonlyQuat} b The second unit quaternion.
+ * @returns {Boolean} True if the quaternions are equal, false otherwise.
+ */
+
+ function equals$2(a, b) {
+ return Math.abs(dot$3(a, b)) >= 1 - EPSILON;
+ }
+ /**
+ * Sets a quaternion to represent the shortest rotation from one
+ * vector to another.
+ *
+ * Both vectors are assumed to be unit length.
+ *
+ * @param {quat} out the receiving quaternion.
+ * @param {ReadonlyVec3} a the initial vector
+ * @param {ReadonlyVec3} b the destination vector
+ * @returns {quat} out
+ */
+
+ var rotationTo = function () {
+ var tmpvec3 = create$4();
+ var xUnitVec3 = fromValues$4(1, 0, 0);
+ var yUnitVec3 = fromValues$4(0, 1, 0);
+ return function (out, a, b) {
+ var dot = dot$4(a, b);
+
+ if (dot < -0.999999) {
+ cross$2(tmpvec3, xUnitVec3, a);
+ if (len$4(tmpvec3) < 0.000001) cross$2(tmpvec3, yUnitVec3, a);
+ normalize$4(tmpvec3, tmpvec3);
+ setAxisAngle(out, tmpvec3, Math.PI);
+ return out;
+ } else if (dot > 0.999999) {
+ out[0] = 0;
+ out[1] = 0;
+ out[2] = 0;
+ out[3] = 1;
+ return out;
+ } else {
+ cross$2(tmpvec3, a, b);
+ out[0] = tmpvec3[0];
+ out[1] = tmpvec3[1];
+ out[2] = tmpvec3[2];
+ out[3] = 1 + dot;
+ return normalize$2(out, out);
+ }
+ };
+ }();
+ /**
+ * Performs a spherical linear interpolation with two control points
+ *
+ * @param {quat} out the receiving quaternion
+ * @param {ReadonlyQuat} a the first operand
+ * @param {ReadonlyQuat} b the second operand
+ * @param {ReadonlyQuat} c the third operand
+ * @param {ReadonlyQuat} d the fourth operand
+ * @param {Number} t interpolation amount, in the range [0-1], between the two inputs
+ * @returns {quat} out
+ */
+
+ var sqlerp = function () {
+ var temp1 = create$2();
+ var temp2 = create$2();
+ return function (out, a, b, c, d, t) {
+ slerp(temp1, a, d, t);
+ slerp(temp2, b, c, t);
+ slerp(out, temp1, temp2, 2 * t * (1 - t));
+ return out;
+ };
+ }();
+ /**
+ * Sets the specified quaternion with values corresponding to the given
+ * axes. Each axis is a vec3 and is expected to be unit length and
+ * perpendicular to all other specified axes.
+ *
+ * @param {ReadonlyVec3} view the vector representing the viewing direction
+ * @param {ReadonlyVec3} right the vector representing the local "right" direction
+ * @param {ReadonlyVec3} up the vector representing the local "up" direction
+ * @returns {quat} out
+ */
+
+ var setAxes = function () {
+ var matr = create$6();
+ return function (out, view, right, up) {
+ matr[0] = right[0];
+ matr[3] = right[1];
+ matr[6] = right[2];
+ matr[1] = up[0];
+ matr[4] = up[1];
+ matr[7] = up[2];
+ matr[2] = -view[0];
+ matr[5] = -view[1];
+ matr[8] = -view[2];
+ return normalize$2(out, fromMat3(out, matr));
+ };
+ }();
+
+ var quat = /*#__PURE__*/Object.freeze({
+ __proto__: null,
+ create: create$2,
+ identity: identity$1,
+ setAxisAngle: setAxisAngle,
+ getAxisAngle: getAxisAngle,
+ getAngle: getAngle,
+ multiply: multiply$2,
+ rotateX: rotateX$1,
+ rotateY: rotateY$1,
+ rotateZ: rotateZ$1,
+ calculateW: calculateW,
+ exp: exp,
+ ln: ln,
+ pow: pow,
+ slerp: slerp,
+ random: random$1,
+ invert: invert$1,
+ conjugate: conjugate$1,
+ fromMat3: fromMat3,
+ fromEuler: fromEuler,
+ str: str$2,
+ clone: clone$2,
+ fromValues: fromValues$2,
+ copy: copy$2,
+ set: set$2,
+ add: add$2,
+ mul: mul$2,
+ scale: scale$2,
+ dot: dot$2,
+ lerp: lerp$2,
+ length: length$2,
+ len: len$2,
+ squaredLength: squaredLength$2,
+ sqrLen: sqrLen$2,
+ normalize: normalize$2,
+ exactEquals: exactEquals$2,
+ equals: equals$2,
+ rotationTo: rotationTo,
+ sqlerp: sqlerp,
+ setAxes: setAxes
+ });
+
+ /**
+ * Dual Quaternion
+ * Format: [real, dual]
+ * Quaternion format: XYZW
+ * Make sure to have normalized dual quaternions, otherwise the functions may not work as intended.
+ * @module quat2
+ */
+
+ /**
+ * Creates a new identity dual quat
+ *
+ * @returns {quat2} a new dual quaternion [real -> rotation, dual -> translation]
+ */
+
+ function create$1() {
+ var dq = new ARRAY_TYPE(8);
+
+ if (ARRAY_TYPE != Float32Array) {
+ dq[0] = 0;
+ dq[1] = 0;
+ dq[2] = 0;
+ dq[4] = 0;
+ dq[5] = 0;
+ dq[6] = 0;
+ dq[7] = 0;
+ }
+
+ dq[3] = 1;
+ return dq;
+ }
+ /**
+ * Creates a new quat initialized with values from an existing quaternion
+ *
+ * @param {ReadonlyQuat2} a dual quaternion to clone
+ * @returns {quat2} new dual quaternion
+ * @function
+ */
+
+ function clone$1(a) {
+ var dq = new ARRAY_TYPE(8);
+ dq[0] = a[0];
+ dq[1] = a[1];
+ dq[2] = a[2];
+ dq[3] = a[3];
+ dq[4] = a[4];
+ dq[5] = a[5];
+ dq[6] = a[6];
+ dq[7] = a[7];
+ return dq;
+ }
+ /**
+ * Creates a new dual quat initialized with the given values
+ *
+ * @param {Number} x1 X component
+ * @param {Number} y1 Y component
+ * @param {Number} z1 Z component
+ * @param {Number} w1 W component
+ * @param {Number} x2 X component
+ * @param {Number} y2 Y component
+ * @param {Number} z2 Z component
+ * @param {Number} w2 W component
+ * @returns {quat2} new dual quaternion
+ * @function
+ */
+
+ function fromValues$1(x1, y1, z1, w1, x2, y2, z2, w2) {
+ var dq = new ARRAY_TYPE(8);
+ dq[0] = x1;
+ dq[1] = y1;
+ dq[2] = z1;
+ dq[3] = w1;
+ dq[4] = x2;
+ dq[5] = y2;
+ dq[6] = z2;
+ dq[7] = w2;
+ return dq;
+ }
+ /**
+ * Creates a new dual quat from the given values (quat and translation)
+ *
+ * @param {Number} x1 X component
+ * @param {Number} y1 Y component
+ * @param {Number} z1 Z component
+ * @param {Number} w1 W component
+ * @param {Number} x2 X component (translation)
+ * @param {Number} y2 Y component (translation)
+ * @param {Number} z2 Z component (translation)
+ * @returns {quat2} new dual quaternion
+ * @function
+ */
+
+ function fromRotationTranslationValues(x1, y1, z1, w1, x2, y2, z2) {
+ var dq = new ARRAY_TYPE(8);
+ dq[0] = x1;
+ dq[1] = y1;
+ dq[2] = z1;
+ dq[3] = w1;
+ var ax = x2 * 0.5,
+ ay = y2 * 0.5,
+ az = z2 * 0.5;
+ dq[4] = ax * w1 + ay * z1 - az * y1;
+ dq[5] = ay * w1 + az * x1 - ax * z1;
+ dq[6] = az * w1 + ax * y1 - ay * x1;
+ dq[7] = -ax * x1 - ay * y1 - az * z1;
+ return dq;
+ }
+ /**
+ * Creates a dual quat from a quaternion and a translation
+ *
+ * @param {ReadonlyQuat2} dual quaternion receiving operation result
+ * @param {ReadonlyQuat} q a normalized quaternion
+ * @param {ReadonlyVec3} t translation vector
+ * @returns {quat2} dual quaternion receiving operation result
+ * @function
+ */
+
+ function fromRotationTranslation(out, q, t) {
+ var ax = t[0] * 0.5,
+ ay = t[1] * 0.5,
+ az = t[2] * 0.5,
+ bx = q[0],
+ by = q[1],
+ bz = q[2],
+ bw = q[3];
+ out[0] = bx;
+ out[1] = by;
+ out[2] = bz;
+ out[3] = bw;
+ out[4] = ax * bw + ay * bz - az * by;
+ out[5] = ay * bw + az * bx - ax * bz;
+ out[6] = az * bw + ax * by - ay * bx;
+ out[7] = -ax * bx - ay * by - az * bz;
+ return out;
+ }
+ /**
+ * Creates a dual quat from a translation
+ *
+ * @param {ReadonlyQuat2} dual quaternion receiving operation result
+ * @param {ReadonlyVec3} t translation vector
+ * @returns {quat2} dual quaternion receiving operation result
+ * @function
+ */
+
+ function fromTranslation(out, t) {
+ out[0] = 0;
+ out[1] = 0;
+ out[2] = 0;
+ out[3] = 1;
+ out[4] = t[0] * 0.5;
+ out[5] = t[1] * 0.5;
+ out[6] = t[2] * 0.5;
+ out[7] = 0;
+ return out;
+ }
+ /**
+ * Creates a dual quat from a quaternion
+ *
+ * @param {ReadonlyQuat2} dual quaternion receiving operation result
+ * @param {ReadonlyQuat} q the quaternion
+ * @returns {quat2} dual quaternion receiving operation result
+ * @function
+ */
+
+ function fromRotation(out, q) {
+ out[0] = q[0];
+ out[1] = q[1];
+ out[2] = q[2];
+ out[3] = q[3];
+ out[4] = 0;
+ out[5] = 0;
+ out[6] = 0;
+ out[7] = 0;
+ return out;
+ }
+ /**
+ * Creates a new dual quat from a matrix (4x4)
+ *
+ * @param {quat2} out the dual quaternion
+ * @param {ReadonlyMat4} a the matrix
+ * @returns {quat2} dual quat receiving operation result
+ * @function
+ */
+
+ function fromMat4(out, a) {
+ //TODO Optimize this
+ var outer = create$2();
+ getRotation(outer, a);
+ var t = new ARRAY_TYPE(3);
+ getTranslation$1(t, a);
+ fromRotationTranslation(out, outer, t);
+ return out;
+ }
+ /**
+ * Copy the values from one dual quat to another
+ *
+ * @param {quat2} out the receiving dual quaternion
+ * @param {ReadonlyQuat2} a the source dual quaternion
+ * @returns {quat2} out
+ * @function
+ */
+
+ function copy$1(out, a) {
+ out[0] = a[0];
+ out[1] = a[1];
+ out[2] = a[2];
+ out[3] = a[3];
+ out[4] = a[4];
+ out[5] = a[5];
+ out[6] = a[6];
+ out[7] = a[7];
+ return out;
+ }
+ /**
+ * Set a dual quat to the identity dual quaternion
+ *
+ * @param {quat2} out the receiving quaternion
+ * @returns {quat2} out
+ */
+
+ function identity(out) {
+ out[0] = 0;
+ out[1] = 0;
+ out[2] = 0;
+ out[3] = 1;
+ out[4] = 0;
+ out[5] = 0;
+ out[6] = 0;
+ out[7] = 0;
+ return out;
+ }
+ /**
+ * Set the components of a dual quat to the given values
+ *
+ * @param {quat2} out the receiving quaternion
+ * @param {Number} x1 X component
+ * @param {Number} y1 Y component
+ * @param {Number} z1 Z component
+ * @param {Number} w1 W component
+ * @param {Number} x2 X component
+ * @param {Number} y2 Y component
+ * @param {Number} z2 Z component
+ * @param {Number} w2 W component
+ * @returns {quat2} out
+ * @function
+ */
+
+ function set$1(out, x1, y1, z1, w1, x2, y2, z2, w2) {
+ out[0] = x1;
+ out[1] = y1;
+ out[2] = z1;
+ out[3] = w1;
+ out[4] = x2;
+ out[5] = y2;
+ out[6] = z2;
+ out[7] = w2;
+ return out;
+ }
+ /**
+ * Gets the real part of a dual quat
+ * @param {quat} out real part
+ * @param {ReadonlyQuat2} a Dual Quaternion
+ * @return {quat} real part
+ */
+
+ var getReal = copy$2;
+ /**
+ * Gets the dual part of a dual quat
+ * @param {quat} out dual part
+ * @param {ReadonlyQuat2} a Dual Quaternion
+ * @return {quat} dual part
+ */
+
+ function getDual(out, a) {
+ out[0] = a[4];
+ out[1] = a[5];
+ out[2] = a[6];
+ out[3] = a[7];
+ return out;
+ }
+ /**
+ * Set the real component of a dual quat to the given quaternion
+ *
+ * @param {quat2} out the receiving quaternion
+ * @param {ReadonlyQuat} q a quaternion representing the real part
+ * @returns {quat2} out
+ * @function
+ */
+
+ var setReal = copy$2;
+ /**
+ * Set the dual component of a dual quat to the given quaternion
+ *
+ * @param {quat2} out the receiving quaternion
+ * @param {ReadonlyQuat} q a quaternion representing the dual part
+ * @returns {quat2} out
+ * @function
+ */
+
+ function setDual(out, q) {
+ out[4] = q[0];
+ out[5] = q[1];
+ out[6] = q[2];
+ out[7] = q[3];
+ return out;
+ }
+ /**
+ * Gets the translation of a normalized dual quat
+ * @param {vec3} out translation
+ * @param {ReadonlyQuat2} a Dual Quaternion to be decomposed
+ * @return {vec3} translation
+ */
+
+ function getTranslation(out, a) {
+ var ax = a[4],
+ ay = a[5],
+ az = a[6],
+ aw = a[7],
+ bx = -a[0],
+ by = -a[1],
+ bz = -a[2],
+ bw = a[3];
+ out[0] = (ax * bw + aw * bx + ay * bz - az * by) * 2;
+ out[1] = (ay * bw + aw * by + az * bx - ax * bz) * 2;
+ out[2] = (az * bw + aw * bz + ax * by - ay * bx) * 2;
+ return out;
+ }
+ /**
+ * Translates a dual quat by the given vector
+ *
+ * @param {quat2} out the receiving dual quaternion
+ * @param {ReadonlyQuat2} a the dual quaternion to translate
+ * @param {ReadonlyVec3} v vector to translate by
+ * @returns {quat2} out
+ */
+
+ function translate(out, a, v) {
+ var ax1 = a[0],
+ ay1 = a[1],
+ az1 = a[2],
+ aw1 = a[3],
+ bx1 = v[0] * 0.5,
+ by1 = v[1] * 0.5,
+ bz1 = v[2] * 0.5,
+ ax2 = a[4],
+ ay2 = a[5],
+ az2 = a[6],
+ aw2 = a[7];
+ out[0] = ax1;
+ out[1] = ay1;
+ out[2] = az1;
+ out[3] = aw1;
+ out[4] = aw1 * bx1 + ay1 * bz1 - az1 * by1 + ax2;
+ out[5] = aw1 * by1 + az1 * bx1 - ax1 * bz1 + ay2;
+ out[6] = aw1 * bz1 + ax1 * by1 - ay1 * bx1 + az2;
+ out[7] = -ax1 * bx1 - ay1 * by1 - az1 * bz1 + aw2;
+ return out;
+ }
+ /**
+ * Rotates a dual quat around the X axis
+ *
+ * @param {quat2} out the receiving dual quaternion
+ * @param {ReadonlyQuat2} a the dual quaternion to rotate
+ * @param {number} rad how far should the rotation be
+ * @returns {quat2} out
+ */
+
+ function rotateX(out, a, rad) {
+ var bx = -a[0],
+ by = -a[1],
+ bz = -a[2],
+ bw = a[3],
+ ax = a[4],
+ ay = a[5],
+ az = a[6],
+ aw = a[7],
+ ax1 = ax * bw + aw * bx + ay * bz - az * by,
+ ay1 = ay * bw + aw * by + az * bx - ax * bz,
+ az1 = az * bw + aw * bz + ax * by - ay * bx,
+ aw1 = aw * bw - ax * bx - ay * by - az * bz;
+ rotateX$1(out, a, rad);
+ bx = out[0];
+ by = out[1];
+ bz = out[2];
+ bw = out[3];
+ out[4] = ax1 * bw + aw1 * bx + ay1 * bz - az1 * by;
+ out[5] = ay1 * bw + aw1 * by + az1 * bx - ax1 * bz;
+ out[6] = az1 * bw + aw1 * bz + ax1 * by - ay1 * bx;
+ out[7] = aw1 * bw - ax1 * bx - ay1 * by - az1 * bz;
+ return out;
+ }
+ /**
+ * Rotates a dual quat around the Y axis
+ *
+ * @param {quat2} out the receiving dual quaternion
+ * @param {ReadonlyQuat2} a the dual quaternion to rotate
+ * @param {number} rad how far should the rotation be
+ * @returns {quat2} out
+ */
+
+ function rotateY(out, a, rad) {
+ var bx = -a[0],
+ by = -a[1],
+ bz = -a[2],
+ bw = a[3],
+ ax = a[4],
+ ay = a[5],
+ az = a[6],
+ aw = a[7],
+ ax1 = ax * bw + aw * bx + ay * bz - az * by,
+ ay1 = ay * bw + aw * by + az * bx - ax * bz,
+ az1 = az * bw + aw * bz + ax * by - ay * bx,
+ aw1 = aw * bw - ax * bx - ay * by - az * bz;
+ rotateY$1(out, a, rad);
+ bx = out[0];
+ by = out[1];
+ bz = out[2];
+ bw = out[3];
+ out[4] = ax1 * bw + aw1 * bx + ay1 * bz - az1 * by;
+ out[5] = ay1 * bw + aw1 * by + az1 * bx - ax1 * bz;
+ out[6] = az1 * bw + aw1 * bz + ax1 * by - ay1 * bx;
+ out[7] = aw1 * bw - ax1 * bx - ay1 * by - az1 * bz;
+ return out;
+ }
+ /**
+ * Rotates a dual quat around the Z axis
+ *
+ * @param {quat2} out the receiving dual quaternion
+ * @param {ReadonlyQuat2} a the dual quaternion to rotate
+ * @param {number} rad how far should the rotation be
+ * @returns {quat2} out
+ */
+
+ function rotateZ(out, a, rad) {
+ var bx = -a[0],
+ by = -a[1],
+ bz = -a[2],
+ bw = a[3],
+ ax = a[4],
+ ay = a[5],
+ az = a[6],
+ aw = a[7],
+ ax1 = ax * bw + aw * bx + ay * bz - az * by,
+ ay1 = ay * bw + aw * by + az * bx - ax * bz,
+ az1 = az * bw + aw * bz + ax * by - ay * bx,
+ aw1 = aw * bw - ax * bx - ay * by - az * bz;
+ rotateZ$1(out, a, rad);
+ bx = out[0];
+ by = out[1];
+ bz = out[2];
+ bw = out[3];
+ out[4] = ax1 * bw + aw1 * bx + ay1 * bz - az1 * by;
+ out[5] = ay1 * bw + aw1 * by + az1 * bx - ax1 * bz;
+ out[6] = az1 * bw + aw1 * bz + ax1 * by - ay1 * bx;
+ out[7] = aw1 * bw - ax1 * bx - ay1 * by - az1 * bz;
+ return out;
+ }
+ /**
+ * Rotates a dual quat by a given quaternion (a * q)
+ *
+ * @param {quat2} out the receiving dual quaternion
+ * @param {ReadonlyQuat2} a the dual quaternion to rotate
+ * @param {ReadonlyQuat} q quaternion to rotate by
+ * @returns {quat2} out
+ */
+
+ function rotateByQuatAppend(out, a, q) {
+ var qx = q[0],
+ qy = q[1],
+ qz = q[2],
+ qw = q[3],
+ ax = a[0],
+ ay = a[1],
+ az = a[2],
+ aw = a[3];
+ out[0] = ax * qw + aw * qx + ay * qz - az * qy;
+ out[1] = ay * qw + aw * qy + az * qx - ax * qz;
+ out[2] = az * qw + aw * qz + ax * qy - ay * qx;
+ out[3] = aw * qw - ax * qx - ay * qy - az * qz;
+ ax = a[4];
+ ay = a[5];
+ az = a[6];
+ aw = a[7];
+ out[4] = ax * qw + aw * qx + ay * qz - az * qy;
+ out[5] = ay * qw + aw * qy + az * qx - ax * qz;
+ out[6] = az * qw + aw * qz + ax * qy - ay * qx;
+ out[7] = aw * qw - ax * qx - ay * qy - az * qz;
+ return out;
+ }
+ /**
+ * Rotates a dual quat by a given quaternion (q * a)
+ *
+ * @param {quat2} out the receiving dual quaternion
+ * @param {ReadonlyQuat} q quaternion to rotate by
+ * @param {ReadonlyQuat2} a the dual quaternion to rotate
+ * @returns {quat2} out
+ */
+
+ function rotateByQuatPrepend(out, q, a) {
+ var qx = q[0],
+ qy = q[1],
+ qz = q[2],
+ qw = q[3],
+ bx = a[0],
+ by = a[1],
+ bz = a[2],
+ bw = a[3];
+ out[0] = qx * bw + qw * bx + qy * bz - qz * by;
+ out[1] = qy * bw + qw * by + qz * bx - qx * bz;
+ out[2] = qz * bw + qw * bz + qx * by - qy * bx;
+ out[3] = qw * bw - qx * bx - qy * by - qz * bz;
+ bx = a[4];
+ by = a[5];
+ bz = a[6];
+ bw = a[7];
+ out[4] = qx * bw + qw * bx + qy * bz - qz * by;
+ out[5] = qy * bw + qw * by + qz * bx - qx * bz;
+ out[6] = qz * bw + qw * bz + qx * by - qy * bx;
+ out[7] = qw * bw - qx * bx - qy * by - qz * bz;
+ return out;
+ }
+ /**
+ * Rotates a dual quat around a given axis. Does the normalisation automatically
+ *
+ * @param {quat2} out the receiving dual quaternion
+ * @param {ReadonlyQuat2} a the dual quaternion to rotate
+ * @param {ReadonlyVec3} axis the axis to rotate around
+ * @param {Number} rad how far the rotation should be
+ * @returns {quat2} out
+ */
+
+ function rotateAroundAxis(out, a, axis, rad) {
+ //Special case for rad = 0
+ if (Math.abs(rad) < EPSILON) {
+ return copy$1(out, a);
+ }
+
+ var axisLength = Math.hypot(axis[0], axis[1], axis[2]);
+ rad = rad * 0.5;
+ var s = Math.sin(rad);
+ var bx = s * axis[0] / axisLength;
+ var by = s * axis[1] / axisLength;
+ var bz = s * axis[2] / axisLength;
+ var bw = Math.cos(rad);
+ var ax1 = a[0],
+ ay1 = a[1],
+ az1 = a[2],
+ aw1 = a[3];
+ out[0] = ax1 * bw + aw1 * bx + ay1 * bz - az1 * by;
+ out[1] = ay1 * bw + aw1 * by + az1 * bx - ax1 * bz;
+ out[2] = az1 * bw + aw1 * bz + ax1 * by - ay1 * bx;
+ out[3] = aw1 * bw - ax1 * bx - ay1 * by - az1 * bz;
+ var ax = a[4],
+ ay = a[5],
+ az = a[6],
+ aw = a[7];
+ out[4] = ax * bw + aw * bx + ay * bz - az * by;
+ out[5] = ay * bw + aw * by + az * bx - ax * bz;
+ out[6] = az * bw + aw * bz + ax * by - ay * bx;
+ out[7] = aw * bw - ax * bx - ay * by - az * bz;
+ return out;
+ }
+ /**
+ * Adds two dual quat's
+ *
+ * @param {quat2} out the receiving dual quaternion
+ * @param {ReadonlyQuat2} a the first operand
+ * @param {ReadonlyQuat2} b the second operand
+ * @returns {quat2} out
+ * @function
+ */
+
+ function add$1(out, a, b) {
+ out[0] = a[0] + b[0];
+ out[1] = a[1] + b[1];
+ out[2] = a[2] + b[2];
+ out[3] = a[3] + b[3];
+ out[4] = a[4] + b[4];
+ out[5] = a[5] + b[5];
+ out[6] = a[6] + b[6];
+ out[7] = a[7] + b[7];
+ return out;
+ }
+ /**
+ * Multiplies two dual quat's
+ *
+ * @param {quat2} out the receiving dual quaternion
+ * @param {ReadonlyQuat2} a the first operand
+ * @param {ReadonlyQuat2} b the second operand
+ * @returns {quat2} out
+ */
+
+ function multiply$1(out, a, b) {
+ var ax0 = a[0],
+ ay0 = a[1],
+ az0 = a[2],
+ aw0 = a[3],
+ bx1 = b[4],
+ by1 = b[5],
+ bz1 = b[6],
+ bw1 = b[7],
+ ax1 = a[4],
+ ay1 = a[5],
+ az1 = a[6],
+ aw1 = a[7],
+ bx0 = b[0],
+ by0 = b[1],
+ bz0 = b[2],
+ bw0 = b[3];
+ out[0] = ax0 * bw0 + aw0 * bx0 + ay0 * bz0 - az0 * by0;
+ out[1] = ay0 * bw0 + aw0 * by0 + az0 * bx0 - ax0 * bz0;
+ out[2] = az0 * bw0 + aw0 * bz0 + ax0 * by0 - ay0 * bx0;
+ out[3] = aw0 * bw0 - ax0 * bx0 - ay0 * by0 - az0 * bz0;
+ out[4] = ax0 * bw1 + aw0 * bx1 + ay0 * bz1 - az0 * by1 + ax1 * bw0 + aw1 * bx0 + ay1 * bz0 - az1 * by0;
+ out[5] = ay0 * bw1 + aw0 * by1 + az0 * bx1 - ax0 * bz1 + ay1 * bw0 + aw1 * by0 + az1 * bx0 - ax1 * bz0;
+ out[6] = az0 * bw1 + aw0 * bz1 + ax0 * by1 - ay0 * bx1 + az1 * bw0 + aw1 * bz0 + ax1 * by0 - ay1 * bx0;
+ out[7] = aw0 * bw1 - ax0 * bx1 - ay0 * by1 - az0 * bz1 + aw1 * bw0 - ax1 * bx0 - ay1 * by0 - az1 * bz0;
+ return out;
+ }
+ /**
+ * Alias for {@link quat2.multiply}
+ * @function
+ */
+
+ var mul$1 = multiply$1;
+ /**
+ * Scales a dual quat by a scalar number
+ *
+ * @param {quat2} out the receiving dual quat
+ * @param {ReadonlyQuat2} a the dual quat to scale
+ * @param {Number} b amount to scale the dual quat by
+ * @returns {quat2} out
+ * @function
+ */
+
+ function scale$1(out, a, b) {
+ out[0] = a[0] * b;
+ out[1] = a[1] * b;
+ out[2] = a[2] * b;
+ out[3] = a[3] * b;
+ out[4] = a[4] * b;
+ out[5] = a[5] * b;
+ out[6] = a[6] * b;
+ out[7] = a[7] * b;
+ return out;
+ }
+ /**
+ * Calculates the dot product of two dual quat's (The dot product of the real parts)
+ *
+ * @param {ReadonlyQuat2} a the first operand
+ * @param {ReadonlyQuat2} b the second operand
+ * @returns {Number} dot product of a and b
+ * @function
+ */
+
+ var dot$1 = dot$2;
+ /**
+ * Performs a linear interpolation between two dual quats's
+ * NOTE: The resulting dual quaternions won't always be normalized (The error is most noticeable when t = 0.5)
+ *
+ * @param {quat2} out the receiving dual quat
+ * @param {ReadonlyQuat2} a the first operand
+ * @param {ReadonlyQuat2} b the second operand
+ * @param {Number} t interpolation amount, in the range [0-1], between the two inputs
+ * @returns {quat2} out
+ */
+
+ function lerp$1(out, a, b, t) {
+ var mt = 1 - t;
+ if (dot$1(a, b) < 0) t = -t;
+ out[0] = a[0] * mt + b[0] * t;
+ out[1] = a[1] * mt + b[1] * t;
+ out[2] = a[2] * mt + b[2] * t;
+ out[3] = a[3] * mt + b[3] * t;
+ out[4] = a[4] * mt + b[4] * t;
+ out[5] = a[5] * mt + b[5] * t;
+ out[6] = a[6] * mt + b[6] * t;
+ out[7] = a[7] * mt + b[7] * t;
+ return out;
+ }
+ /**
+ * Calculates the inverse of a dual quat. If they are normalized, conjugate is cheaper
+ *
+ * @param {quat2} out the receiving dual quaternion
+ * @param {ReadonlyQuat2} a dual quat to calculate inverse of
+ * @returns {quat2} out
+ */
+
+ function invert(out, a) {
+ var sqlen = squaredLength$1(a);
+ out[0] = -a[0] / sqlen;
+ out[1] = -a[1] / sqlen;
+ out[2] = -a[2] / sqlen;
+ out[3] = a[3] / sqlen;
+ out[4] = -a[4] / sqlen;
+ out[5] = -a[5] / sqlen;
+ out[6] = -a[6] / sqlen;
+ out[7] = a[7] / sqlen;
+ return out;
+ }
+ /**
+ * Calculates the conjugate of a dual quat
+ * If the dual quaternion is normalized, this function is faster than quat2.inverse and produces the same result.
+ *
+ * @param {quat2} out the receiving quaternion
+ * @param {ReadonlyQuat2} a quat to calculate conjugate of
+ * @returns {quat2} out
+ */
+
+ function conjugate(out, a) {
+ out[0] = -a[0];
+ out[1] = -a[1];
+ out[2] = -a[2];
+ out[3] = a[3];
+ out[4] = -a[4];
+ out[5] = -a[5];
+ out[6] = -a[6];
+ out[7] = a[7];
+ return out;
+ }
+ /**
+ * Calculates the length of a dual quat
+ *
+ * @param {ReadonlyQuat2} a dual quat to calculate length of
+ * @returns {Number} length of a
+ * @function
+ */
+
+ var length$1 = length$2;
+ /**
+ * Alias for {@link quat2.length}
+ * @function
+ */
+
+ var len$1 = length$1;
+ /**
+ * Calculates the squared length of a dual quat
+ *
+ * @param {ReadonlyQuat2} a dual quat to calculate squared length of
+ * @returns {Number} squared length of a
+ * @function
+ */
+
+ var squaredLength$1 = squaredLength$2;
+ /**
+ * Alias for {@link quat2.squaredLength}
+ * @function
+ */
+
+ var sqrLen$1 = squaredLength$1;
+ /**
+ * Normalize a dual quat
+ *
+ * @param {quat2} out the receiving dual quaternion
+ * @param {ReadonlyQuat2} a dual quaternion to normalize
+ * @returns {quat2} out
+ * @function
+ */
+
+ function normalize$1(out, a) {
+ var magnitude = squaredLength$1(a);
+
+ if (magnitude > 0) {
+ magnitude = Math.sqrt(magnitude);
+ var a0 = a[0] / magnitude;
+ var a1 = a[1] / magnitude;
+ var a2 = a[2] / magnitude;
+ var a3 = a[3] / magnitude;
+ var b0 = a[4];
+ var b1 = a[5];
+ var b2 = a[6];
+ var b3 = a[7];
+ var a_dot_b = a0 * b0 + a1 * b1 + a2 * b2 + a3 * b3;
+ out[0] = a0;
+ out[1] = a1;
+ out[2] = a2;
+ out[3] = a3;
+ out[4] = (b0 - a0 * a_dot_b) / magnitude;
+ out[5] = (b1 - a1 * a_dot_b) / magnitude;
+ out[6] = (b2 - a2 * a_dot_b) / magnitude;
+ out[7] = (b3 - a3 * a_dot_b) / magnitude;
+ }
+
+ return out;
+ }
+ /**
+ * Returns a string representation of a dual quaternion
+ *
+ * @param {ReadonlyQuat2} a dual quaternion to represent as a string
+ * @returns {String} string representation of the dual quat
+ */
+
+ function str$1(a) {
+ return "quat2(" + a[0] + ", " + a[1] + ", " + a[2] + ", " + a[3] + ", " + a[4] + ", " + a[5] + ", " + a[6] + ", " + a[7] + ")";
+ }
+ /**
+ * Returns whether or not the dual quaternions have exactly the same elements in the same position (when compared with ===)
+ *
+ * @param {ReadonlyQuat2} a the first dual quaternion.
+ * @param {ReadonlyQuat2} b the second dual quaternion.
+ * @returns {Boolean} true if the dual quaternions are equal, false otherwise.
+ */
+
+ function exactEquals$1(a, b) {
+ return a[0] === b[0] && a[1] === b[1] && a[2] === b[2] && a[3] === b[3] && a[4] === b[4] && a[5] === b[5] && a[6] === b[6] && a[7] === b[7];
+ }
+ /**
+ * Returns whether or not the dual quaternions have approximately the same elements in the same position.
+ *
+ * @param {ReadonlyQuat2} a the first dual quat.
+ * @param {ReadonlyQuat2} b the second dual quat.
+ * @returns {Boolean} true if the dual quats are equal, false otherwise.
+ */
+
+ function equals$1(a, b) {
+ var a0 = a[0],
+ a1 = a[1],
+ a2 = a[2],
+ a3 = a[3],
+ a4 = a[4],
+ a5 = a[5],
+ a6 = a[6],
+ a7 = a[7];
+ var b0 = b[0],
+ b1 = b[1],
+ b2 = b[2],
+ b3 = b[3],
+ b4 = b[4],
+ b5 = b[5],
+ b6 = b[6],
+ b7 = b[7];
+ return Math.abs(a0 - b0) <= EPSILON * Math.max(1.0, Math.abs(a0), Math.abs(b0)) && Math.abs(a1 - b1) <= EPSILON * Math.max(1.0, Math.abs(a1), Math.abs(b1)) && Math.abs(a2 - b2) <= EPSILON * Math.max(1.0, Math.abs(a2), Math.abs(b2)) && Math.abs(a3 - b3) <= EPSILON * Math.max(1.0, Math.abs(a3), Math.abs(b3)) && Math.abs(a4 - b4) <= EPSILON * Math.max(1.0, Math.abs(a4), Math.abs(b4)) && Math.abs(a5 - b5) <= EPSILON * Math.max(1.0, Math.abs(a5), Math.abs(b5)) && Math.abs(a6 - b6) <= EPSILON * Math.max(1.0, Math.abs(a6), Math.abs(b6)) && Math.abs(a7 - b7) <= EPSILON * Math.max(1.0, Math.abs(a7), Math.abs(b7));
+ }
+
+ var quat2 = /*#__PURE__*/Object.freeze({
+ __proto__: null,
+ create: create$1,
+ clone: clone$1,
+ fromValues: fromValues$1,
+ fromRotationTranslationValues: fromRotationTranslationValues,
+ fromRotationTranslation: fromRotationTranslation,
+ fromTranslation: fromTranslation,
+ fromRotation: fromRotation,
+ fromMat4: fromMat4,
+ copy: copy$1,
+ identity: identity,
+ set: set$1,
+ getReal: getReal,
+ getDual: getDual,
+ setReal: setReal,
+ setDual: setDual,
+ getTranslation: getTranslation,
+ translate: translate,
+ rotateX: rotateX,
+ rotateY: rotateY,
+ rotateZ: rotateZ,
+ rotateByQuatAppend: rotateByQuatAppend,
+ rotateByQuatPrepend: rotateByQuatPrepend,
+ rotateAroundAxis: rotateAroundAxis,
+ add: add$1,
+ multiply: multiply$1,
+ mul: mul$1,
+ scale: scale$1,
+ dot: dot$1,
+ lerp: lerp$1,
+ invert: invert,
+ conjugate: conjugate,
+ length: length$1,
+ len: len$1,
+ squaredLength: squaredLength$1,
+ sqrLen: sqrLen$1,
+ normalize: normalize$1,
+ str: str$1,
+ exactEquals: exactEquals$1,
+ equals: equals$1
+ });
+
+ /**
+ * 2 Dimensional Vector
+ * @module vec2
+ */
+
+ /**
+ * Creates a new, empty vec2
+ *
+ * @returns {vec2} a new 2D vector
+ */
+
+ function create() {
+ var out = new ARRAY_TYPE(2);
+
+ if (ARRAY_TYPE != Float32Array) {
+ out[0] = 0;
+ out[1] = 0;
+ }
+
+ return out;
+ }
+ /**
+ * Creates a new vec2 initialized with values from an existing vector
+ *
+ * @param {ReadonlyVec2} a vector to clone
+ * @returns {vec2} a new 2D vector
+ */
+
+ function clone(a) {
+ var out = new ARRAY_TYPE(2);
+ out[0] = a[0];
+ out[1] = a[1];
+ return out;
+ }
+ /**
+ * Creates a new vec2 initialized with the given values
+ *
+ * @param {Number} x X component
+ * @param {Number} y Y component
+ * @returns {vec2} a new 2D vector
+ */
+
+ function fromValues(x, y) {
+ var out = new ARRAY_TYPE(2);
+ out[0] = x;
+ out[1] = y;
+ return out;
+ }
+ /**
+ * Copy the values from one vec2 to another
+ *
+ * @param {vec2} out the receiving vector
+ * @param {ReadonlyVec2} a the source vector
+ * @returns {vec2} out
+ */
+
+ function copy(out, a) {
+ out[0] = a[0];
+ out[1] = a[1];
+ return out;
+ }
+ /**
+ * Set the components of a vec2 to the given values
+ *
+ * @param {vec2} out the receiving vector
+ * @param {Number} x X component
+ * @param {Number} y Y component
+ * @returns {vec2} out
+ */
+
+ function set(out, x, y) {
+ out[0] = x;
+ out[1] = y;
+ return out;
+ }
+ /**
+ * Adds two vec2's
+ *
+ * @param {vec2} out the receiving vector
+ * @param {ReadonlyVec2} a the first operand
+ * @param {ReadonlyVec2} b the second operand
+ * @returns {vec2} out
+ */
+
+ function add(out, a, b) {
+ out[0] = a[0] + b[0];
+ out[1] = a[1] + b[1];
+ return out;
+ }
+ /**
+ * Subtracts vector b from vector a
+ *
+ * @param {vec2} out the receiving vector
+ * @param {ReadonlyVec2} a the first operand
+ * @param {ReadonlyVec2} b the second operand
+ * @returns {vec2} out
+ */
+
+ function subtract(out, a, b) {
+ out[0] = a[0] - b[0];
+ out[1] = a[1] - b[1];
+ return out;
+ }
+ /**
+ * Multiplies two vec2's
+ *
+ * @param {vec2} out the receiving vector
+ * @param {ReadonlyVec2} a the first operand
+ * @param {ReadonlyVec2} b the second operand
+ * @returns {vec2} out
+ */
+
+ function multiply(out, a, b) {
+ out[0] = a[0] * b[0];
+ out[1] = a[1] * b[1];
+ return out;
+ }
+ /**
+ * Divides two vec2's
+ *
+ * @param {vec2} out the receiving vector
+ * @param {ReadonlyVec2} a the first operand
+ * @param {ReadonlyVec2} b the second operand
+ * @returns {vec2} out
+ */
+
+ function divide(out, a, b) {
+ out[0] = a[0] / b[0];
+ out[1] = a[1] / b[1];
+ return out;
+ }
+ /**
+ * Math.ceil the components of a vec2
+ *
+ * @param {vec2} out the receiving vector
+ * @param {ReadonlyVec2} a vector to ceil
+ * @returns {vec2} out
+ */
+
+ function ceil(out, a) {
+ out[0] = Math.ceil(a[0]);
+ out[1] = Math.ceil(a[1]);
+ return out;
+ }
+ /**
+ * Math.floor the components of a vec2
+ *
+ * @param {vec2} out the receiving vector
+ * @param {ReadonlyVec2} a vector to floor
+ * @returns {vec2} out
+ */
+
+ function floor(out, a) {
+ out[0] = Math.floor(a[0]);
+ out[1] = Math.floor(a[1]);
+ return out;
+ }
+ /**
+ * Returns the minimum of two vec2's
+ *
+ * @param {vec2} out the receiving vector
+ * @param {ReadonlyVec2} a the first operand
+ * @param {ReadonlyVec2} b the second operand
+ * @returns {vec2} out
+ */
+
+ function min(out, a, b) {
+ out[0] = Math.min(a[0], b[0]);
+ out[1] = Math.min(a[1], b[1]);
+ return out;
+ }
+ /**
+ * Returns the maximum of two vec2's
+ *
+ * @param {vec2} out the receiving vector
+ * @param {ReadonlyVec2} a the first operand
+ * @param {ReadonlyVec2} b the second operand
+ * @returns {vec2} out
+ */
+
+ function max(out, a, b) {
+ out[0] = Math.max(a[0], b[0]);
+ out[1] = Math.max(a[1], b[1]);
+ return out;
+ }
+ /**
+ * Math.round the components of a vec2
+ *
+ * @param {vec2} out the receiving vector
+ * @param {ReadonlyVec2} a vector to round
+ * @returns {vec2} out
+ */
+
+ function round(out, a) {
+ out[0] = Math.round(a[0]);
+ out[1] = Math.round(a[1]);
+ return out;
+ }
+ /**
+ * Scales a vec2 by a scalar number
+ *
+ * @param {vec2} out the receiving vector
+ * @param {ReadonlyVec2} a the vector to scale
+ * @param {Number} b amount to scale the vector by
+ * @returns {vec2} out
+ */
+
+ function scale(out, a, b) {
+ out[0] = a[0] * b;
+ out[1] = a[1] * b;
+ return out;
+ }
+ /**
+ * Adds two vec2's after scaling the second operand by a scalar value
+ *
+ * @param {vec2} out the receiving vector
+ * @param {ReadonlyVec2} a the first operand
+ * @param {ReadonlyVec2} b the second operand
+ * @param {Number} scale the amount to scale b by before adding
+ * @returns {vec2} out
+ */
+
+ function scaleAndAdd(out, a, b, scale) {
+ out[0] = a[0] + b[0] * scale;
+ out[1] = a[1] + b[1] * scale;
+ return out;
+ }
+ /**
+ * Calculates the euclidian distance between two vec2's
+ *
+ * @param {ReadonlyVec2} a the first operand
+ * @param {ReadonlyVec2} b the second operand
+ * @returns {Number} distance between a and b
+ */
+
+ function distance(a, b) {
+ var x = b[0] - a[0],
+ y = b[1] - a[1];
+ return Math.hypot(x, y);
+ }
+ /**
+ * Calculates the squared euclidian distance between two vec2's
+ *
+ * @param {ReadonlyVec2} a the first operand
+ * @param {ReadonlyVec2} b the second operand
+ * @returns {Number} squared distance between a and b
+ */
+
+ function squaredDistance(a, b) {
+ var x = b[0] - a[0],
+ y = b[1] - a[1];
+ return x * x + y * y;
+ }
+ /**
+ * Calculates the length of a vec2
+ *
+ * @param {ReadonlyVec2} a vector to calculate length of
+ * @returns {Number} length of a
+ */
+
+ function length(a) {
+ var x = a[0],
+ y = a[1];
+ return Math.hypot(x, y);
+ }
+ /**
+ * Calculates the squared length of a vec2
+ *
+ * @param {ReadonlyVec2} a vector to calculate squared length of
+ * @returns {Number} squared length of a
+ */
+
+ function squaredLength(a) {
+ var x = a[0],
+ y = a[1];
+ return x * x + y * y;
+ }
+ /**
+ * Negates the components of a vec2
+ *
+ * @param {vec2} out the receiving vector
+ * @param {ReadonlyVec2} a vector to negate
+ * @returns {vec2} out
+ */
+
+ function negate(out, a) {
+ out[0] = -a[0];
+ out[1] = -a[1];
+ return out;
+ }
+ /**
+ * Returns the inverse of the components of a vec2
+ *
+ * @param {vec2} out the receiving vector
+ * @param {ReadonlyVec2} a vector to invert
+ * @returns {vec2} out
+ */
+
+ function inverse(out, a) {
+ out[0] = 1.0 / a[0];
+ out[1] = 1.0 / a[1];
+ return out;
+ }
+ /**
+ * Normalize a vec2
+ *
+ * @param {vec2} out the receiving vector
+ * @param {ReadonlyVec2} a vector to normalize
+ * @returns {vec2} out
+ */
+
+ function normalize(out, a) {
+ var x = a[0],
+ y = a[1];
+ var len = x * x + y * y;
+
+ if (len > 0) {
+ //TODO: evaluate use of glm_invsqrt here?
+ len = 1 / Math.sqrt(len);
+ }
+
+ out[0] = a[0] * len;
+ out[1] = a[1] * len;
+ return out;
+ }
+ /**
+ * Calculates the dot product of two vec2's
+ *
+ * @param {ReadonlyVec2} a the first operand
+ * @param {ReadonlyVec2} b the second operand
+ * @returns {Number} dot product of a and b
+ */
+
+ function dot(a, b) {
+ return a[0] * b[0] + a[1] * b[1];
+ }
+ /**
+ * Computes the cross product of two vec2's
+ * Note that the cross product must by definition produce a 3D vector
+ *
+ * @param {vec3} out the receiving vector
+ * @param {ReadonlyVec2} a the first operand
+ * @param {ReadonlyVec2} b the second operand
+ * @returns {vec3} out
+ */
+
+ function cross(out, a, b) {
+ var z = a[0] * b[1] - a[1] * b[0];
+ out[0] = out[1] = 0;
+ out[2] = z;
+ return out;
+ }
+ /**
+ * Performs a linear interpolation between two vec2's
+ *
+ * @param {vec2} out the receiving vector
+ * @param {ReadonlyVec2} a the first operand
+ * @param {ReadonlyVec2} b the second operand
+ * @param {Number} t interpolation amount, in the range [0-1], between the two inputs
+ * @returns {vec2} out
+ */
+
+ function lerp(out, a, b, t) {
+ var ax = a[0],
+ ay = a[1];
+ out[0] = ax + t * (b[0] - ax);
+ out[1] = ay + t * (b[1] - ay);
+ return out;
+ }
+ /**
+ * Generates a random vector with the given scale
+ *
+ * @param {vec2} out the receiving vector
+ * @param {Number} [scale] Length of the resulting vector. If omitted, a unit vector will be returned
+ * @returns {vec2} out
+ */
+
+ function random(out, scale) {
+ scale = scale || 1.0;
+ var r = RANDOM() * 2.0 * Math.PI;
+ out[0] = Math.cos(r) * scale;
+ out[1] = Math.sin(r) * scale;
+ return out;
+ }
+ /**
+ * Transforms the vec2 with a mat2
+ *
+ * @param {vec2} out the receiving vector
+ * @param {ReadonlyVec2} a the vector to transform
+ * @param {ReadonlyMat2} m matrix to transform with
+ * @returns {vec2} out
+ */
+
+ function transformMat2(out, a, m) {
+ var x = a[0],
+ y = a[1];
+ out[0] = m[0] * x + m[2] * y;
+ out[1] = m[1] * x + m[3] * y;
+ return out;
+ }
+ /**
+ * Transforms the vec2 with a mat2d
+ *
+ * @param {vec2} out the receiving vector
+ * @param {ReadonlyVec2} a the vector to transform
+ * @param {ReadonlyMat2d} m matrix to transform with
+ * @returns {vec2} out
+ */
+
+ function transformMat2d(out, a, m) {
+ var x = a[0],
+ y = a[1];
+ out[0] = m[0] * x + m[2] * y + m[4];
+ out[1] = m[1] * x + m[3] * y + m[5];
+ return out;
+ }
+ /**
+ * Transforms the vec2 with a mat3
+ * 3rd vector component is implicitly '1'
+ *
+ * @param {vec2} out the receiving vector
+ * @param {ReadonlyVec2} a the vector to transform
+ * @param {ReadonlyMat3} m matrix to transform with
+ * @returns {vec2} out
+ */
+
+ function transformMat3(out, a, m) {
+ var x = a[0],
+ y = a[1];
+ out[0] = m[0] * x + m[3] * y + m[6];
+ out[1] = m[1] * x + m[4] * y + m[7];
+ return out;
+ }
+ /**
+ * Transforms the vec2 with a mat4
+ * 3rd vector component is implicitly '0'
+ * 4th vector component is implicitly '1'
+ *
+ * @param {vec2} out the receiving vector
+ * @param {ReadonlyVec2} a the vector to transform
+ * @param {ReadonlyMat4} m matrix to transform with
+ * @returns {vec2} out
+ */
+
+ function transformMat4(out, a, m) {
+ var x = a[0];
+ var y = a[1];
+ out[0] = m[0] * x + m[4] * y + m[12];
+ out[1] = m[1] * x + m[5] * y + m[13];
+ return out;
+ }
+ /**
+ * Rotate a 2D vector
+ * @param {vec2} out The receiving vec2
+ * @param {ReadonlyVec2} a The vec2 point to rotate
+ * @param {ReadonlyVec2} b The origin of the rotation
+ * @param {Number} rad The angle of rotation in radians
+ * @returns {vec2} out
+ */
+
+ function rotate(out, a, b, rad) {
+ //Translate point to the origin
+ var p0 = a[0] - b[0],
+ p1 = a[1] - b[1],
+ sinC = Math.sin(rad),
+ cosC = Math.cos(rad); //perform rotation and translate to correct position
+
+ out[0] = p0 * cosC - p1 * sinC + b[0];
+ out[1] = p0 * sinC + p1 * cosC + b[1];
+ return out;
+ }
+ /**
+ * Get the angle between two 2D vectors
+ * @param {ReadonlyVec2} a The first operand
+ * @param {ReadonlyVec2} b The second operand
+ * @returns {Number} The angle in radians
+ */
+
+ function angle(a, b) {
+ var x1 = a[0],
+ y1 = a[1],
+ x2 = b[0],
+ y2 = b[1],
+ // mag is the product of the magnitudes of a and b
+ mag = Math.sqrt((x1 * x1 + y1 * y1) * (x2 * x2 + y2 * y2)),
+ // mag &&.. short circuits if mag == 0
+ cosine = mag && (x1 * x2 + y1 * y2) / mag; // Math.min(Math.max(cosine, -1), 1) clamps the cosine between -1 and 1
+
+ return Math.acos(Math.min(Math.max(cosine, -1), 1));
+ }
+ /**
+ * Set the components of a vec2 to zero
+ *
+ * @param {vec2} out the receiving vector
+ * @returns {vec2} out
+ */
+
+ function zero(out) {
+ out[0] = 0.0;
+ out[1] = 0.0;
+ return out;
+ }
+ /**
+ * Returns a string representation of a vector
+ *
+ * @param {ReadonlyVec2} a vector to represent as a string
+ * @returns {String} string representation of the vector
+ */
+
+ function str(a) {
+ return "vec2(" + a[0] + ", " + a[1] + ")";
+ }
+ /**
+ * Returns whether or not the vectors exactly have the same elements in the same position (when compared with ===)
+ *
+ * @param {ReadonlyVec2} a The first vector.
+ * @param {ReadonlyVec2} b The second vector.
+ * @returns {Boolean} True if the vectors are equal, false otherwise.
+ */
+
+ function exactEquals(a, b) {
+ return a[0] === b[0] && a[1] === b[1];
+ }
+ /**
+ * Returns whether or not the vectors have approximately the same elements in the same position.
+ *
+ * @param {ReadonlyVec2} a The first vector.
+ * @param {ReadonlyVec2} b The second vector.
+ * @returns {Boolean} True if the vectors are equal, false otherwise.
+ */
+
+ function equals(a, b) {
+ var a0 = a[0],
+ a1 = a[1];
+ var b0 = b[0],
+ b1 = b[1];
+ return Math.abs(a0 - b0) <= EPSILON * Math.max(1.0, Math.abs(a0), Math.abs(b0)) && Math.abs(a1 - b1) <= EPSILON * Math.max(1.0, Math.abs(a1), Math.abs(b1));
+ }
+ /**
+ * Alias for {@link vec2.length}
+ * @function
+ */
+
+ var len = length;
+ /**
+ * Alias for {@link vec2.subtract}
+ * @function
+ */
+
+ var sub = subtract;
+ /**
+ * Alias for {@link vec2.multiply}
+ * @function
+ */
+
+ var mul = multiply;
+ /**
+ * Alias for {@link vec2.divide}
+ * @function
+ */
+
+ var div = divide;
+ /**
+ * Alias for {@link vec2.distance}
+ * @function
+ */
+
+ var dist = distance;
+ /**
+ * Alias for {@link vec2.squaredDistance}
+ * @function
+ */
+
+ var sqrDist = squaredDistance;
+ /**
+ * Alias for {@link vec2.squaredLength}
+ * @function
+ */
+
+ var sqrLen = squaredLength;
+ /**
+ * Perform some operation over an array of vec2s.
+ *
+ * @param {Array} a the array of vectors to iterate over
+ * @param {Number} stride Number of elements between the start of each vec2. If 0 assumes tightly packed
+ * @param {Number} offset Number of elements to skip at the beginning of the array
+ * @param {Number} count Number of vec2s to iterate over. If 0 iterates over entire array
+ * @param {Function} fn Function to call for each vector in the array
+ * @param {Object} [arg] additional argument to pass to fn
+ * @returns {Array} a
+ * @function
+ */
+
+ var forEach = function () {
+ var vec = create();
+ return function (a, stride, offset, count, fn, arg) {
+ var i, l;
+
+ if (!stride) {
+ stride = 2;
+ }
+
+ if (!offset) {
+ offset = 0;
+ }
+
+ if (count) {
+ l = Math.min(count * stride + offset, a.length);
+ } else {
+ l = a.length;
+ }
+
+ for (i = offset; i < l; i += stride) {
+ vec[0] = a[i];
+ vec[1] = a[i + 1];
+ fn(vec, vec, arg);
+ a[i] = vec[0];
+ a[i + 1] = vec[1];
+ }
+
+ return a;
+ };
+ }();
+
+ var vec2 = /*#__PURE__*/Object.freeze({
+ __proto__: null,
+ create: create,
+ clone: clone,
+ fromValues: fromValues,
+ copy: copy,
+ set: set,
+ add: add,
+ subtract: subtract,
+ multiply: multiply,
+ divide: divide,
+ ceil: ceil,
+ floor: floor,
+ min: min,
+ max: max,
+ round: round,
+ scale: scale,
+ scaleAndAdd: scaleAndAdd,
+ distance: distance,
+ squaredDistance: squaredDistance,
+ length: length,
+ squaredLength: squaredLength,
+ negate: negate,
+ inverse: inverse,
+ normalize: normalize,
+ dot: dot,
+ cross: cross,
+ lerp: lerp,
+ random: random,
+ transformMat2: transformMat2,
+ transformMat2d: transformMat2d,
+ transformMat3: transformMat3,
+ transformMat4: transformMat4,
+ rotate: rotate,
+ angle: angle,
+ zero: zero,
+ str: str,
+ exactEquals: exactEquals,
+ equals: equals,
+ len: len,
+ sub: sub,
+ mul: mul,
+ div: div,
+ dist: dist,
+ sqrDist: sqrDist,
+ sqrLen: sqrLen,
+ forEach: forEach
+ });
+
+ exports.glMatrix = common;
+ exports.mat2 = mat2;
+ exports.mat2d = mat2d;
+ exports.mat3 = mat3;
+ exports.mat4 = mat4;
+ exports.quat = quat;
+ exports.quat2 = quat2;
+ exports.vec2 = vec2;
+ exports.vec3 = vec3;
+ exports.vec4 = vec4;
+
+ Object.defineProperty(exports, '__esModule', { value: true });
+
+}));
diff --git a/scripts/hardware_wallet.js b/scripts/hardware_wallet.js
new file mode 100755
index 0000000..906e58d
--- /dev/null
+++ b/scripts/hardware_wallet.js
@@ -0,0 +1,7062 @@
+// Use strict
+"use strict";
+
+
+// Classes
+
+// HardwareWallet class
+class HardwareWallet {
+
+ // Public
+
+ // Constructor
+ constructor(application) {
+
+ // Set application
+ this.application = application;
+
+ // Set transport to no transport
+ this.transport = HardwareWallet.NO_TRANSPORT;
+
+ // Set root public key to no root public key
+ this.rootPublicKey = HardwareWallet.NO_ROOT_PUBLIC_KEY;
+
+ // Set seed cookie to no seed cookie
+ this.seedCookie = HardwareWallet.NO_SEED_COOKIE;
+
+ // Set connected to false
+ this.connected = false;
+
+ // Set in use to false
+ this.inUse = false;
+
+ // Set exclusive lock obtained to false
+ this.exclusiveLockObtained = false;
+
+ // Set exclusive lock release event index
+ this.exclusiveLockReleaseEventIndex = 0;
+
+ // Set locked to false
+ this.locked = false;
+
+ // Set connection type to USB connection type
+ this.connectionType = HardwareWallet.USB_CONNECTION_TYPE;
+
+ // Set wallet key path
+ this.walletKeyPath = Wallet.NO_KEY_PATH;
+
+ // Set hardware type
+ this.hardwareType = HardwareWallet.LEDGER_HARDWARE_TYPE;
+ }
+
+ // Get root public key
+ getRootPublicKey() {
+
+ // Return copy of root public key
+ return new Uint8Array(this.rootPublicKey);
+ }
+
+ // Get seed cookie
+ getSeedCookie() {
+
+ // Return seed cookie
+ return this.seedCookie;
+ }
+
+ // Get connection type
+ getConnectionType() {
+
+ // Return connection type
+ return this.connectionType;
+ }
+
+ // Get hardware type
+ getHardwareType() {
+
+ // Return hardware type
+ return this.hardwareType;
+ }
+
+ // Is connected
+ isConnected() {
+
+ // Return if connected
+ return this.connected === true;
+ }
+
+ // Set in use
+ setInUse(inUse) {
+
+ // Set is in use
+ this.inUse = inUse;
+ }
+
+ // Get in use
+ getInUse() {
+
+ // Return in use
+ return this.inUse;
+ }
+
+ // Is locked
+ isLocked() {
+
+ // Return if locked
+ return this.locked === true;
+ }
+
+ // Close
+ close() {
+
+ // Check if root public key exists
+ if(this.rootPublicKey !== HardwareWallet.NO_ROOT_PUBLIC_KEY) {
+
+ // Securely clear the root public key
+ this.rootPublicKey.fill(0);
+
+ // Set root public key to no root public key
+ this.rootPublicKey = HardwareWallet.NO_ROOT_PUBLIC_KEY;
+ }
+
+ // Set seed cookie to no seed cookie
+ this.seedCookie = HardwareWallet.NO_SEED_COOKIE;
+
+ // Clear in use
+ this.inUse = false;
+
+ // Set self
+ var self = this;
+
+ // Return promise
+ return new Promise(function(resolve, reject) {
+
+ // Return obtainined exclusive lock
+ return self.obtainExclusiveLock().then(function() {
+
+ // Check if connected
+ if(self.isConnected() === true) {
+
+ // Return closing transport and catch errors
+ return self.transport.close().catch(function(error) {
+
+ // Finally
+ }).finally(function() {
+
+ // Clear connected
+ self.connected = false;
+
+ // Set transport to no transport
+ self.transport = HardwareWallet.NO_TRANSPORT;
+
+ // Trigger disconnect event
+ $(self).trigger(HardwareWallet.DISCONNECT_EVENT);
+
+ // Release exclusive lock
+ self.releaseExclusiveLock();
+
+ // Resolve
+ resolve();
+ });
+ }
+
+ // Otherwise
+ else {
+
+ // Release exclusive lock
+ self.releaseExclusiveLock();
+
+ // Resolve
+ resolve();
+ }
+ });
+ });
+ }
+
+ // Connect
+ connect(hardwareWalletDescriptor, failOnLock = false, text = HardwareWallet.NO_TEXT, textArguments = [], allowUnlock = false, preventMessages = false, cancelOccurred = Common.NO_CANCEL_OCCURRED) {
+
+ // Set self
+ var self = this;
+
+ // Return promise
+ return new Promise(function(resolve, reject) {
+
+ // Return obtainined exclusive lock
+ return self.obtainExclusiveLock().then(function() {
+
+ // Check if not connected
+ if(self.isConnected() === false) {
+
+ // Function get hardware wallet transport
+ var getHardwareWalletTransport = function() {
+
+ // Return promise
+ return new Promise(function(resolve, reject) {
+
+ // Check if connecting to any hardware wallet descriptor
+ if(hardwareWalletDescriptor === HardwareWallet.ANY_HARDWARE_WALLET_DESCRIPTOR) {
+
+ // Check if USB is supported
+ if("usb" in navigator === true) {
+
+ // Return connecting to any USB hardware wallet
+ return HardwareWalletUsbTransport.request().then(function(transport) {
+
+ // Set connection type to USB connection type
+ self.connectionType = HardwareWallet.USB_CONNECTION_TYPE;
+
+ // Resolve transport
+ resolve(transport);
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Check if error is that user canceled action
+ if(typeof error === "object" && error !== null && (("code" in error === true && error["code"] === HardwareWallet.NOT_FOUND_ERROR_CODE) || ("name" in error === true && error["name"] === "NotFoundError"))) {
+
+ // Check if Bluetooth is supported
+ if("bluetooth" in navigator === true) {
+
+ // Return connecting to any Bluetooth hardware wallet
+ return HardwareWalletBluetoothTransport.request().then(function(transport) {
+
+ // Set connection type to Bluetooth connection type
+ self.connectionType = HardwareWallet.BLUETOOTH_CONNECTION_TYPE;
+
+ // Resolve transport
+ resolve(transport);
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Check if error is that user canceled action
+ if(typeof error === "object" && error !== null && (("code" in error === true && error["code"] === HardwareWallet.NOT_FOUND_ERROR_CODE) || ("name" in error === true && error["name"] === "NotFoundError"))) {
+
+ // Reject error
+ reject(Message.createText(Language.getDefaultTranslation('No hardware wallet was selected.')) + ((Common.isPopup() === true) ? Message.createText(Language.getDefaultTranslation('(?<=.) ')) + Message.createText(Language.getDefaultTranslation('You may need to open this extension in a tab or window if it\'s not able to connect to a hardware wallet.')) : ""));
+ }
+
+ // Otherwise check if error was a connection error
+ else if(typeof error === "object" && error !== null && (("code" in error === true && error["code"] === HardwareWallet.NETWORK_ERROR_CODE) || ("name" in error === true && error["name"] === "NetworkError"))) {
+
+ // Check if is an extension
+ if(Common.isExtension() === true) {
+
+ // Reject error
+ reject(Message.createText(Language.getDefaultTranslation('Connecting to that hardware wallet failed.')) + Message.createText(Language.getDefaultTranslation('(?<=.) ')) + Message.createText(Language.getDefaultTranslation('You may need to be already paired with the device before this extension can connect to it.')));
+ }
+
+ // Otherwise check if is an app
+ else if(Common.isApp() === true) {
+
+ // Reject error
+ reject(Message.createText(Language.getDefaultTranslation('Connecting to that hardware wallet failed.')) + Message.createText(Language.getDefaultTranslation('(?<=.) ')) + Message.createText(Language.getDefaultTranslation('You may need to be already paired with the device before this app can connect to it.')));
+ }
+
+ // Otherwise
+ else {
+
+ // Reject error
+ reject(Message.createText(Language.getDefaultTranslation('Connecting to that hardware wallet failed.')) + Message.createText(Language.getDefaultTranslation('(?<=.) ')) + Message.createText(Language.getDefaultTranslation('You may need to be already paired with the device before this site can connect to it.')));
+ }
+ }
+
+ // Otherwise check if error was an invalid state error
+ else if(typeof error === "object" && error !== null && (("code" in error === true && error["code"] === HardwareWallet.INVALID_STATE_ERROR_CODE) || ("name" in error === true && error["name"] === "InvalidStateError"))) {
+
+ // Reject error
+ reject(Message.createText(Language.getDefaultTranslation('That hardware wallet is currently in use.')));
+ }
+
+ // Otherwise
+ else {
+
+ // Reject error
+ reject(Message.createText(Language.getDefaultTranslation('Connecting to that hardware wallet failed.')));
+ }
+ });
+ }
+
+ // Otherwise
+ else {
+
+ // Reject error
+ reject(Message.createText(Language.getDefaultTranslation('No hardware wallet was selected.')) + ((Common.isPopup() === true) ? Message.createText(Language.getDefaultTranslation('(?<=.) ')) + Message.createText(Language.getDefaultTranslation('You may need to open this extension in a tab or window if it\'s not able to connect to a hardware wallet.')) : ""));
+ }
+ }
+
+ // Otherwise check if error was an invalid state error
+ else if(typeof error === "object" && error !== null && (("code" in error === true && error["code"] === HardwareWallet.INVALID_STATE_ERROR_CODE) || ("name" in error === true && error["name"] === "InvalidStateError"))) {
+
+ // Reject error
+ reject(Message.createText(Language.getDefaultTranslation('That hardware wallet is currently in use.')));
+ }
+
+ // Otherwise
+ else {
+
+ // Reject error
+ reject(Message.createText(Language.getDefaultTranslation('Connecting to that hardware wallet failed.')));
+ }
+ });
+ }
+
+ // Otherwise check if Bluetooth is supported
+ else if("bluetooth" in navigator === true) {
+
+ // Return connecting to any Bluetooth hardware wallet
+ return HardwareWalletBluetoothTransport.request().then(function(transport) {
+
+ // Set connection type to Bluetooth connection type
+ self.connectionType = HardwareWallet.BLUETOOTH_CONNECTION_TYPE;
+
+ // Resolve transport
+ resolve(transport);
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Check if error is that user canceled action
+ if(typeof error === "object" && error !== null && (("code" in error === true && error["code"] === HardwareWallet.NOT_FOUND_ERROR_CODE) || ("name" in error === true && error["name"] === "NotFoundError"))) {
+
+ // Reject error
+ reject(Message.createText(Language.getDefaultTranslation('No hardware wallet was selected.')) + ((Common.isPopup() === true) ? Message.createText(Language.getDefaultTranslation('(?<=.) ')) + Message.createText(Language.getDefaultTranslation('You may need to open this extension in a tab or window if it\'s not able to connect to a hardware wallet.')) : ""));
+ }
+
+ // Otherwise check if error was a connection error
+ else if(typeof error === "object" && error !== null && (("code" in error === true && error["code"] === HardwareWallet.NETWORK_ERROR_CODE) || ("name" in error === true && error["name"] === "NetworkError"))) {
+
+ // Check if is an extension
+ if(Common.isExtension() === true) {
+
+ // Reject error
+ reject(Message.createText(Language.getDefaultTranslation('Connecting to that hardware wallet failed.')) + Message.createText(Language.getDefaultTranslation('(?<=.) ')) + Message.createText(Language.getDefaultTranslation('You may need to be already paired with the device before this extension can connect to it.')));
+ }
+
+ // Otherwise check if is an app
+ else if(Common.isApp() === true) {
+
+ // Reject error
+ reject(Message.createText(Language.getDefaultTranslation('Connecting to that hardware wallet failed.')) + Message.createText(Language.getDefaultTranslation('(?<=.) ')) + Message.createText(Language.getDefaultTranslation('You may need to be already paired with the device before this app can connect to it.')));
+ }
+
+ // Otherwise
+ else {
+
+ // Reject error
+ reject(Message.createText(Language.getDefaultTranslation('Connecting to that hardware wallet failed.')) + Message.createText(Language.getDefaultTranslation('(?<=.) ')) + Message.createText(Language.getDefaultTranslation('You may need to be already paired with the device before this site can connect to it.')));
+ }
+ }
+
+ // Otherwise check if error was an invalid state error
+ else if(typeof error === "object" && error !== null && (("code" in error === true && error["code"] === HardwareWallet.INVALID_STATE_ERROR_CODE) || ("name" in error === true && error["name"] === "InvalidStateError"))) {
+
+ // Reject error
+ reject(Message.createText(Language.getDefaultTranslation('That hardware wallet is currently in use.')));
+ }
+
+ // Otherwise
+ else {
+
+ // Reject error
+ reject(Message.createText(Language.getDefaultTranslation('Connecting to that hardware wallet failed.')));
+ }
+ });
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Return connecting to provided hardware wallet descriptor
+ return HardwareWalletUsbTransport.request(hardwareWalletDescriptor).then(function(transport) {
+
+ // Set connection type to USB connection type
+ self.connectionType = HardwareWallet.USB_CONNECTION_TYPE;
+
+ // Resolve transport
+ resolve(transport);
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Reject error
+ reject(Message.createText(Language.getDefaultTranslation('Connecting to that hardware wallet failed.')));
+ });
+ }
+ });
+ };
+
+ // Return gwetting hardware wallet transport
+ return getHardwareWalletTransport().then(function(transport) {
+
+ // Set transport
+ self.transport = transport;
+
+ // Get product name
+ var productName = self.transport["deviceModel"]["productName"];
+
+ // Get minimum compatible version
+ var minimumCompatibleVersion = self.getMinimumCompatibleVersion();
+
+ // Check transport's type
+ switch(self.transport.type) {
+
+ // Ledger type
+ case HardwareWalletDefinitions.LEDGER_TRANSPORT_TYPE:
+
+ // Set hardware type to Ledger hardware type
+ self.hardwareType = HardwareWallet.LEDGER_HARDWARE_TYPE;
+
+ // Break
+ break;
+
+ // Trezor type
+ case HardwareWalletDefinitions.TREZOR_TRANSPORT_TYPE:
+
+ // Set hardware type to Trezor hardware type
+ self.hardwareType = HardwareWallet.TREZOR_HARDWARE_TYPE;
+
+ // Break
+ break;
+ }
+
+ // Set connected
+ self.connected = true;
+
+ // Transport on disconnect
+ self.transport.on("disconnect", function() {
+
+ // Obtain exclusive lock
+ self.obtainExclusiveLock().then(function() {
+
+ // Check if connected
+ if(self.isConnected() === true) {
+
+ // Clear connected
+ self.connected = false;
+
+ // Set transport to no transport
+ self.transport = HardwareWallet.NO_TRANSPORT;
+
+ // Trigger disconnect event
+ $(self).trigger(HardwareWallet.DISCONNECT_EVENT);
+ }
+
+ // Release exclusive lock
+ self.releaseExclusiveLock();
+ });
+ });
+
+ // Check transport's type
+ switch(self.transport.type) {
+
+ // Ledger type
+ case HardwareWalletDefinitions.LEDGER_TRANSPORT_TYPE:
+
+ // Return requesting getting the application information from the hardware wallet
+ return self.send(HardwareWalletDefinitions.LEDGER_GET_APPLICATION_INFORMATION_MESSAGE_TYPE, HardwareWallet.NO_PARAMETER, HardwareWallet.NO_PARAMETER, HardwareWallet.NO_DATA, HardwareWalletDefinitions.LEDGER_SUCCESS_MESSAGE_TYPE, text, textArguments, allowUnlock, failOnLock, preventMessages, cancelOccurred).then(function(response) {
+
+ // Check if response contains an application name length
+ if(response["length"] >= Uint8Array["BYTES_PER_ELEMENT"] + Uint8Array["BYTES_PER_ELEMENT"]) {
+
+ // Get application name length
+ var applicationNameLength = response[Uint8Array["BYTES_PER_ELEMENT"]];
+
+ // Check if response contains an application name
+ if(response["length"] >= Uint8Array["BYTES_PER_ELEMENT"] + Uint8Array["BYTES_PER_ELEMENT"] + applicationNameLength) {
+
+ // Set error occured to false
+ var errorOccurred = false;
+
+ // Try
+ try {
+ // Get application name from the response
+ var applicationName = (new TextDecoder("utf-8", {"fatal": true})).decode(response.subarray(Uint8Array["BYTES_PER_ELEMENT"] + Uint8Array["BYTES_PER_ELEMENT"], Uint8Array["BYTES_PER_ELEMENT"] + Uint8Array["BYTES_PER_ELEMENT"] + applicationNameLength));
+ }
+
+ // Catch error
+ catch(error) {
+
+ // Set error occurred
+ errorOccurred = true;
+ }
+
+ // Check if error didn't occur and application name is valid
+ if(errorOccurred === false && applicationName === HardwareWallet.APPLICATION_NAME) {
+
+ // Check if response contains an application version length
+ if(response["length"] >= Uint8Array["BYTES_PER_ELEMENT"] + Uint8Array["BYTES_PER_ELEMENT"] + applicationNameLength + Uint8Array["BYTES_PER_ELEMENT"]) {
+
+ // Get application version length
+ var applicationVersionLength = response[Uint8Array["BYTES_PER_ELEMENT"] + Uint8Array["BYTES_PER_ELEMENT"] + applicationNameLength];
+
+ // Check if response contains an application version
+ if(response["length"] >= Uint8Array["BYTES_PER_ELEMENT"] + Uint8Array["BYTES_PER_ELEMENT"] + applicationNameLength + Uint8Array["BYTES_PER_ELEMENT"] + applicationVersionLength) {
+
+ // Try
+ try {
+
+ // Get application version from the response
+ var applicationVersion = (new TextDecoder("utf-8", {"fatal": true})).decode(response.subarray(Uint8Array["BYTES_PER_ELEMENT"] + Uint8Array["BYTES_PER_ELEMENT"] + applicationNameLength + Uint8Array["BYTES_PER_ELEMENT"], Uint8Array["BYTES_PER_ELEMENT"] + Uint8Array["BYTES_PER_ELEMENT"] + applicationNameLength + Uint8Array["BYTES_PER_ELEMENT"] + applicationVersionLength));
+ }
+
+ // Catch error
+ catch(error) {
+
+ // Set error occurred
+ errorOccurred = true;
+ }
+
+ // Check if error didn't occur and application version is compatible
+ if(errorOccurred === false && HardwareWallet.isCompatibleVersion(applicationVersion, minimumCompatibleVersion) === true) {
+
+ // Return requesting getting the seed cookie from the hardware wallet
+ return self.send(HardwareWalletDefinitions.MIMBLEWIMBLE_COIN_GET_SEED_COOKIE_MESSAGE_TYPE, HardwareWallet.NO_PARAMETER, HardwareWallet.NO_PARAMETER, {
+
+ // Coin type
+ "Coin Type": Consensus.getWalletType(),
+
+ // Network type
+ "Network Type": Consensus.getNetworkType(),
+
+ // Account
+ "Account": new BigNumber(HardwareWallet.ACCOUNT)
+
+ }, HardwareWalletDefinitions.LEDGER_SUCCESS_MESSAGE_TYPE, text, textArguments, allowUnlock, failOnLock, preventMessages, cancelOccurred).then(function(response) {
+
+ // Check if response is valid
+ if(response["length"] === HardwareWallet.SEED_COOKIE_LENGTH) {
+
+ // Get seed cookie from response
+ self.seedCookie = response;
+
+ // Release exclusive lock
+ self.releaseExclusiveLock();
+
+ // Resolve
+ resolve();
+ }
+
+ // Otherwise
+ else {
+
+ // Return closing transport and catch errors
+ return self.transport.close().catch(function(error) {
+
+ // Finally
+ }).finally(function() {
+
+ // Clear connected
+ self.connected = false;
+
+ // Set transport to no transport
+ self.transport = HardwareWallet.NO_TRANSPORT;
+
+ // Trigger disconnect event
+ $(self).trigger(HardwareWallet.DISCONNECT_EVENT);
+
+ // Release exclusive lock
+ self.releaseExclusiveLock();
+
+ // Reject error
+ reject(Message.createText(Language.getDefaultTranslation('Getting the seed cookie from that %1$x hardware wallet failed.'), [productName]) + Message.createText(Language.getDefaultTranslation('(?<=.) ')) + Message.createText((failOnLock === false) ? Language.getDefaultTranslation('Make sure that the correct app is open on the hardware wallet.') : Language.getDefaultTranslation('Make sure that the correct app is open on the hardware wallet and that the hardware wallet isn\'t locked.')));
+ });
+ }
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Return closing transport and catch errors
+ return self.transport.close().catch(function(error) {
+
+ // Finally
+ }).finally(function() {
+
+ // Clear connected
+ self.connected = false;
+
+ // Set transport to no transport
+ self.transport = HardwareWallet.NO_TRANSPORT;
+
+ // Trigger disconnect event
+ $(self).trigger(HardwareWallet.DISCONNECT_EVENT);
+
+ // Release exclusive lock
+ self.releaseExclusiveLock();
+
+ // Check if error is canceled
+ if(error === Common.CANCELED_ERROR) {
+
+ // Reject error
+ reject(error);
+ }
+
+ // Otherwise check if error is disconnected error
+ else if(error === HardwareWallet.DISCONNECTED_ERROR) {
+
+ // Reject error
+ reject(Message.createText(Language.getDefaultTranslation('That hardware wallet was disconnected.')));
+ }
+
+ // Otherwise
+ else {
+
+ // Reject error
+ reject(Message.createText(Language.getDefaultTranslation('Getting the seed cookie from that %1$x hardware wallet failed.'), [productName]) + Message.createText(Language.getDefaultTranslation('(?<=.) ')) + Message.createText((failOnLock === false) ? Language.getDefaultTranslation('Make sure that the correct app is open on the hardware wallet.') : Language.getDefaultTranslation('Make sure that the correct app is open on the hardware wallet and that the hardware wallet isn\'t locked.')));
+ }
+ });
+ });
+ }
+
+ // Otherwise
+ else {
+
+ // Return closing transport and catch errors
+ return self.transport.close().catch(function(error) {
+
+ // Finally
+ }).finally(function() {
+
+ // Clear connected
+ self.connected = false;
+
+ // Set transport to no transport
+ self.transport = HardwareWallet.NO_TRANSPORT;
+
+ // Trigger disconnect event
+ $(self).trigger(HardwareWallet.DISCONNECT_EVENT);
+
+ // Release exclusive lock
+ self.releaseExclusiveLock();
+
+ // Reject error
+ reject(Message.createText(Language.getDefaultTranslation('The app on that %1$x hardware wallet isn\'t compatible. Update the app on the hardware wallet to version %2$v or newer to continue.'), [productName, minimumCompatibleVersion]));
+ });
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Return closing transport and catch errors
+ return self.transport.close().catch(function(error) {
+
+ // Finally
+ }).finally(function() {
+
+ // Clear connected
+ self.connected = false;
+
+ // Set transport to no transport
+ self.transport = HardwareWallet.NO_TRANSPORT;
+
+ // Trigger disconnect event
+ $(self).trigger(HardwareWallet.DISCONNECT_EVENT);
+
+ // Release exclusive lock
+ self.releaseExclusiveLock();
+
+ // Reject error
+ reject(Message.createText(Language.getDefaultTranslation('Getting the app information from that %1$x hardware wallet failed.'), [productName]) + Message.createText(Language.getDefaultTranslation('(?<=.) ')) + Message.createText((failOnLock === false) ? Language.getDefaultTranslation('Make sure that the correct app is open on the hardware wallet.') : Language.getDefaultTranslation('Make sure that the correct app is open on the hardware wallet and that the hardware wallet isn\'t locked.')));
+ });
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Return closing transport and catch errors
+ return self.transport.close().catch(function(error) {
+
+ // Finally
+ }).finally(function() {
+
+ // Clear connected
+ self.connected = false;
+
+ // Set transport to no transport
+ self.transport = HardwareWallet.NO_TRANSPORT;
+
+ // Trigger disconnect event
+ $(self).trigger(HardwareWallet.DISCONNECT_EVENT);
+
+ // Release exclusive lock
+ self.releaseExclusiveLock();
+
+ // Reject error
+ reject(Message.createText(Language.getDefaultTranslation('Getting the app information from that %1$x hardware wallet failed.'), [productName]) + Message.createText(Language.getDefaultTranslation('(?<=.) ')) + Message.createText((failOnLock === false) ? Language.getDefaultTranslation('Make sure that the correct app is open on the hardware wallet.') : Language.getDefaultTranslation('Make sure that the correct app is open on the hardware wallet and that the hardware wallet isn\'t locked.')));
+ });
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Return closing transport and catch errors
+ return self.transport.close().catch(function(error) {
+
+ // Finally
+ }).finally(function() {
+
+ // Clear connected
+ self.connected = false;
+
+ // Set transport to no transport
+ self.transport = HardwareWallet.NO_TRANSPORT;
+
+ // Trigger disconnect event
+ $(self).trigger(HardwareWallet.DISCONNECT_EVENT);
+
+ // Release exclusive lock
+ self.releaseExclusiveLock();
+
+ // Reject error
+ reject(Message.createText(Language.getDefaultTranslation('Getting the app information from that %1$x hardware wallet failed.'), [productName]) + Message.createText(Language.getDefaultTranslation('(?<=.) ')) + Message.createText((failOnLock === false) ? Language.getDefaultTranslation('Make sure that the correct app is open on the hardware wallet.') : Language.getDefaultTranslation('Make sure that the correct app is open on the hardware wallet and that the hardware wallet isn\'t locked.')));
+ });
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Return closing transport and catch errors
+ return self.transport.close().catch(function(error) {
+
+ // Finally
+ }).finally(function() {
+
+ // Clear connected
+ self.connected = false;
+
+ // Set transport to no transport
+ self.transport = HardwareWallet.NO_TRANSPORT;
+
+ // Trigger disconnect event
+ $(self).trigger(HardwareWallet.DISCONNECT_EVENT);
+
+ // Release exclusive lock
+ self.releaseExclusiveLock();
+
+ // Reject error
+ reject(Message.createText(Language.getDefaultTranslation('Getting the app information from that %1$x hardware wallet failed.'), [productName]) + Message.createText(Language.getDefaultTranslation('(?<=.) ')) + Message.createText((failOnLock === false) ? Language.getDefaultTranslation('Make sure that the correct app is open on the hardware wallet.') : Language.getDefaultTranslation('Make sure that the correct app is open on the hardware wallet and that the hardware wallet isn\'t locked.')));
+ });
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Return closing transport and catch errors
+ return self.transport.close().catch(function(error) {
+
+ // Finally
+ }).finally(function() {
+
+ // Clear connected
+ self.connected = false;
+
+ // Set transport to no transport
+ self.transport = HardwareWallet.NO_TRANSPORT;
+
+ // Trigger disconnect event
+ $(self).trigger(HardwareWallet.DISCONNECT_EVENT);
+
+ // Release exclusive lock
+ self.releaseExclusiveLock();
+
+ // Reject error
+ reject(Message.createText(Language.getDefaultTranslation('Getting the app information from that %1$x hardware wallet failed.'), [productName]) + Message.createText(Language.getDefaultTranslation('(?<=.) ')) + Message.createText((failOnLock === false) ? Language.getDefaultTranslation('Make sure that the correct app is open on the hardware wallet.') : Language.getDefaultTranslation('Make sure that the correct app is open on the hardware wallet and that the hardware wallet isn\'t locked.')));
+ });
+ }
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Return closing transport and catch errors
+ return self.transport.close().catch(function(error) {
+
+ // Finally
+ }).finally(function() {
+
+ // Clear connected
+ self.connected = false;
+
+ // Set transport to no transport
+ self.transport = HardwareWallet.NO_TRANSPORT;
+
+ // Trigger disconnect event
+ $(self).trigger(HardwareWallet.DISCONNECT_EVENT);
+
+ // Release exclusive lock
+ self.releaseExclusiveLock();
+
+ // Check if error is canceled
+ if(error === Common.CANCELED_ERROR) {
+
+ // Reject error
+ reject(error);
+ }
+
+ // Otherwise check if error is disconnected error
+ else if(error === HardwareWallet.DISCONNECTED_ERROR) {
+
+ // Reject error
+ reject(Message.createText(Language.getDefaultTranslation('That hardware wallet was disconnected.')));
+ }
+
+ // Otherwise
+ else {
+
+ // Reject error
+ reject(Message.createText(Language.getDefaultTranslation('Getting the app information from that %1$x hardware wallet failed.'), [productName]) + Message.createText(Language.getDefaultTranslation('(?<=.) ')) + Message.createText((failOnLock === false) ? Language.getDefaultTranslation('Make sure that the correct app is open on the hardware wallet.') : Language.getDefaultTranslation('Make sure that the correct app is open on the hardware wallet and that the hardware wallet isn\'t locked.')));
+ }
+ });
+ });
+
+ // Trezor type
+ case HardwareWalletDefinitions.TREZOR_TRANSPORT_TYPE:
+
+ // Return requesting initializing on the hardware wallet
+ return self.send(HardwareWalletDefinitions.TREZOR_INITIALIZE_MESSAGE_TYPE, HardwareWallet.NO_PARAMETER, HardwareWallet.NO_PARAMETER, HardwareWallet.NO_DATA, HardwareWalletDefinitions.TREZOR_FEATURES_MESSAGE_TYPE, text, textArguments, allowUnlock, failOnLock, preventMessages, cancelOccurred).then(function(response) {
+
+ // Check if response contains a major version, minor version, patch version, pin protection, passphrase protection, initialized, unlocked, and model
+ if(response["length"] >= Uint32Array["BYTES_PER_ELEMENT"] + Uint32Array["BYTES_PER_ELEMENT"] + Uint32Array["BYTES_PER_ELEMENT"] + Uint8Array["BYTES_PER_ELEMENT"] + Uint8Array["BYTES_PER_ELEMENT"] + Uint8Array["BYTES_PER_ELEMENT"] + Uint8Array["BYTES_PER_ELEMENT"] + Uint8Array["BYTES_PER_ELEMENT"]) {
+
+ // Get major version
+ var majorVersion = parseInt(Common.toHexString(response.subarray(0, Uint32Array["BYTES_PER_ELEMENT"])), Common.HEX_NUMBER_BASE);
+
+ // Get minor version
+ var minorVersion = parseInt(Common.toHexString(response.subarray(Uint32Array["BYTES_PER_ELEMENT"], Uint32Array["BYTES_PER_ELEMENT"] + Uint32Array["BYTES_PER_ELEMENT"])), Common.HEX_NUMBER_BASE);
+
+ // Get patch version
+ var patchVersion = parseInt(Common.toHexString(response.subarray(Uint32Array["BYTES_PER_ELEMENT"] + Uint32Array["BYTES_PER_ELEMENT"], Uint32Array["BYTES_PER_ELEMENT"] + Uint32Array["BYTES_PER_ELEMENT"] + Uint32Array["BYTES_PER_ELEMENT"])), Common.HEX_NUMBER_BASE);
+
+ // Get firmware version
+ var firmwareVersion = majorVersion.toFixed() + "." + minorVersion.toFixed() + "." + patchVersion.toFixed();
+
+ // Get model length
+ var modelLength = response[Uint32Array["BYTES_PER_ELEMENT"] + Uint32Array["BYTES_PER_ELEMENT"] + Uint32Array["BYTES_PER_ELEMENT"] + Uint8Array["BYTES_PER_ELEMENT"] + Uint8Array["BYTES_PER_ELEMENT"] + Uint8Array["BYTES_PER_ELEMENT"] + Uint8Array["BYTES_PER_ELEMENT"]];
+
+ // Check if response contains a model and capabilities
+ if(response["length"] >= Uint32Array["BYTES_PER_ELEMENT"] + Uint32Array["BYTES_PER_ELEMENT"] + Uint32Array["BYTES_PER_ELEMENT"] + Uint8Array["BYTES_PER_ELEMENT"] + Uint8Array["BYTES_PER_ELEMENT"] + Uint8Array["BYTES_PER_ELEMENT"] + Uint8Array["BYTES_PER_ELEMENT"] + Uint8Array["BYTES_PER_ELEMENT"] + modelLength + Uint8Array["BYTES_PER_ELEMENT"]) {
+
+ // Set error occured to false
+ var errorOccurred = false;
+
+ // Try
+ try {
+
+ // Get model
+ var model = (new TextDecoder("utf-8", {"fatal": true})).decode(response.subarray(Uint32Array["BYTES_PER_ELEMENT"] + Uint32Array["BYTES_PER_ELEMENT"] + Uint32Array["BYTES_PER_ELEMENT"] + Uint8Array["BYTES_PER_ELEMENT"] + Uint8Array["BYTES_PER_ELEMENT"] + Uint8Array["BYTES_PER_ELEMENT"] + Uint8Array["BYTES_PER_ELEMENT"] + Uint8Array["BYTES_PER_ELEMENT"], Uint32Array["BYTES_PER_ELEMENT"] + Uint32Array["BYTES_PER_ELEMENT"] + Uint32Array["BYTES_PER_ELEMENT"] + Uint8Array["BYTES_PER_ELEMENT"] + Uint8Array["BYTES_PER_ELEMENT"] + Uint8Array["BYTES_PER_ELEMENT"] + Uint8Array["BYTES_PER_ELEMENT"] + Uint8Array["BYTES_PER_ELEMENT"] + modelLength));
+ }
+
+ // Catch error
+ catch(error) {
+
+ // Set error occurred
+ errorOccurred = true;
+ }
+
+ // Check if error didn't occur
+ if(errorOccurred === false) {
+
+ // Check model
+ switch(model) {
+
+ // One
+ case "1":
+
+ // Update transport's product name to include model
+ self.transport["deviceModel"]["productName"] += " Model One";
+
+ // Break
+ break;
+
+ // T
+ case "T":
+
+ // Update transport's product name to include model
+ self.transport["deviceModel"]["productName"] += " Model T";
+
+ // Break
+ break;
+
+ // Safe 3, Safe 5, or default
+ case "Safe 3":
+ case "Safe 5":
+ default:
+
+ // Update transport's product name to include model
+ self.transport["deviceModel"]["productName"] += " " + model;
+
+ // Break
+ break;
+ }
+
+ // Update product name
+ productName = self.transport["deviceModel"]["productName"];
+
+ // Update minimum compatible version
+ minimumCompatibleVersion = self.getMinimumCompatibleVersion();
+
+ // Check if firmware version is compatible
+ if(HardwareWallet.isCompatibleVersion(firmwareVersion, minimumCompatibleVersion) === true) {
+
+ // Get capabilities length
+ var capabilitiesLength = response[Uint32Array["BYTES_PER_ELEMENT"] + Uint32Array["BYTES_PER_ELEMENT"] + Uint32Array["BYTES_PER_ELEMENT"] + Uint8Array["BYTES_PER_ELEMENT"] + Uint8Array["BYTES_PER_ELEMENT"] + Uint8Array["BYTES_PER_ELEMENT"] + Uint8Array["BYTES_PER_ELEMENT"] + Uint8Array["BYTES_PER_ELEMENT"] + modelLength];
+
+ // Check if response contains capabilities and passphrase always on device
+ if(response["length"] >= Uint32Array["BYTES_PER_ELEMENT"] + Uint32Array["BYTES_PER_ELEMENT"] + Uint32Array["BYTES_PER_ELEMENT"] + Uint8Array["BYTES_PER_ELEMENT"] + Uint8Array["BYTES_PER_ELEMENT"] + Uint8Array["BYTES_PER_ELEMENT"] + Uint8Array["BYTES_PER_ELEMENT"] + Uint8Array["BYTES_PER_ELEMENT"] + modelLength + Uint8Array["BYTES_PER_ELEMENT"] + capabilitiesLength + Uint8Array["BYTES_PER_ELEMENT"]) {
+
+ // Get capabilities
+ var capabilities = response.subarray(Uint32Array["BYTES_PER_ELEMENT"] + Uint32Array["BYTES_PER_ELEMENT"] + Uint32Array["BYTES_PER_ELEMENT"] + Uint8Array["BYTES_PER_ELEMENT"] + Uint8Array["BYTES_PER_ELEMENT"] + Uint8Array["BYTES_PER_ELEMENT"] + Uint8Array["BYTES_PER_ELEMENT"] + Uint8Array["BYTES_PER_ELEMENT"] + modelLength + Uint8Array["BYTES_PER_ELEMENT"], Uint32Array["BYTES_PER_ELEMENT"] + Uint32Array["BYTES_PER_ELEMENT"] + Uint32Array["BYTES_PER_ELEMENT"] + Uint8Array["BYTES_PER_ELEMENT"] + Uint8Array["BYTES_PER_ELEMENT"] + Uint8Array["BYTES_PER_ELEMENT"] + Uint8Array["BYTES_PER_ELEMENT"] + Uint8Array["BYTES_PER_ELEMENT"] + modelLength + Uint8Array["BYTES_PER_ELEMENT"] + capabilitiesLength);
+
+ // Check if device is MimbleWimble Coin capable
+ if(capabilities.indexOf(HardwareWallet.MIMBLEWIMBLE_COIN_CAPABLE) !== Common.INDEX_NOT_FOUND) {
+
+ // Get initialized
+ var initialized = response[Uint32Array["BYTES_PER_ELEMENT"] + Uint32Array["BYTES_PER_ELEMENT"] + Uint32Array["BYTES_PER_ELEMENT"] + Uint8Array["BYTES_PER_ELEMENT"] + Uint8Array["BYTES_PER_ELEMENT"]] !== 0;
+
+ // Check if device is initialized
+ if(initialized === true) {
+
+ // Get pin enabled
+ var pinEnabled = response[Uint32Array["BYTES_PER_ELEMENT"] + Uint32Array["BYTES_PER_ELEMENT"] + Uint32Array["BYTES_PER_ELEMENT"]] !== 0;
+
+ // Get unlocked
+ var unlocked = response[Uint32Array["BYTES_PER_ELEMENT"] + Uint32Array["BYTES_PER_ELEMENT"] + Uint32Array["BYTES_PER_ELEMENT"] + Uint8Array["BYTES_PER_ELEMENT"] + Uint8Array["BYTES_PER_ELEMENT"] + Uint8Array["BYTES_PER_ELEMENT"]] !== 0;
+
+ // Get passphrase enabled
+ var passphraseEnabled = response[Uint32Array["BYTES_PER_ELEMENT"] + Uint32Array["BYTES_PER_ELEMENT"] + Uint32Array["BYTES_PER_ELEMENT"] + Uint8Array["BYTES_PER_ELEMENT"]] !== 0;
+
+ // Get passphrase always on device
+ var passphraseAlwaysOnDevice = response[Uint32Array["BYTES_PER_ELEMENT"] + Uint32Array["BYTES_PER_ELEMENT"] + Uint32Array["BYTES_PER_ELEMENT"] + Uint8Array["BYTES_PER_ELEMENT"] + Uint8Array["BYTES_PER_ELEMENT"] + Uint8Array["BYTES_PER_ELEMENT"] + Uint8Array["BYTES_PER_ELEMENT"] + Uint8Array["BYTES_PER_ELEMENT"] + modelLength + Uint8Array["BYTES_PER_ELEMENT"] + capabilitiesLength] !== 0;
+
+ // Check if not set to fail on lock, pin isn't enabled, or device is unlocked
+ if(failOnLock === false || pinEnabled === false || unlocked === true) {
+
+ // Check if not set to fail on lock, passphrase isn't enabled, or passphrase always on device isn't enabled
+ if(failOnLock === false || passphraseEnabled === false || passphraseAlwaysOnDevice === false) {
+
+ // Return requesting getting the seed cookie from the hardware wallet
+ return self.send(HardwareWalletDefinitions.MIMBLEWIMBLE_COIN_GET_SEED_COOKIE_MESSAGE_TYPE, HardwareWallet.NO_PARAMETER, HardwareWallet.NO_PARAMETER, {
+
+ // Coin type
+ "Coin Type": Consensus.getWalletType(),
+
+ // Network type
+ "Network Type": Consensus.getNetworkType(),
+
+ // Account
+ "Account": new BigNumber(HardwareWallet.ACCOUNT)
+
+ }, HardwareWalletDefinitions.MIMBLEWIMBLE_COIN_SEED_COOKIE_MESSAGE_TYPE, text, textArguments, allowUnlock, failOnLock, preventMessages, cancelOccurred).then(function(response) {
+
+ // Check if response is valid
+ if(response["length"] === HardwareWallet.SEED_COOKIE_LENGTH) {
+
+ // Get seed cookie from response
+ self.seedCookie = response;
+
+ // Release exclusive lock
+ self.releaseExclusiveLock();
+
+ // Resolve
+ resolve();
+ }
+
+ // Otherwise
+ else {
+
+ // Return closing transport and catch errors
+ return self.transport.close().catch(function(error) {
+
+ // Finally
+ }).finally(function() {
+
+ // Clear connected
+ self.connected = false;
+
+ // Set transport to no transport
+ self.transport = HardwareWallet.NO_TRANSPORT;
+
+ // Trigger disconnect event
+ $(self).trigger(HardwareWallet.DISCONNECT_EVENT);
+
+ // Release exclusive lock
+ self.releaseExclusiveLock();
+
+ // Reject error
+ reject(Message.createText(Language.getDefaultTranslation('Getting the seed cookie from that %1$x hardware wallet failed.'), [productName]));
+ });
+ }
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Return closing transport and catch errors
+ return self.transport.close().catch(function() {
+
+ // Finally
+ }).finally(function() {
+
+ // Clear connected
+ self.connected = false;
+
+ // Set transport to no transport
+ self.transport = HardwareWallet.NO_TRANSPORT;
+
+ // Trigger disconnect event
+ $(self).trigger(HardwareWallet.DISCONNECT_EVENT);
+
+ // Release exclusive lock
+ self.releaseExclusiveLock();
+
+ // Check if error is canceled
+ if(error === Common.CANCELED_ERROR) {
+
+ // Reject error
+ reject(error);
+ }
+
+ // Otherwise check if error is disconnected error
+ else if(error === HardwareWallet.DISCONNECTED_ERROR) {
+
+ // Reject error
+ reject(Message.createText(Language.getDefaultTranslation('That hardware wallet was disconnected.')));
+ }
+
+ // Otherwise
+ else {
+
+ // Check if failure occurred
+ if(Object.isObject(error) === true && "Message Type" in error === true && error["Message Type"] === HardwareWalletDefinitions.TREZOR_FAILURE_MESSAGE_TYPE) {
+
+ // Try
+ try {
+
+ // Decode error
+ var decodedError = self.decode(error["Message Type"], error["Data"], HardwareWalletDefinitions.TREZOR_TRANSPORT_TYPE);
+ }
+
+ // Catch errors
+ catch(decodeError) {
+
+ // Set error occurred
+ errorOccurred = true;
+ }
+
+ // Check if an error didn't occur
+ if(errorOccurred === false) {
+
+ // Check if user rejected the request
+ if(decodedError["length"] >= Uint8Array["BYTES_PER_ELEMENT"] && (decodedError[0] === HardwareWalletDefinitions.TREZOR_ACTION_CANCELED_FAILURE_TYPE || decodedError[0] === HardwareWalletDefinitions.TREZOR_PIN_CANCELED_FAILURE_TYPE)) {
+
+ // Reject canceled error
+ reject(Common.CANCELED_ERROR);
+
+ // Return
+ return;
+ }
+ }
+ }
+
+ // Reject error
+ reject(Message.createText(Language.getDefaultTranslation('Getting the seed cookie from that %1$x hardware wallet failed.'), [productName]));
+ }
+ });
+ });
+ }
+
+ // Otherwise
+ else {
+
+ // Return closing transport and catch errors
+ return self.transport.close().catch(function(error) {
+
+ // Finally
+ }).finally(function() {
+
+ // Clear connected
+ self.connected = false;
+
+ // Set transport to no transport
+ self.transport = HardwareWallet.NO_TRANSPORT;
+
+ // Trigger disconnect event
+ $(self).trigger(HardwareWallet.DISCONNECT_EVENT);
+
+ // Release exclusive lock
+ self.releaseExclusiveLock();
+
+ // Reject error
+ reject(Message.createText(Language.getDefaultTranslation('That %1$x hardware wallet is locked. Unlock the hardware wallet to continue.'), [productName]));
+ });
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Return closing transport and catch errors
+ return self.transport.close().catch(function(error) {
+
+ // Finally
+ }).finally(function() {
+
+ // Clear connected
+ self.connected = false;
+
+ // Set transport to no transport
+ self.transport = HardwareWallet.NO_TRANSPORT;
+
+ // Trigger disconnect event
+ $(self).trigger(HardwareWallet.DISCONNECT_EVENT);
+
+ // Release exclusive lock
+ self.releaseExclusiveLock();
+
+ // Reject error
+ reject(Message.createText(Language.getDefaultTranslation('That %1$x hardware wallet is locked. Unlock the hardware wallet to continue.'), [productName]));
+ });
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Return closing transport and catch errors
+ return self.transport.close().catch(function(error) {
+
+ // Finally
+ }).finally(function() {
+
+ // Clear connected
+ self.connected = false;
+
+ // Set transport to no transport
+ self.transport = HardwareWallet.NO_TRANSPORT;
+
+ // Trigger disconnect event
+ $(self).trigger(HardwareWallet.DISCONNECT_EVENT);
+
+ // Release exclusive lock
+ self.releaseExclusiveLock();
+
+ // Reject error
+ reject(Message.createText(Language.getDefaultTranslation('That %1$x hardware wallet isn\'t initialized. Initialize the hardware wallet to continue.'), [productName]));
+ });
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Return closing transport and catch errors
+ return self.transport.close().catch(function(error) {
+
+ // Finally
+ }).finally(function() {
+
+ // Clear connected
+ self.connected = false;
+
+ // Set transport to no transport
+ self.transport = HardwareWallet.NO_TRANSPORT;
+
+ // Trigger disconnect event
+ $(self).trigger(HardwareWallet.DISCONNECT_EVENT);
+
+ // Release exclusive lock
+ self.releaseExclusiveLock();
+
+ // Reject error
+ reject(Message.createText(Language.getDefaultTranslation('The firmware on that %1$x hardware wallet isn\'t compatible. Install a compatible firmware on the hardware wallet to continue.'), [productName]));
+ });
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Return closing transport and catch errors
+ return self.transport.close().catch(function(error) {
+
+ // Finally
+ }).finally(function() {
+
+ // Clear connected
+ self.connected = false;
+
+ // Set transport to no transport
+ self.transport = HardwareWallet.NO_TRANSPORT;
+
+ // Trigger disconnect event
+ $(self).trigger(HardwareWallet.DISCONNECT_EVENT);
+
+ // Release exclusive lock
+ self.releaseExclusiveLock();
+
+ // Reject error
+ reject(Message.createText(Language.getDefaultTranslation('Getting the features from that %1$x hardware wallet failed.'), [productName]));
+ });
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Return closing transport and catch errors
+ return self.transport.close().catch(function(error) {
+
+ // Finally
+ }).finally(function() {
+
+ // Clear connected
+ self.connected = false;
+
+ // Set transport to no transport
+ self.transport = HardwareWallet.NO_TRANSPORT;
+
+ // Trigger disconnect event
+ $(self).trigger(HardwareWallet.DISCONNECT_EVENT);
+
+ // Release exclusive lock
+ self.releaseExclusiveLock();
+
+ // Reject error
+ reject(Message.createText(Language.getDefaultTranslation('The firmware on that %1$x hardware wallet isn\'t compatible. Update the firmware on the hardware wallet to version %2$v or newer to continue.'), [productName, minimumCompatibleVersion]));
+ });
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Return closing transport and catch errors
+ return self.transport.close().catch(function(error) {
+
+ // Finally
+ }).finally(function() {
+
+ // Clear connected
+ self.connected = false;
+
+ // Set transport to no transport
+ self.transport = HardwareWallet.NO_TRANSPORT;
+
+ // Trigger disconnect event
+ $(self).trigger(HardwareWallet.DISCONNECT_EVENT);
+
+ // Release exclusive lock
+ self.releaseExclusiveLock();
+
+ // Reject error
+ reject(Message.createText(Language.getDefaultTranslation('Getting the features from that %1$x hardware wallet failed.'), [productName]));
+ });
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Return closing transport and catch errors
+ return self.transport.close().catch(function(error) {
+
+ // Finally
+ }).finally(function() {
+
+ // Clear connected
+ self.connected = false;
+
+ // Set transport to no transport
+ self.transport = HardwareWallet.NO_TRANSPORT;
+
+ // Trigger disconnect event
+ $(self).trigger(HardwareWallet.DISCONNECT_EVENT);
+
+ // Release exclusive lock
+ self.releaseExclusiveLock();
+
+ // Reject error
+ reject(Message.createText(Language.getDefaultTranslation('Getting the features from that %1$x hardware wallet failed.'), [productName]));
+ });
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Return closing transport and catch errors
+ return self.transport.close().catch(function(error) {
+
+ // Finally
+ }).finally(function() {
+
+ // Clear connected
+ self.connected = false;
+
+ // Set transport to no transport
+ self.transport = HardwareWallet.NO_TRANSPORT;
+
+ // Trigger disconnect event
+ $(self).trigger(HardwareWallet.DISCONNECT_EVENT);
+
+ // Release exclusive lock
+ self.releaseExclusiveLock();
+
+ // Reject error
+ reject(Message.createText(Language.getDefaultTranslation('Getting the features from that %1$x hardware wallet failed.'), [productName]));
+ });
+ }
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Return closing transport and catch errors
+ return self.transport.close().catch(function() {
+
+ // Finally
+ }).finally(function() {
+
+ // Clear connected
+ self.connected = false;
+
+ // Set transport to no transport
+ self.transport = HardwareWallet.NO_TRANSPORT;
+
+ // Trigger disconnect event
+ $(self).trigger(HardwareWallet.DISCONNECT_EVENT);
+
+ // Release exclusive lock
+ self.releaseExclusiveLock();
+
+ // Check if error is canceled
+ if(error === Common.CANCELED_ERROR) {
+
+ // Reject error
+ reject(error);
+ }
+
+ // Otherwise check if error is disconnected error
+ else if(error === HardwareWallet.DISCONNECTED_ERROR) {
+
+ // Reject error
+ reject(Message.createText(Language.getDefaultTranslation('That hardware wallet was disconnected.')));
+ }
+
+ // Otherwise
+ else {
+
+ // Check if failure occurred
+ if(Object.isObject(error) === true && "Message Type" in error === true && error["Message Type"] === HardwareWalletDefinitions.TREZOR_FAILURE_MESSAGE_TYPE) {
+
+ // Initialize error occurred
+ var errorOccurred = false;
+
+ // Try
+ try {
+
+ // Decode error
+ var decodedError = self.decode(error["Message Type"], error["Data"], HardwareWalletDefinitions.TREZOR_TRANSPORT_TYPE);
+ }
+
+ // Catch errors
+ catch(decodeError) {
+
+ // Set error occurred
+ errorOccurred = true;
+ }
+
+ // Check if an error didn't occur
+ if(errorOccurred === false) {
+
+ // Check if user rejected the request
+ if(decodedError["length"] >= Uint8Array["BYTES_PER_ELEMENT"] && (decodedError[0] === HardwareWalletDefinitions.TREZOR_ACTION_CANCELED_FAILURE_TYPE || decodedError[0] === HardwareWalletDefinitions.TREZOR_PIN_CANCELED_FAILURE_TYPE)) {
+
+ // Reject canceled error
+ reject(Common.CANCELED_ERROR);
+
+ // Return
+ return;
+ }
+ }
+ }
+
+ // Reject error
+ reject(Message.createText(Language.getDefaultTranslation('Getting the features from that %1$x hardware wallet failed.'), [productName]) + Message.createText(Language.getDefaultTranslation('(?<=.) ')) + Message.createText(Language.getDefaultTranslation('You may need to disconnect and reconnect the hardware wallet to connect to it.')));
+ }
+ });
+ });
+ }
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Release exclusive lock
+ self.releaseExclusiveLock();
+
+ // Reject error
+ reject(error);
+ });
+ }
+
+ // Otherwise
+ else {
+
+ // Release exclusive lock
+ self.releaseExclusiveLock();
+
+ // Reject error
+ reject(Message.createText(Language.getDefaultTranslation('The hardware wallet is already connected.')));
+ }
+ });
+ });
+ }
+
+ // Get public key
+ getPublicKey() {
+
+ // Set self
+ var self = this;
+
+ // Return promise
+ return new Promise(function(resolve, reject) {
+
+ // Return obtainined exclusive lock
+ return self.obtainExclusiveLock().then(function() {
+
+ // Check if connected
+ if(self.isConnected() === true) {
+
+ // Get product name
+ var productName = self.transport["deviceModel"]["productName"];
+
+ // Return requesting getting the root public key from the hardware wallet
+ return self.send(HardwareWalletDefinitions.MIMBLEWIMBLE_COIN_GET_ROOT_PUBLIC_KEY_MESSAGE_TYPE, HardwareWallet.NO_PARAMETER, HardwareWallet.NO_PARAMETER, {
+
+ // Coin type
+ "Coin Type": Consensus.getWalletType(),
+
+ // Network type
+ "Network Type": Consensus.getNetworkType(),
+
+ // Account
+ "Account": new BigNumber(HardwareWallet.ACCOUNT)
+
+ }, [HardwareWalletDefinitions.LEDGER_SUCCESS_MESSAGE_TYPE, HardwareWalletDefinitions.MIMBLEWIMBLE_COIN_ROOT_PUBLIC_KEY_MESSAGE_TYPE], HardwareWallet.NO_TEXT, [], false, true, true, Common.NO_CANCEL_OCCURRED).then(function(response) {
+
+ // Check if response is valid
+ if(response["length"] === Crypto.SECP256K1_PUBLIC_KEY_LENGTH && Secp256k1Zkp.isValidPublicKey(response) === true) {
+
+ // Get root public key from response
+ self.rootPublicKey = response;
+
+ // Release exclusive lock
+ self.releaseExclusiveLock();
+
+ // Resolve root public key
+ resolve(self.getRootPublicKey());
+ }
+
+ // Otherwise
+ else {
+
+ // Securely clear response
+ response.fill(0);
+
+ // Release exclusive lock
+ self.releaseExclusiveLock();
+
+ // Check transport's type
+ switch(self.transport.type) {
+
+ // Ledger type
+ case HardwareWalletDefinitions.LEDGER_TRANSPORT_TYPE:
+
+ // Reject error
+ reject(Message.createText(Language.getDefaultTranslation('Getting the root public key from that %1$x hardware wallet failed.'), [productName]) + Message.createText(Language.getDefaultTranslation('(?<=.) ')) + Message.createText(Language.getDefaultTranslation('Make sure that the correct app is open on the hardware wallet and that the hardware wallet isn\'t locked.')));
+
+ // Break
+ break;
+
+ // Trezor type
+ case HardwareWalletDefinitions.TREZOR_TRANSPORT_TYPE:
+
+ // Reject error
+ reject(Message.createText(Language.getDefaultTranslation('Getting the root public key from that %1$x hardware wallet failed.'), [productName]));
+
+ // Break
+ break;
+ }
+ }
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Release exclusive lock
+ self.releaseExclusiveLock();
+
+ // Check if error is disconnected error
+ if(error === HardwareWallet.DISCONNECTED_ERROR) {
+
+ // Reject error
+ reject(Message.createText(Language.getDefaultTranslation('That hardware wallet was disconnected.')));
+ }
+
+ // Otherwise
+ else {
+
+ // Check transport's type
+ switch(self.transport.type) {
+
+ // Ledger type
+ case HardwareWalletDefinitions.LEDGER_TRANSPORT_TYPE:
+
+ // Check if user rejected the request
+ if(Object.isObject(error) === true && "Message Type" in error === true && error["Message Type"] === HardwareWalletDefinitions.LEDGER_USER_REJECTED_MESSAGE_TYPE) {
+
+ // Reject error
+ reject(Message.createText(Language.getDefaultTranslation('Exporting the root public key on that %1$x hardware wallet was denied.'), [productName]));
+ }
+
+ // Otherwise
+ else {
+
+ // Reject error
+ reject(Message.createText(Language.getDefaultTranslation('Getting the root public key from that %1$x hardware wallet failed.'), [productName]) + Message.createText(Language.getDefaultTranslation('(?<=.) ')) + Message.createText(Language.getDefaultTranslation('Make sure that the correct app is open on the hardware wallet and that the hardware wallet isn\'t locked.')));
+ }
+
+ // Break
+ break;
+
+ // Trezor type
+ case HardwareWalletDefinitions.TREZOR_TRANSPORT_TYPE:
+
+ // Check if failure occurred
+ if(Object.isObject(error) === true && "Message Type" in error === true && error["Message Type"] === HardwareWalletDefinitions.TREZOR_FAILURE_MESSAGE_TYPE) {
+
+ // Initialize error occurred
+ var errorOccurred = false;
+
+ // Try
+ try {
+
+ // Decode error
+ var decodedError = self.decode(error["Message Type"], error["Data"]);
+ }
+
+ // Catch errors
+ catch(decodeError) {
+
+ // Set error occurred
+ errorOccurred = true;
+ }
+
+ // Check if an error didn't occur
+ if(errorOccurred === false) {
+
+ // Check if user rejected the request
+ if(decodedError["length"] >= Uint8Array["BYTES_PER_ELEMENT"] && (decodedError[0] === HardwareWalletDefinitions.TREZOR_ACTION_CANCELED_FAILURE_TYPE || decodedError[0] === HardwareWalletDefinitions.TREZOR_PIN_CANCELED_FAILURE_TYPE)) {
+
+ // Reject error
+ reject(Message.createText(Language.getDefaultTranslation('Exporting the root public key on that %1$x hardware wallet was denied.'), [productName]));
+
+ // Return
+ return;
+ }
+ }
+ }
+
+ // Reject error
+ reject(Message.createText(Language.getDefaultTranslation('Getting the root public key from that %1$x hardware wallet failed.'), [productName]));
+
+ // Break
+ break;
+ }
+ }
+ });
+ }
+
+ // Otherwise
+ else {
+
+ // Release exclusive lock
+ self.releaseExclusiveLock();
+
+ // Reject error
+ reject(Message.createText(Language.getDefaultTranslation('That hardware wallet was disconnected.')));
+ }
+ });
+ });
+ }
+
+ // Get commit
+ getCommit(value, identifier, switchType, text = HardwareWallet.NO_TEXT, textArguments = [], allowUnlock = false, preventMessages = false, cancelOccurred = Common.NO_CANCEL_OCCURRED) {
+
+ // Set self
+ var self = this;
+
+ // Return promise
+ return new Promise(function(resolve, reject) {
+
+ // Return obtainined exclusive lock
+ return self.obtainExclusiveLock().then(function() {
+
+ // Check if connected
+ if(self.isConnected() === true) {
+
+ // Try
+ try {
+
+ // Return requesting getting the commit from the hardware wallet
+ return self.send(HardwareWalletDefinitions.MIMBLEWIMBLE_COIN_GET_COMMITMENT_MESSAGE_TYPE, HardwareWallet.NO_PARAMETER, HardwareWallet.NO_PARAMETER, {
+
+ // Coin type
+ "Coin Type": Consensus.getWalletType(),
+
+ // Network type
+ "Network Type": Consensus.getNetworkType(),
+
+ // Account
+ "Account": new BigNumber(HardwareWallet.ACCOUNT),
+
+ // Identifier
+ "Identifier": identifier.getValue(),
+
+ // Value
+ "Value": value,
+
+ // Switch type
+ "Switch Type": switchType
+
+ }, [HardwareWalletDefinitions.LEDGER_SUCCESS_MESSAGE_TYPE, HardwareWalletDefinitions.MIMBLEWIMBLE_COIN_COMMITMENT_MESSAGE_TYPE], text, textArguments, allowUnlock, false, preventMessages, cancelOccurred).then(function(response) {
+
+ // Release exclusive lock
+ self.releaseExclusiveLock();
+
+ // Check if response is valid
+ if(response["length"] === Crypto.COMMIT_LENGTH && Secp256k1Zkp.isValidCommit(response) === true) {
+
+ // Get commit from response
+ var commit = response;
+
+ // Resolve commit
+ resolve(commit);
+ }
+
+ // Otherwise
+ else {
+
+ // Reject
+ reject();
+ }
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Release exclusive lock
+ self.releaseExclusiveLock();
+
+ // Check transport's type
+ switch(self.transport.type) {
+
+ // Trezor type
+ case HardwareWalletDefinitions.TREZOR_TRANSPORT_TYPE:
+
+ // Check if failure occurred
+ if(Object.isObject(error) === true && "Message Type" in error === true && error["Message Type"] === HardwareWalletDefinitions.TREZOR_FAILURE_MESSAGE_TYPE) {
+
+ // Initialize error occurred
+ var errorOccurred = false;
+
+ // Try
+ try {
+
+ // Decode error
+ var decodedError = self.decode(error["Message Type"], error["Data"]);
+ }
+
+ // Catch errors
+ catch(decodeError) {
+
+ // Set error occurred
+ errorOccurred = true;
+ }
+
+ // Check if an error didn't occur
+ if(errorOccurred === false) {
+
+ // Check if user rejected the request
+ if(decodedError["length"] >= Uint8Array["BYTES_PER_ELEMENT"] && (decodedError[0] === HardwareWalletDefinitions.TREZOR_ACTION_CANCELED_FAILURE_TYPE || decodedError[0] === HardwareWalletDefinitions.TREZOR_PIN_CANCELED_FAILURE_TYPE)) {
+
+ // Reject canceled error
+ reject(Common.CANCELED_ERROR);
+
+ // Return
+ return;
+ }
+ }
+ }
+
+ // Break
+ break;
+ }
+
+ // Reject error
+ reject(error);
+ });
+ }
+
+ // Catch errors
+ catch(error) {
+
+ // Release exclusive lock
+ self.releaseExclusiveLock();
+
+ // Reject error
+ reject(error);
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Release exclusive lock
+ self.releaseExclusiveLock();
+
+ // Reject disconnected error
+ reject(HardwareWallet.DISCONNECTED_ERROR);
+ }
+ });
+ });
+ }
+
+ // Get proof
+ getProof(value, identifier, switchType, message, proofBuilder, text = HardwareWallet.NO_TEXT, textArguments = [], allowUnlock = false, preventMessages = false, cancelOccurred = Common.NO_CANCEL_OCCURRED) {
+
+ // Set self
+ var self = this;
+
+ // Return promise
+ return new Promise(function(resolve, reject) {
+
+ // Return obtainined exclusive lock
+ return self.obtainExclusiveLock().then(function() {
+
+ // Check if connected
+ if(self.isConnected() === true) {
+
+ // Try
+ try {
+
+ // Return requesting getting the commit from the hardware wallet
+ return self.send(HardwareWalletDefinitions.MIMBLEWIMBLE_COIN_GET_COMMITMENT_MESSAGE_TYPE, HardwareWallet.NO_PARAMETER, HardwareWallet.NO_PARAMETER, {
+
+ // Coin type
+ "Coin Type": Consensus.getWalletType(),
+
+ // Network type
+ "Network Type": Consensus.getNetworkType(),
+
+ // Account
+ "Account": new BigNumber(HardwareWallet.ACCOUNT),
+
+ // Identifier
+ "Identifier": identifier.getValue(),
+
+ // Value
+ "Value": value,
+
+ // Switch type
+ "Switch Type": switchType
+
+ }, [HardwareWalletDefinitions.LEDGER_SUCCESS_MESSAGE_TYPE, HardwareWalletDefinitions.MIMBLEWIMBLE_COIN_COMMITMENT_MESSAGE_TYPE], text, textArguments, allowUnlock, false, preventMessages, cancelOccurred).then(function(response) {
+
+ // Check if response is valid
+ if(response["length"] === Crypto.COMMIT_LENGTH && Secp256k1Zkp.isValidCommit(response) === true) {
+
+ // Get commit from response
+ var commit = response;
+
+ // Return getting rewind nonce from the proof builder
+ return proofBuilder.rewindNonce(commit).then(function(rewindNonce) {
+
+ // Try
+ try {
+
+ // Return requesting getting the proof components from the hardware wallet
+ return self.send(HardwareWalletDefinitions.MIMBLEWIMBLE_COIN_GET_BULLETPROOF_COMPONENTS_MESSAGE_TYPE, message, HardwareWallet.NO_PARAMETER, {
+
+ // Coin type
+ "Coin Type": Consensus.getWalletType(),
+
+ // Network type
+ "Network Type": Consensus.getNetworkType(),
+
+ // Message type
+ "Parameter One": message,
+
+ // Account
+ "Account": new BigNumber(HardwareWallet.ACCOUNT),
+
+ // Identifier
+ "Identifier": identifier.getValue(),
+
+ // Value
+ "Value": value,
+
+ // Switch type
+ "Switch Type": switchType
+
+ }, [HardwareWalletDefinitions.LEDGER_SUCCESS_MESSAGE_TYPE, HardwareWalletDefinitions.MIMBLEWIMBLE_COIN_BULLETPROOF_COMPONENTS_MESSAGE_TYPE], text, textArguments, allowUnlock, false, preventMessages, cancelOccurred).then(function(response) {
+
+ // Release exclusive lock
+ self.releaseExclusiveLock();
+
+ // Check if response is valid
+ if(response["length"] === Crypto.TAU_X_LENGTH + Crypto.SECP256K1_PUBLIC_KEY_LENGTH + Crypto.SECP256K1_PUBLIC_KEY_LENGTH && (Secp256k1Zkp.isValidSecretKey(response.subarray(0, Crypto.TAU_X_LENGTH)) === true || Common.arraysAreEqual(response.subarray(0, Crypto.TAU_X_LENGTH), Crypto.ZERO_SECRET_KEY) === true) && Secp256k1Zkp.isValidPublicKey(response.subarray(Crypto.TAU_X_LENGTH, Crypto.TAU_X_LENGTH + Crypto.SECP256K1_PUBLIC_KEY_LENGTH)) === true && Secp256k1Zkp.isValidPublicKey(response.subarray(Crypto.TAU_X_LENGTH + Crypto.SECP256K1_PUBLIC_KEY_LENGTH, Crypto.TAU_X_LENGTH + Crypto.SECP256K1_PUBLIC_KEY_LENGTH + Crypto.SECP256K1_PUBLIC_KEY_LENGTH)) === true) {
+
+ // Get tau x from response
+ var tauX = response.subarray(0, Crypto.TAU_X_LENGTH);
+
+ // Get t one from response
+ var tOne = response.subarray(Crypto.TAU_X_LENGTH, Crypto.TAU_X_LENGTH + Crypto.SECP256K1_PUBLIC_KEY_LENGTH);
+
+ // Get t two from response
+ var tTwo = response.subarray(Crypto.TAU_X_LENGTH + Crypto.SECP256K1_PUBLIC_KEY_LENGTH, Crypto.TAU_X_LENGTH + Crypto.SECP256K1_PUBLIC_KEY_LENGTH + Crypto.SECP256K1_PUBLIC_KEY_LENGTH);
+
+ // Try
+ try {
+
+ // Get proof message from identifier and switch type
+ var proofMessage = proofBuilder.proofMessage(identifier, switchType);
+ }
+
+ // Catch errors
+ catch(error) {
+
+ // Securely clear rewind nonce
+ rewindNonce.fill(0);
+
+ // Reject error
+ reject(error);
+
+ // Return
+ return;
+ }
+
+ // Check if creating proof with the tau x, t one, t two, commit, value, rewind nonce, and proof message was successful
+ var proof = Secp256k1Zkp.createBulletproofBlindless(tauX, tOne, tTwo, commit, value.toFixed(), rewindNonce, new Uint8Array([]), proofMessage);
+
+ if(proof !== Secp256k1Zkp.OPERATION_FAILED && Secp256k1Zkp.verifyBulletproof(proof, commit, new Uint8Array([])) === true) {
+
+ // Securely clear rewind nonce
+ rewindNonce.fill(0);
+
+ // Resolve proof
+ resolve(proof);
+ }
+
+ // Otherwise
+ else {
+
+ // Securely clear rewind nonce
+ rewindNonce.fill(0);
+
+ // Reject
+ reject();
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Securely clear rewind nonce
+ rewindNonce.fill(0);
+
+ // Reject
+ reject();
+ }
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Securely clear rewind nonce
+ rewindNonce.fill(0);
+
+ // Release exclusive lock
+ self.releaseExclusiveLock();
+
+ // Check transport's type
+ switch(self.transport.type) {
+
+ // Trezor type
+ case HardwareWalletDefinitions.TREZOR_TRANSPORT_TYPE:
+
+ // Check if failure occurred
+ if(Object.isObject(error) === true && "Message Type" in error === true && error["Message Type"] === HardwareWalletDefinitions.TREZOR_FAILURE_MESSAGE_TYPE) {
+
+ // Initialize error occurred
+ var errorOccurred = false;
+
+ // Try
+ try {
+
+ // Decode error
+ var decodedError = self.decode(error["Message Type"], error["Data"]);
+ }
+
+ // Catch errors
+ catch(decodeError) {
+
+ // Set error occurred
+ errorOccurred = true;
+ }
+
+ // Check if an error didn't occur
+ if(errorOccurred === false) {
+
+ // Check if user rejected the request
+ if(decodedError["length"] >= Uint8Array["BYTES_PER_ELEMENT"] && (decodedError[0] === HardwareWalletDefinitions.TREZOR_ACTION_CANCELED_FAILURE_TYPE || decodedError[0] === HardwareWalletDefinitions.TREZOR_PIN_CANCELED_FAILURE_TYPE)) {
+
+ // Reject canceled error
+ reject(Common.CANCELED_ERROR);
+
+ // Return
+ return;
+ }
+ }
+ }
+
+ // Break
+ break;
+ }
+
+ // Reject error
+ reject(error);
+ });
+ }
+
+ // Catch errors
+ catch(error) {
+
+ // Securely clear rewind nonce
+ rewindNonce.fill(0);
+
+ // Release exclusive lock
+ self.releaseExclusiveLock();
+
+ // Reject error
+ reject(error);
+ }
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Release exclusive lock
+ self.releaseExclusiveLock();
+
+ // Reject error
+ reject(error);
+ });
+ }
+
+ // Otherwise
+ else {
+
+ // Release exclusive lock
+ self.releaseExclusiveLock();
+
+ // Reject
+ reject();
+ }
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Release exclusive lock
+ self.releaseExclusiveLock();
+
+ // Check transport's type
+ switch(self.transport.type) {
+
+ // Trezor type
+ case HardwareWalletDefinitions.TREZOR_TRANSPORT_TYPE:
+
+ // Check if failure occurred
+ if(Object.isObject(error) === true && "Message Type" in error === true && error["Message Type"] === HardwareWalletDefinitions.TREZOR_FAILURE_MESSAGE_TYPE) {
+
+ // Initialize error occurred
+ var errorOccurred = false;
+
+ // Try
+ try {
+
+ // Decode error
+ var decodedError = self.decode(error["Message Type"], error["Data"]);
+ }
+
+ // Catch errors
+ catch(decodeError) {
+
+ // Set error occurred
+ errorOccurred = true;
+ }
+
+ // Check if an error didn't occur
+ if(errorOccurred === false) {
+
+ // Check if user rejected the request
+ if(decodedError["length"] >= Uint8Array["BYTES_PER_ELEMENT"] && (decodedError[0] === HardwareWalletDefinitions.TREZOR_ACTION_CANCELED_FAILURE_TYPE || decodedError[0] === HardwareWalletDefinitions.TREZOR_PIN_CANCELED_FAILURE_TYPE)) {
+
+ // Reject canceled error
+ reject(Common.CANCELED_ERROR);
+
+ // Return
+ return;
+ }
+ }
+ }
+
+ // Break
+ break;
+ }
+
+ // Reject error
+ reject(error);
+ });
+ }
+
+ // Catch errors
+ catch(error) {
+
+ // Release exclusive lock
+ self.releaseExclusiveLock();
+
+ // Reject error
+ reject(error);
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Release exclusive lock
+ self.releaseExclusiveLock();
+
+ // Reject disconnected error
+ reject(HardwareWallet.DISCONNECTED_ERROR);
+ }
+ });
+ });
+ }
+
+ // Get Tor address
+ getTorAddress(index, text = HardwareWallet.NO_TEXT, textArguments = [], allowUnlock = false, preventMessages = false, cancelOccurred = Common.NO_CANCEL_OCCURRED) {
+
+ // Set self
+ var self = this;
+
+ // Return promise
+ return new Promise(function(resolve, reject) {
+
+ // Return obtainined exclusive lock
+ return self.obtainExclusiveLock().then(function() {
+
+ // Check if connected
+ if(self.isConnected() === true) {
+
+ // Try
+ try {
+
+ // Return requesting getting the Tor address from the hardware wallet
+ return self.send(HardwareWalletDefinitions.MIMBLEWIMBLE_COIN_GET_ADDRESS_MESSAGE_TYPE, HardwareWallet.TOR_ADDRESS_TYPE, HardwareWallet.NO_PARAMETER, {
+
+ // Coin type
+ "Coin Type": Consensus.getWalletType(),
+
+ // Network type
+ "Network Type": Consensus.getNetworkType(),
+
+ // Address type
+ "Parameter One": HardwareWallet.TOR_ADDRESS_TYPE,
+
+ // Account
+ "Account": new BigNumber(HardwareWallet.ACCOUNT),
+
+ // Index
+ "Index": new BigNumber(index)
+
+ }, [HardwareWalletDefinitions.LEDGER_SUCCESS_MESSAGE_TYPE, HardwareWalletDefinitions.MIMBLEWIMBLE_COIN_ADDRESS_MESSAGE_TYPE], text, textArguments, allowUnlock, false, preventMessages, cancelOccurred).then(function(response) {
+
+ // Release exclusive lock
+ self.releaseExclusiveLock();
+
+ // Check if response is valid
+ if(response["length"] === ((self.transport.type === HardwareWalletDefinitions.TREZOR_TRANSPORT_TYPE) ? Uint8Array["BYTES_PER_ELEMENT"] : 0) + Tor.ADDRESS_LENGTH) {
+
+ // Try
+ try {
+
+ // Get Tor address from response
+ var torAddress = (new TextDecoder("utf-8", {"fatal": true})).decode((self.transport.type === HardwareWalletDefinitions.TREZOR_TRANSPORT_TYPE) ? response.subarray(Uint8Array["BYTES_PER_ELEMENT"]) : response);
+
+ // Get public key from Tor address
+ Tor.torAddressToPublicKey(torAddress);
+ }
+
+ // Catch errors
+ catch(error) {
+
+ // Reject error
+ reject(error);
+
+ // Return
+ return;
+ }
+
+ // Resolve Tor address
+ resolve(torAddress);
+ }
+
+ // Otherwise
+ else {
+
+ // Reject
+ reject();
+ }
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Release exclusive lock
+ self.releaseExclusiveLock();
+
+ // Check transport's type
+ switch(self.transport.type) {
+
+ // Trezor type
+ case HardwareWalletDefinitions.TREZOR_TRANSPORT_TYPE:
+
+ // Check if failure occurred
+ if(Object.isObject(error) === true && "Message Type" in error === true && error["Message Type"] === HardwareWalletDefinitions.TREZOR_FAILURE_MESSAGE_TYPE) {
+
+ // Initialize error occurred
+ var errorOccurred = false;
+
+ // Try
+ try {
+
+ // Decode error
+ var decodedError = self.decode(error["Message Type"], error["Data"]);
+ }
+
+ // Catch errors
+ catch(decodeError) {
+
+ // Set error occurred
+ errorOccurred = true;
+ }
+
+ // Check if an error didn't occur
+ if(errorOccurred === false) {
+
+ // Check if user rejected the request
+ if(decodedError["length"] >= Uint8Array["BYTES_PER_ELEMENT"] && (decodedError[0] === HardwareWalletDefinitions.TREZOR_ACTION_CANCELED_FAILURE_TYPE || decodedError[0] === HardwareWalletDefinitions.TREZOR_PIN_CANCELED_FAILURE_TYPE)) {
+
+ // Reject canceled error
+ reject(Common.CANCELED_ERROR);
+
+ // Return
+ return;
+ }
+ }
+ }
+
+ // Break
+ break;
+ }
+
+ // Reject error
+ reject(error);
+ });
+ }
+
+ // Catch errors
+ catch(error) {
+
+ // Release exclusive lock
+ self.releaseExclusiveLock();
+
+ // Reject error
+ reject(error);
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Release exclusive lock
+ self.releaseExclusiveLock();
+
+ // Reject disconnected error
+ reject(HardwareWallet.DISCONNECTED_ERROR);
+ }
+ });
+ });
+ }
+
+ // Verify Tor address
+ verifyTorAddress(index, text = HardwareWallet.NO_TEXT, textArguments = [], allowUnlock = false, preventMessages = false, cancelOccurred = Common.NO_CANCEL_OCCURRED) {
+
+ // Set self
+ var self = this;
+
+ // Return promise
+ return new Promise(function(resolve, reject) {
+
+ // Return obtainined exclusive lock
+ return self.obtainExclusiveLock().then(function() {
+
+ // Check if connected
+ if(self.isConnected() === true) {
+
+ // Try
+ try {
+
+ // Return requesting verifying the Tor address on the hardware wallet
+ return self.send(HardwareWalletDefinitions.MIMBLEWIMBLE_COIN_VERIFY_ADDRESS_MESSAGE_TYPE, HardwareWallet.TOR_ADDRESS_TYPE, HardwareWallet.NO_PARAMETER, {
+
+ // Coin type
+ "Coin Type": Consensus.getWalletType(),
+
+ // Network type
+ "Network Type": Consensus.getNetworkType(),
+
+ // Address type
+ "Parameter One": HardwareWallet.TOR_ADDRESS_TYPE,
+
+ // Account
+ "Account": new BigNumber(HardwareWallet.ACCOUNT),
+
+ // Index
+ "Index": new BigNumber(index)
+
+ }, [HardwareWalletDefinitions.LEDGER_SUCCESS_MESSAGE_TYPE, HardwareWalletDefinitions.TREZOR_SUCCESS_MESSAGE_TYPE], text, textArguments, allowUnlock, false, preventMessages, cancelOccurred).then(function(response) {
+
+ // Release exclusive lock
+ self.releaseExclusiveLock();
+
+ // Check if response is valid
+ if(response["length"] === 0) {
+
+ // Resolve
+ resolve();
+ }
+
+ // Otherwise
+ else {
+
+ // Reject
+ reject();
+ }
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Release exclusive lock
+ self.releaseExclusiveLock();
+
+ // Check transport's type
+ switch(self.transport.type) {
+
+ // Ledger type
+ case HardwareWalletDefinitions.LEDGER_TRANSPORT_TYPE:
+
+ // Check if user rejected the request
+ if(Object.isObject(error) === true && "Message Type" in error === true && error["Message Type"] === HardwareWalletDefinitions.LEDGER_USER_REJECTED_MESSAGE_TYPE) {
+
+ // Reject user rejected error
+ reject(HardwareWallet.USER_REJECTED_ERROR);
+ }
+
+ // Otherwise
+ else {
+
+ // Reject error
+ reject(error);
+ }
+
+ // Break
+ break;
+
+ // Trezor type
+ case HardwareWalletDefinitions.TREZOR_TRANSPORT_TYPE:
+
+ // Check if failure occurred
+ if(Object.isObject(error) === true && "Message Type" in error === true && error["Message Type"] === HardwareWalletDefinitions.TREZOR_FAILURE_MESSAGE_TYPE) {
+
+ // Initialize error occurred
+ var errorOccurred = false;
+
+ // Try
+ try {
+
+ // Decode error
+ var decodedError = self.decode(error["Message Type"], error["Data"]);
+ }
+
+ // Catch errors
+ catch(decodeError) {
+
+ // Set error occurred
+ errorOccurred = true;
+ }
+
+ // Check if an error didn't occur
+ if(errorOccurred === false) {
+
+ // Check if user rejected the request
+ if(decodedError["length"] >= Uint8Array["BYTES_PER_ELEMENT"] && (decodedError[0] === HardwareWalletDefinitions.TREZOR_ACTION_CANCELED_FAILURE_TYPE || decodedError[0] === HardwareWalletDefinitions.TREZOR_PIN_CANCELED_FAILURE_TYPE)) {
+
+ // Reject user rejected error
+ reject(HardwareWallet.USER_REJECTED_ERROR);
+
+ // Return
+ return;
+ }
+ }
+ }
+
+ // Reject error
+ reject(error);
+
+ // Break
+ break;
+ }
+ });
+ }
+
+ // Catch errors
+ catch(error) {
+
+ // Release exclusive lock
+ self.releaseExclusiveLock();
+
+ // Reject error
+ reject(error);
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Release exclusive lock
+ self.releaseExclusiveLock();
+
+ // Reject disconnected error
+ reject(HardwareWallet.DISCONNECTED_ERROR);
+ }
+ });
+ });
+ }
+
+ // Get MQS address
+ getMqsAddress(index, text = HardwareWallet.NO_TEXT, textArguments = [], allowUnlock = false, preventMessages = false, cancelOccurred = Common.NO_CANCEL_OCCURRED) {
+
+ // Set self
+ var self = this;
+
+ // Return promise
+ return new Promise(function(resolve, reject) {
+
+ // Return obtainined exclusive lock
+ return self.obtainExclusiveLock().then(function() {
+
+ // Check if connected
+ if(self.isConnected() === true) {
+
+ // Try
+ try {
+
+ // Return requesting getting the MQS address from the hardware wallet
+ return self.send(HardwareWalletDefinitions.MIMBLEWIMBLE_COIN_GET_ADDRESS_MESSAGE_TYPE, HardwareWallet.MQS_ADDRESS_TYPE, HardwareWallet.NO_PARAMETER, {
+
+ // Coin type
+ "Coin Type": Consensus.getWalletType(),
+
+ // Network type
+ "Network Type": Consensus.getNetworkType(),
+
+ // Address type
+ "Parameter One": HardwareWallet.MQS_ADDRESS_TYPE,
+
+ // Account
+ "Account": new BigNumber(HardwareWallet.ACCOUNT),
+
+ // Index
+ "Index": new BigNumber(index)
+
+ }, [HardwareWalletDefinitions.LEDGER_SUCCESS_MESSAGE_TYPE, HardwareWalletDefinitions.MIMBLEWIMBLE_COIN_ADDRESS_MESSAGE_TYPE], text, textArguments, allowUnlock, false, preventMessages, cancelOccurred).then(function(response) {
+
+ // Release exclusive lock
+ self.releaseExclusiveLock();
+
+ // Check if response is valid
+ if(response["length"] === ((self.transport.type === HardwareWalletDefinitions.TREZOR_TRANSPORT_TYPE) ? Uint8Array["BYTES_PER_ELEMENT"] : 0) + Mqs.ADDRESS_LENGTH) {
+
+ // Try
+ try {
+
+ // Get MQS address from response
+ var mqsAddress = (new TextDecoder("utf-8", {"fatal": true})).decode((self.transport.type === HardwareWalletDefinitions.TREZOR_TRANSPORT_TYPE) ? response.subarray(Uint8Array["BYTES_PER_ELEMENT"]) : response);
+
+ // Get public key from MQS address
+ Mqs.mqsAddressToPublicKey(mqsAddress, Consensus.getNetworkType() === Consensus.MAINNET_NETWORK_TYPE);
+ }
+
+ // Catch errors
+ catch(error) {
+
+ // Reject error
+ reject(error);
+
+ // Return
+ return;
+ }
+
+ // Resolve MQS address
+ resolve(mqsAddress);
+ }
+
+ // Otherwise
+ else {
+
+ // Reject
+ reject();
+ }
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Release exclusive lock
+ self.releaseExclusiveLock();
+
+ // Check transport's type
+ switch(self.transport.type) {
+
+ // Trezor type
+ case HardwareWalletDefinitions.TREZOR_TRANSPORT_TYPE:
+
+ // Check if failure occurred
+ if(Object.isObject(error) === true && "Message Type" in error === true && error["Message Type"] === HardwareWalletDefinitions.TREZOR_FAILURE_MESSAGE_TYPE) {
+
+ // Initialize error occurred
+ var errorOccurred = false;
+
+ // Try
+ try {
+
+ // Decode error
+ var decodedError = self.decode(error["Message Type"], error["Data"]);
+ }
+
+ // Catch errors
+ catch(decodeError) {
+
+ // Set error occurred
+ errorOccurred = true;
+ }
+
+ // Check if an error didn't occur
+ if(errorOccurred === false) {
+
+ // Check if user rejected the request
+ if(decodedError["length"] >= Uint8Array["BYTES_PER_ELEMENT"] && (decodedError[0] === HardwareWalletDefinitions.TREZOR_ACTION_CANCELED_FAILURE_TYPE || decodedError[0] === HardwareWalletDefinitions.TREZOR_PIN_CANCELED_FAILURE_TYPE)) {
+
+ // Reject canceled error
+ reject(Common.CANCELED_ERROR);
+
+ // Return
+ return;
+ }
+ }
+ }
+
+ // Break
+ break;
+ }
+
+ // Reject error
+ reject(error);
+ });
+ }
+
+ // Catch errors
+ catch(error) {
+
+ // Release exclusive lock
+ self.releaseExclusiveLock();
+
+ // Reject error
+ reject(error);
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Release exclusive lock
+ self.releaseExclusiveLock();
+
+ // Reject disconnected error
+ reject(HardwareWallet.DISCONNECTED_ERROR);
+ }
+ });
+ });
+ }
+
+ // Verify MQS address
+ verifyMqsAddress(index, text = HardwareWallet.NO_TEXT, textArguments = [], allowUnlock = false, preventMessages = false, cancelOccurred = Common.NO_CANCEL_OCCURRED) {
+
+ // Set self
+ var self = this;
+
+ // Return promise
+ return new Promise(function(resolve, reject) {
+
+ // Return obtainined exclusive lock
+ return self.obtainExclusiveLock().then(function() {
+
+ // Check if connected
+ if(self.isConnected() === true) {
+
+ // Try
+ try {
+
+ // Return requesting verifying the MQS address on the hardware wallet
+ return self.send(HardwareWalletDefinitions.MIMBLEWIMBLE_COIN_VERIFY_ADDRESS_MESSAGE_TYPE, HardwareWallet.MQS_ADDRESS_TYPE, HardwareWallet.NO_PARAMETER, {
+
+ // Coin type
+ "Coin Type": Consensus.getWalletType(),
+
+ // Network type
+ "Network Type": Consensus.getNetworkType(),
+
+ // Address type
+ "Parameter One": HardwareWallet.MQS_ADDRESS_TYPE,
+
+ // Account
+ "Account": new BigNumber(HardwareWallet.ACCOUNT),
+
+ // Index
+ "Index": new BigNumber(index)
+
+ }, [HardwareWalletDefinitions.LEDGER_SUCCESS_MESSAGE_TYPE, HardwareWalletDefinitions.TREZOR_SUCCESS_MESSAGE_TYPE], text, textArguments, allowUnlock, false, preventMessages, cancelOccurred).then(function(response) {
+
+ // Release exclusive lock
+ self.releaseExclusiveLock();
+
+ // Check if response is valid
+ if(response["length"] === 0) {
+
+ // Resolve
+ resolve();
+ }
+
+ // Otherwise
+ else {
+
+ // Reject
+ reject();
+ }
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Release exclusive lock
+ self.releaseExclusiveLock();
+
+ // Check transport's type
+ switch(self.transport.type) {
+
+ // Ledger type
+ case HardwareWalletDefinitions.LEDGER_TRANSPORT_TYPE:
+
+ // Check if user rejected the request
+ if(Object.isObject(error) === true && "Message Type" in error === true && error["Message Type"] === HardwareWalletDefinitions.LEDGER_USER_REJECTED_MESSAGE_TYPE) {
+
+ // Reject user rejected error
+ reject(HardwareWallet.USER_REJECTED_ERROR);
+ }
+
+ // Otherwise
+ else {
+
+ // Reject error
+ reject(error);
+ }
+
+ // Break
+ break;
+
+ // Trezor type
+ case HardwareWalletDefinitions.TREZOR_TRANSPORT_TYPE:
+
+ // Check if failure occurred
+ if(Object.isObject(error) === true && "Message Type" in error === true && error["Message Type"] === HardwareWalletDefinitions.TREZOR_FAILURE_MESSAGE_TYPE) {
+
+ // Initialize error occurred
+ var errorOccurred = false;
+
+ // Try
+ try {
+
+ // Decode error
+ var decodedError = self.decode(error["Message Type"], error["Data"]);
+ }
+
+ // Catch errors
+ catch(decodeError) {
+
+ // Set error occurred
+ errorOccurred = true;
+ }
+
+ // Check if an error didn't occur
+ if(errorOccurred === false) {
+
+ // Check if user rejected the request
+ if(decodedError["length"] >= Uint8Array["BYTES_PER_ELEMENT"] && (decodedError[0] === HardwareWalletDefinitions.TREZOR_ACTION_CANCELED_FAILURE_TYPE || decodedError[0] === HardwareWalletDefinitions.TREZOR_PIN_CANCELED_FAILURE_TYPE)) {
+
+ // Reject user rejected error
+ reject(HardwareWallet.USER_REJECTED_ERROR);
+
+ // Return
+ return;
+ }
+ }
+ }
+
+ // Reject error
+ reject(error);
+
+ // Break
+ break;
+ }
+ });
+ }
+
+ // Catch errors
+ catch(error) {
+
+ // Release exclusive lock
+ self.releaseExclusiveLock();
+
+ // Reject error
+ reject(error);
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Release exclusive lock
+ self.releaseExclusiveLock();
+
+ // Reject disconnected error
+ reject(HardwareWallet.DISCONNECTED_ERROR);
+ }
+ });
+ });
+ }
+
+ // Get Slatepack address
+ getSlatepackAddress(index, text = HardwareWallet.NO_TEXT, textArguments = [], allowUnlock = false, preventMessages = false, cancelOccurred = Common.NO_CANCEL_OCCURRED) {
+
+ // Set self
+ var self = this;
+
+ // Return promise
+ return new Promise(function(resolve, reject) {
+
+ // Return obtainined exclusive lock
+ return self.obtainExclusiveLock().then(function() {
+
+ // Check if connected
+ if(self.isConnected() === true) {
+
+ // Try
+ try {
+
+ // Return requesting getting the Slatepack address from the hardware wallet
+ return self.send(HardwareWalletDefinitions.MIMBLEWIMBLE_COIN_GET_ADDRESS_MESSAGE_TYPE, HardwareWallet.SLATEPACK_ADDRESS_TYPE, HardwareWallet.NO_PARAMETER, {
+
+ // Coin type
+ "Coin Type": Consensus.getWalletType(),
+
+ // Network type
+ "Network Type": Consensus.getNetworkType(),
+
+ // Address type
+ "Parameter One": HardwareWallet.SLATEPACK_ADDRESS_TYPE,
+
+ // Account
+ "Account": new BigNumber(HardwareWallet.ACCOUNT),
+
+ // Index
+ "Index": new BigNumber(index)
+
+ }, [HardwareWalletDefinitions.LEDGER_SUCCESS_MESSAGE_TYPE, HardwareWalletDefinitions.MIMBLEWIMBLE_COIN_ADDRESS_MESSAGE_TYPE], text, textArguments, allowUnlock, false, preventMessages, cancelOccurred).then(function(response) {
+
+ // Release exclusive lock
+ self.releaseExclusiveLock();
+
+ // Check if response is valid
+ if(response["length"] === ((self.transport.type === HardwareWalletDefinitions.TREZOR_TRANSPORT_TYPE) ? Uint8Array["BYTES_PER_ELEMENT"] : 0) + Slatepack.ADDRESS_LENGTH) {
+
+ // Try
+ try {
+
+ // Get Slatepack address from response
+ var slatepackAddress = (new TextDecoder("utf-8", {"fatal": true})).decode((self.transport.type === HardwareWalletDefinitions.TREZOR_TRANSPORT_TYPE) ? response.subarray(Uint8Array["BYTES_PER_ELEMENT"]) : response);
+
+ // Get public key from Slatepack address
+ Slatepack.slatepackAddressToPublicKey(slatepackAddress);
+ }
+
+ // Catch errors
+ catch(error) {
+
+ // Reject error
+ reject(error);
+
+ // Return
+ return;
+ }
+
+ // Resolve Slatepack address
+ resolve(slatepackAddress);
+ }
+
+ // Otherwise
+ else {
+
+ // Reject
+ reject();
+ }
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Release exclusive lock
+ self.releaseExclusiveLock();
+
+ // Check transport's type
+ switch(self.transport.type) {
+
+ // Trezor type
+ case HardwareWalletDefinitions.TREZOR_TRANSPORT_TYPE:
+
+ // Check if failure occurred
+ if(Object.isObject(error) === true && "Message Type" in error === true && error["Message Type"] === HardwareWalletDefinitions.TREZOR_FAILURE_MESSAGE_TYPE) {
+
+ // Initialize error occurred
+ var errorOccurred = false;
+
+ // Try
+ try {
+
+ // Decode error
+ var decodedError = self.decode(error["Message Type"], error["Data"]);
+ }
+
+ // Catch errors
+ catch(decodeError) {
+
+ // Set error occurred
+ errorOccurred = true;
+ }
+
+ // Check if an error didn't occur
+ if(errorOccurred === false) {
+
+ // Check if user rejected the request
+ if(decodedError["length"] >= Uint8Array["BYTES_PER_ELEMENT"] && (decodedError[0] === HardwareWalletDefinitions.TREZOR_ACTION_CANCELED_FAILURE_TYPE || decodedError[0] === HardwareWalletDefinitions.TREZOR_PIN_CANCELED_FAILURE_TYPE)) {
+
+ // Reject canceled error
+ reject(Common.CANCELED_ERROR);
+
+ // Return
+ return;
+ }
+ }
+ }
+
+ // Break
+ break;
+ }
+
+ // Reject error
+ reject(error);
+ });
+ }
+
+ // Catch errors
+ catch(error) {
+
+ // Release exclusive lock
+ self.releaseExclusiveLock();
+
+ // Reject error
+ reject(error);
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Release exclusive lock
+ self.releaseExclusiveLock();
+
+ // Reject disconnected error
+ reject(HardwareWallet.DISCONNECTED_ERROR);
+ }
+ });
+ });
+ }
+
+ // Verify Slatepack address
+ verifySlatepackAddress(index, text = HardwareWallet.NO_TEXT, textArguments = [], allowUnlock = false, preventMessages = false, cancelOccurred = Common.NO_CANCEL_OCCURRED) {
+
+ // Set self
+ var self = this;
+
+ // Return promise
+ return new Promise(function(resolve, reject) {
+
+ // Return obtainined exclusive lock
+ return self.obtainExclusiveLock().then(function() {
+
+ // Check if connected
+ if(self.isConnected() === true) {
+
+ // Try
+ try {
+
+ // Return requesting verifying the Slatepack address on the hardware wallet
+ return self.send(HardwareWalletDefinitions.MIMBLEWIMBLE_COIN_VERIFY_ADDRESS_MESSAGE_TYPE, HardwareWallet.SLATEPACK_ADDRESS_TYPE, HardwareWallet.NO_PARAMETER, {
+
+ // Coin type
+ "Coin Type": Consensus.getWalletType(),
+
+ // Network type
+ "Network Type": Consensus.getNetworkType(),
+
+ // Address type
+ "Parameter One": HardwareWallet.SLATEPACK_ADDRESS_TYPE,
+
+ // Account
+ "Account": new BigNumber(HardwareWallet.ACCOUNT),
+
+ // Index
+ "Index": new BigNumber(index)
+
+ }, [HardwareWalletDefinitions.LEDGER_SUCCESS_MESSAGE_TYPE, HardwareWalletDefinitions.TREZOR_SUCCESS_MESSAGE_TYPE], text, textArguments, allowUnlock, false, preventMessages, cancelOccurred).then(function(response) {
+
+ // Release exclusive lock
+ self.releaseExclusiveLock();
+
+ // Check if response is valid
+ if(response["length"] === 0) {
+
+ // Resolve
+ resolve();
+ }
+
+ // Otherwise
+ else {
+
+ // Reject
+ reject();
+ }
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Release exclusive lock
+ self.releaseExclusiveLock();
+
+ // Check transport's type
+ switch(self.transport.type) {
+
+ // Ledger type
+ case HardwareWalletDefinitions.LEDGER_TRANSPORT_TYPE:
+
+ // Check if user rejected the request
+ if(Object.isObject(error) === true && "Message Type" in error === true && error["Message Type"] === HardwareWalletDefinitions.LEDGER_USER_REJECTED_MESSAGE_TYPE) {
+
+ // Reject user rejected error
+ reject(HardwareWallet.USER_REJECTED_ERROR);
+ }
+
+ // Otherwise
+ else {
+
+ // Reject error
+ reject(error);
+ }
+
+ // Break
+ break;
+
+ // Trezor type
+ case HardwareWalletDefinitions.TREZOR_TRANSPORT_TYPE:
+
+ // Check if failure occurred
+ if(Object.isObject(error) === true && "Message Type" in error === true && error["Message Type"] === HardwareWalletDefinitions.TREZOR_FAILURE_MESSAGE_TYPE) {
+
+ // Initialize error occurred
+ var errorOccurred = false;
+
+ // Try
+ try {
+
+ // Decode error
+ var decodedError = self.decode(error["Message Type"], error["Data"]);
+ }
+
+ // Catch errors
+ catch(decodeError) {
+
+ // Set error occurred
+ errorOccurred = true;
+ }
+
+ // Check if an error didn't occur
+ if(errorOccurred === false) {
+
+ // Check if user rejected the request
+ if(decodedError["length"] >= Uint8Array["BYTES_PER_ELEMENT"] && (decodedError[0] === HardwareWalletDefinitions.TREZOR_ACTION_CANCELED_FAILURE_TYPE || decodedError[0] === HardwareWalletDefinitions.TREZOR_PIN_CANCELED_FAILURE_TYPE)) {
+
+ // Reject user rejected error
+ reject(HardwareWallet.USER_REJECTED_ERROR);
+
+ // Return
+ return;
+ }
+ }
+ }
+
+ // Reject error
+ reject(error);
+
+ // Break
+ break;
+ }
+ });
+ }
+
+ // Catch errors
+ catch(error) {
+
+ // Release exclusive lock
+ self.releaseExclusiveLock();
+
+ // Reject error
+ reject(error);
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Release exclusive lock
+ self.releaseExclusiveLock();
+
+ // Reject disconnected error
+ reject(HardwareWallet.DISCONNECTED_ERROR);
+ }
+ });
+ });
+ }
+
+ // Decrypt slate
+ decryptSlate(index, slate, address, nonce, salt = HardwareWallet.NO_SALT, text = HardwareWallet.NO_TEXT, textArguments = [], allowUnlock = false, preventMessages = false, cancelOccurred = Common.NO_CANCEL_OCCURRED) {
+
+ // Set self
+ var self = this;
+
+ // Return promise
+ return new Promise(function(resolve, reject) {
+
+ // Return obtainined exclusive lock
+ return self.obtainExclusiveLock().then(function() {
+
+ // Check if connected
+ if(self.isConnected() === true) {
+
+ // Check if slate is valid
+ if(slate["length"] > Slatepack.TAG_LENGTH) {
+
+ // Try
+ try {
+
+ // Return requesting start decrypting on the hardware wallet
+ return self.send(HardwareWalletDefinitions.MIMBLEWIMBLE_COIN_START_DECRYPTING_SLATE_MESSAGE_TYPE, HardwareWallet.NO_PARAMETER, HardwareWallet.NO_PARAMETER, {
+
+ // Coin type
+ "Coin Type": Consensus.getWalletType(),
+
+ // Network type
+ "Network Type": Consensus.getNetworkType(),
+
+ // Account
+ "Account": new BigNumber(HardwareWallet.ACCOUNT),
+
+ // Index
+ "Index": new BigNumber(index),
+
+ // Nonce
+ "Nonce": nonce,
+
+ // Sender address or ephemeral X25519 public key
+ "Sender Address Or Ephemeral X25519 Public Key": (new TextEncoder()).encode(address),
+
+ // Salt or encrypted file key
+ "Salt Or Encrypted File Key": (salt !== HardwareWallet.NO_SALT) ? salt : new Uint8Array([]),
+
+ // Payload nonce
+ "Payload Nonce": new Uint8Array([])
+
+ }, [HardwareWalletDefinitions.LEDGER_SUCCESS_MESSAGE_TYPE, HardwareWalletDefinitions.TREZOR_SUCCESS_MESSAGE_TYPE], text, textArguments, allowUnlock, false, preventMessages, cancelOccurred).then(function(response) {
+
+ // Check if response is valid
+ if(response["length"] === 0) {
+
+ // Get encrypted data from slate
+ var encryptedData = slate.subarray(0, slate["length"] - Slatepack.TAG_LENGTH);
+
+ // Set decrypt chunk
+ var decryptChunk = new Promise(function(resolve, reject) {
+
+ // Resolve
+ resolve();
+ });
+
+ // Initialize decrypting chunks
+ var decryptingChunks = [decryptChunk];
+
+ // Go through all of the encrypted data chunks
+ for(let i = 0; i < Math.ceil(encryptedData["length"] / HardwareWallet.ENCRYPTION_AND_DECRYPTION_MAXIMUM_CHUNK_SIZE); ++i) {
+
+ // Decrypt chunk after the previous chunk is decrypted
+ decryptChunk = decryptChunk.then(function() {
+
+ // Return promise
+ return new Promise(function(resolve, reject) {
+
+ // Return decrypting chunk on the hardware wallet
+ return self.send(HardwareWalletDefinitions.MIMBLEWIMBLE_COIN_CONTINUE_DECRYPTING_SLATE_MESSAGE_TYPE, HardwareWallet.NO_PARAMETER, HardwareWallet.NO_PARAMETER, {
+
+ // Data
+ "Encrypted Data": encryptedData.subarray(i * HardwareWallet.ENCRYPTION_AND_DECRYPTION_MAXIMUM_CHUNK_SIZE, Math.min(encryptedData["length"], i * HardwareWallet.ENCRYPTION_AND_DECRYPTION_MAXIMUM_CHUNK_SIZE + HardwareWallet.ENCRYPTION_AND_DECRYPTION_MAXIMUM_CHUNK_SIZE))
+
+ }, [HardwareWalletDefinitions.LEDGER_SUCCESS_MESSAGE_TYPE, HardwareWalletDefinitions.MIMBLEWIMBLE_COIN_DECRYPTED_SLATE_DATA_MESSAGE_TYPE], text, textArguments, allowUnlock, false, preventMessages, cancelOccurred).then(function(response) {
+
+ // Check if response is valid
+ if(response["length"] > 0) {
+
+ // Get decrypted data chunk from response
+ var decryptedDataChunk = response;
+
+ // Resolve decrypted data chunk
+ resolve(decryptedDataChunk);
+ }
+
+ // Otherwise
+ else {
+
+ // Reject
+ reject();
+ }
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Check transport's type
+ switch(self.transport.type) {
+
+ // Trezor type
+ case HardwareWalletDefinitions.TREZOR_TRANSPORT_TYPE:
+
+ // Check if failure occurred
+ if(Object.isObject(error) === true && "Message Type" in error === true && error["Message Type"] === HardwareWalletDefinitions.TREZOR_FAILURE_MESSAGE_TYPE) {
+
+ // Initialize error occurred
+ var errorOccurred = false;
+
+ // Try
+ try {
+
+ // Decode error
+ var decodedError = self.decode(error["Message Type"], error["Data"]);
+ }
+
+ // Catch errors
+ catch(decodeError) {
+
+ // Set error occurred
+ errorOccurred = true;
+ }
+
+ // Check if an error didn't occur
+ if(errorOccurred === false) {
+
+ // Check if user rejected the request
+ if(decodedError["length"] >= Uint8Array["BYTES_PER_ELEMENT"] && (decodedError[0] === HardwareWalletDefinitions.TREZOR_ACTION_CANCELED_FAILURE_TYPE || decodedError[0] === HardwareWalletDefinitions.TREZOR_PIN_CANCELED_FAILURE_TYPE)) {
+
+ // Reject canceled error
+ reject(Common.CANCELED_ERROR);
+
+ // Return
+ return;
+ }
+ }
+ }
+
+ // Break
+ break;
+ }
+
+ // Reject error
+ reject(error);
+ });
+ });
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Return Promise
+ return new Promise(function(resolve, reject) {
+
+ // Reject error
+ reject(error);
+ });
+ });
+
+ // Append decrypting chunk to list
+ decryptingChunks.push(decryptChunk);
+ }
+
+ // Return decrypting all chunks
+ return Promise.all(decryptingChunks).then(function(decryptedDataChunks) {
+
+ // Get tag from slate
+ var tag = slate.subarray(slate["length"] - Slatepack.TAG_LENGTH);
+
+ // Return requesting finish decrypting on the hardware wallet
+ return self.send(HardwareWalletDefinitions.MIMBLEWIMBLE_COIN_FINISH_DECRYPTING_SLATE_MESSAGE_TYPE, HardwareWallet.NO_PARAMETER, HardwareWallet.NO_PARAMETER, {
+
+ // Tag
+ "Tag": tag
+
+ }, [HardwareWalletDefinitions.LEDGER_SUCCESS_MESSAGE_TYPE, HardwareWalletDefinitions.MIMBLEWIMBLE_COIN_DECRYPTED_SLATE_AES_KEY_MESSAGE_TYPE], text, textArguments, allowUnlock, false, preventMessages, cancelOccurred).then(function(response) {
+
+ // Release exclusive lock
+ self.releaseExclusiveLock();
+
+ // Check if response is valid
+ if(response["length"] === Crypto.AES_KEY_LENGTH) {
+
+ // Get AES key from response
+ var aesKey = response;
+
+ // Initialize AES decrypt chunks
+ var aesDecryptChunks = [];
+
+ // Go through all decrypted chunks
+ for(var i = 1; i < decryptedDataChunks["length"]; ++i) {
+
+ // Get decrypted data chunk
+ let decryptedDataChunk = decryptedDataChunks[i];
+
+ // Append decrypting AES chunk to list
+ aesDecryptChunks.push(new Promise(function(resolve, reject) {
+
+ // Return performing AES decryption on decrypted data chunk using the AES key
+ return Crypto.aesDecrypt(decryptedDataChunk, aesKey).then(function(dataChunk) {
+
+ // Resolve data chunk
+ resolve(dataChunk);
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Reject error
+ reject(error);
+ });
+ }));
+ }
+
+ // Return waiting for all data chunks to be AES decrypted
+ return Promise.all(aesDecryptChunks).then(function(dataChunks) {
+
+ // Securely clear AES key
+ aesKey.fill(0);
+
+ // Resolve combined data chunks
+ resolve(Common.mergeArrays(dataChunks));
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Securely clear AES key
+ aesKey.fill(0);
+
+ // Reject error
+ reject(error);
+ });
+ }
+
+ // Otherwise
+ else {
+
+ // Reject
+ reject();
+ }
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Release exclusive lock
+ self.releaseExclusiveLock();
+
+ // Check transport's type
+ switch(self.transport.type) {
+
+ // Trezor type
+ case HardwareWalletDefinitions.TREZOR_TRANSPORT_TYPE:
+
+ // Check if failure occurred
+ if(Object.isObject(error) === true && "Message Type" in error === true && error["Message Type"] === HardwareWalletDefinitions.TREZOR_FAILURE_MESSAGE_TYPE) {
+
+ // Initialize error occurred
+ var errorOccurred = false;
+
+ // Try
+ try {
+
+ // Decode error
+ var decodedError = self.decode(error["Message Type"], error["Data"]);
+ }
+
+ // Catch errors
+ catch(decodeError) {
+
+ // Set error occurred
+ errorOccurred = true;
+ }
+
+ // Check if an error didn't occur
+ if(errorOccurred === false) {
+
+ // Check if user rejected the request
+ if(decodedError["length"] >= Uint8Array["BYTES_PER_ELEMENT"] && (decodedError[0] === HardwareWalletDefinitions.TREZOR_ACTION_CANCELED_FAILURE_TYPE || decodedError[0] === HardwareWalletDefinitions.TREZOR_PIN_CANCELED_FAILURE_TYPE)) {
+
+ // Reject canceled error
+ reject(Common.CANCELED_ERROR);
+
+ // Return
+ return;
+ }
+ }
+ }
+
+ // Break
+ break;
+ }
+
+ // Reject error
+ reject(error);
+ });
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Release exclusive lock
+ self.releaseExclusiveLock();
+
+ // Reject error
+ reject(error);
+ });
+ }
+
+ // Otherwise
+ else {
+
+ // Release exclusive lock
+ self.releaseExclusiveLock();
+
+ // Reject
+ reject();
+ }
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Release exclusive lock
+ self.releaseExclusiveLock();
+
+ // Check transport's type
+ switch(self.transport.type) {
+
+ // Trezor type
+ case HardwareWalletDefinitions.TREZOR_TRANSPORT_TYPE:
+
+ // Check if failure occurred
+ if(Object.isObject(error) === true && "Message Type" in error === true && error["Message Type"] === HardwareWalletDefinitions.TREZOR_FAILURE_MESSAGE_TYPE) {
+
+ // Initialize error occurred
+ var errorOccurred = false;
+
+ // Try
+ try {
+
+ // Decode error
+ var decodedError = self.decode(error["Message Type"], error["Data"]);
+ }
+
+ // Catch errors
+ catch(decodeError) {
+
+ // Set error occurred
+ errorOccurred = true;
+ }
+
+ // Check if an error didn't occur
+ if(errorOccurred === false) {
+
+ // Check if user rejected the request
+ if(decodedError["length"] >= Uint8Array["BYTES_PER_ELEMENT"] && (decodedError[0] === HardwareWalletDefinitions.TREZOR_ACTION_CANCELED_FAILURE_TYPE || decodedError[0] === HardwareWalletDefinitions.TREZOR_PIN_CANCELED_FAILURE_TYPE)) {
+
+ // Reject canceled error
+ reject(Common.CANCELED_ERROR);
+
+ // Return
+ return;
+ }
+ }
+ }
+
+ // Break
+ break;
+ }
+
+ // Reject error
+ reject(error);
+ });
+ }
+
+ // Catch errors
+ catch(error) {
+
+ // Release exclusive lock
+ self.releaseExclusiveLock();
+
+ // Reject error
+ reject(error);
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Release exclusive lock
+ self.releaseExclusiveLock();
+
+ // Reject
+ reject();
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Release exclusive lock
+ self.releaseExclusiveLock();
+
+ // Reject disconnected error
+ reject(HardwareWallet.DISCONNECTED_ERROR);
+ }
+ });
+ });
+ }
+
+ // Encrypt slate
+ encryptSlate(index, slate, address, text = HardwareWallet.NO_TEXT, textArguments = [], allowUnlock = false, preventMessages = false, cancelOccurred = Common.NO_CANCEL_OCCURRED) {
+
+ // Set self
+ var self = this;
+
+ // Return promise
+ return new Promise(function(resolve, reject) {
+
+ // Return obtainined exclusive lock
+ return self.obtainExclusiveLock().then(function() {
+
+ // Check if connected
+ if(self.isConnected() === true) {
+
+ // Check if slate is valid
+ if(slate["length"] !== 0) {
+
+ // Try
+ try {
+
+ // Return requesting start encrypting on the hardware wallet
+ return self.send(HardwareWalletDefinitions.MIMBLEWIMBLE_COIN_START_ENCRYPTING_SLATE_MESSAGE_TYPE, HardwareWallet.NO_PARAMETER, HardwareWallet.NO_PARAMETER, {
+
+ // Coin type
+ "Coin Type": Consensus.getWalletType(),
+
+ // Network type
+ "Network Type": Consensus.getNetworkType(),
+
+ // Account
+ "Account": new BigNumber(HardwareWallet.ACCOUNT),
+
+ // Index
+ "Index": new BigNumber(index),
+
+ // Recipient address
+ "Recipient Address": (new TextEncoder()).encode(address)
+
+ }, [HardwareWalletDefinitions.LEDGER_SUCCESS_MESSAGE_TYPE, HardwareWalletDefinitions.MIMBLEWIMBLE_COIN_ENCRYPTED_SLATE_NONCE_AND_SALT_MESSAGE_TYPE], text, textArguments, allowUnlock, false, preventMessages, cancelOccurred).then(function(response) {
+
+ // Check if response is valid
+ if(response["length"] >= Slatepack.NONCE_LENGTH) {
+
+ // Get nonce from response
+ var nonce = response.subarray(0, Slatepack.NONCE_LENGTH);
+
+ // Get decrypted data from slate
+ var decryptedData = slate;
+
+ // Set encrypt chunk
+ var encryptChunk = new Promise(function(resolve, reject) {
+
+ // Resolve
+ resolve(new Uint8Array([]));
+ });
+
+ // Initialize encrypting chunks
+ var encryptingChunks = [encryptChunk];
+
+ // Go through all of the decrypted data chunks
+ for(let i = 0; i < Math.ceil(decryptedData["length"] / HardwareWallet.ENCRYPTION_AND_DECRYPTION_MAXIMUM_CHUNK_SIZE); ++i) {
+
+ // Encrypt chunk after the previous chunk is encrypted
+ encryptChunk = encryptChunk.then(function() {
+
+ // Return promise
+ return new Promise(function(resolve, reject) {
+
+ // Return encrypting chunk on the hardware wallet
+ return self.send(HardwareWalletDefinitions.MIMBLEWIMBLE_COIN_CONTINUE_ENCRYPTING_SLATE_MESSAGE_TYPE, HardwareWallet.NO_PARAMETER, HardwareWallet.NO_PARAMETER, {
+
+ // Data
+ "Data": decryptedData.subarray(i * HardwareWallet.ENCRYPTION_AND_DECRYPTION_MAXIMUM_CHUNK_SIZE, Math.min(decryptedData["length"], i * HardwareWallet.ENCRYPTION_AND_DECRYPTION_MAXIMUM_CHUNK_SIZE + HardwareWallet.ENCRYPTION_AND_DECRYPTION_MAXIMUM_CHUNK_SIZE))
+
+ }, [HardwareWalletDefinitions.LEDGER_SUCCESS_MESSAGE_TYPE, HardwareWalletDefinitions.MIMBLEWIMBLE_COIN_ENCRYPTED_SLATE_DATA_MESSAGE_TYPE], text, textArguments, allowUnlock, false, preventMessages, cancelOccurred).then(function(response) {
+
+ // Check if response is valid
+ if(response["length"] > 0) {
+
+ // Get encrypted data chunk from response
+ var encryptedDataChunk = response;
+
+ // Resolve encrypted data chunk
+ resolve(encryptedDataChunk);
+ }
+
+ // Otherwise
+ else {
+
+ // Reject
+ reject();
+ }
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Check transport's type
+ switch(self.transport.type) {
+
+ // Trezor type
+ case HardwareWalletDefinitions.TREZOR_TRANSPORT_TYPE:
+
+ // Check if failure occurred
+ if(Object.isObject(error) === true && "Message Type" in error === true && error["Message Type"] === HardwareWalletDefinitions.TREZOR_FAILURE_MESSAGE_TYPE) {
+
+ // Initialize error occurred
+ var errorOccurred = false;
+
+ // Try
+ try {
+
+ // Decode error
+ var decodedError = self.decode(error["Message Type"], error["Data"]);
+ }
+
+ // Catch errors
+ catch(decodeError) {
+
+ // Set error occurred
+ errorOccurred = true;
+ }
+
+ // Check if an error didn't occur
+ if(errorOccurred === false) {
+
+ // Check if user rejected the request
+ if(decodedError["length"] >= Uint8Array["BYTES_PER_ELEMENT"] && (decodedError[0] === HardwareWalletDefinitions.TREZOR_ACTION_CANCELED_FAILURE_TYPE || decodedError[0] === HardwareWalletDefinitions.TREZOR_PIN_CANCELED_FAILURE_TYPE)) {
+
+ // Reject canceled error
+ reject(Common.CANCELED_ERROR);
+
+ // Return
+ return;
+ }
+ }
+ }
+
+ // Break
+ break;
+ }
+
+ // Reject error
+ reject(error);
+ });
+ });
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Return Promise
+ return new Promise(function(resolve, reject) {
+
+ // Reject error
+ reject(error);
+ });
+ });
+
+ // Append encrypting chunk to list
+ encryptingChunks.push(encryptChunk);
+ }
+
+ // Return encrypting all chunks
+ return Promise.all(encryptingChunks).then(function(encryptedDataChunks) {
+
+ // Return requesting finish encrypting on the hardware wallet
+ return self.send(HardwareWalletDefinitions.MIMBLEWIMBLE_COIN_FINISH_ENCRYPTING_SLATE_MESSAGE_TYPE, HardwareWallet.NO_PARAMETER, HardwareWallet.NO_PARAMETER, HardwareWallet.NO_DATA, [HardwareWalletDefinitions.LEDGER_SUCCESS_MESSAGE_TYPE, HardwareWalletDefinitions.MIMBLEWIMBLE_COIN_ENCRYPTED_SLATE_TAG_AND_SIGNATURE_MESSAGE_TYPE], text, textArguments, allowUnlock, false, preventMessages, cancelOccurred).then(function(response) {
+
+ // Release exclusive lock
+ self.releaseExclusiveLock();
+
+ // Check if response is valid
+ if(response["length"] >= Slatepack.TAG_LENGTH) {
+
+ // Get tag from response
+ var tag = response.subarray(0, Slatepack.TAG_LENGTH);
+
+ // Append the tag to the encrypted data chunks
+ var encryptedData = Common.mergeArrays([
+
+ // Encrypted data chunks
+ Common.mergeArrays(encryptedDataChunks),
+
+ // tag
+ tag
+ ]);
+
+ // Resolve nonce and encrypted data
+ resolve([
+
+ // Nonce
+ nonce,
+
+ // Encrypted data
+ encryptedData
+ ]);
+ }
+
+ // Otherwise
+ else {
+
+ // Reject
+ reject();
+ }
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Release exclusive lock
+ self.releaseExclusiveLock();
+
+ // Check transport's type
+ switch(self.transport.type) {
+
+ // Trezor type
+ case HardwareWalletDefinitions.TREZOR_TRANSPORT_TYPE:
+
+ // Check if failure occurred
+ if(Object.isObject(error) === true && "Message Type" in error === true && error["Message Type"] === HardwareWalletDefinitions.TREZOR_FAILURE_MESSAGE_TYPE) {
+
+ // Initialize error occurred
+ var errorOccurred = false;
+
+ // Try
+ try {
+
+ // Decode error
+ var decodedError = self.decode(error["Message Type"], error["Data"]);
+ }
+
+ // Catch errors
+ catch(decodeError) {
+
+ // Set error occurred
+ errorOccurred = true;
+ }
+
+ // Check if an error didn't occur
+ if(errorOccurred === false) {
+
+ // Check if user rejected the request
+ if(decodedError["length"] >= Uint8Array["BYTES_PER_ELEMENT"] && (decodedError[0] === HardwareWalletDefinitions.TREZOR_ACTION_CANCELED_FAILURE_TYPE || decodedError[0] === HardwareWalletDefinitions.TREZOR_PIN_CANCELED_FAILURE_TYPE)) {
+
+ // Reject canceled error
+ reject(Common.CANCELED_ERROR);
+
+ // Return
+ return;
+ }
+ }
+ }
+
+ // Break
+ break;
+ }
+
+ // Reject error
+ reject(error);
+ });
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Release exclusive lock
+ self.releaseExclusiveLock();
+
+ // Reject error
+ reject(error);
+ });
+ }
+
+ // Otherwise
+ else {
+
+ // Release exclusive lock
+ self.releaseExclusiveLock();
+
+ // Reject
+ reject();
+ }
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Release exclusive lock
+ self.releaseExclusiveLock();
+
+ // Check transport's type
+ switch(self.transport.type) {
+
+ // Trezor type
+ case HardwareWalletDefinitions.TREZOR_TRANSPORT_TYPE:
+
+ // Check if failure occurred
+ if(Object.isObject(error) === true && "Message Type" in error === true && error["Message Type"] === HardwareWalletDefinitions.TREZOR_FAILURE_MESSAGE_TYPE) {
+
+ // Initialize error occurred
+ var errorOccurred = false;
+
+ // Try
+ try {
+
+ // Decode error
+ var decodedError = self.decode(error["Message Type"], error["Data"]);
+ }
+
+ // Catch errors
+ catch(decodeError) {
+
+ // Set error occurred
+ errorOccurred = true;
+ }
+
+ // Check if an error didn't occur
+ if(errorOccurred === false) {
+
+ // Check if user rejected the request
+ if(decodedError["length"] >= Uint8Array["BYTES_PER_ELEMENT"] && (decodedError[0] === HardwareWalletDefinitions.TREZOR_ACTION_CANCELED_FAILURE_TYPE || decodedError[0] === HardwareWalletDefinitions.TREZOR_PIN_CANCELED_FAILURE_TYPE)) {
+
+ // Reject canceled error
+ reject(Common.CANCELED_ERROR);
+
+ // Return
+ return;
+ }
+ }
+ }
+
+ // Break
+ break;
+ }
+
+ // Reject error
+ reject(error);
+ });
+ }
+
+ // Catch errors
+ catch(error) {
+
+ // Release exclusive lock
+ self.releaseExclusiveLock();
+
+ // Reject error
+ reject(error);
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Release exclusive lock
+ self.releaseExclusiveLock();
+
+ // Reject
+ reject();
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Release exclusive lock
+ self.releaseExclusiveLock();
+
+ // Reject disconnected error
+ reject(HardwareWallet.DISCONNECTED_ERROR);
+ }
+ });
+ });
+ }
+
+ // Start transaction
+ startTransaction(index, output, input, fee, secretNonceIndex = HardwareWallet.NO_SECRET_NONCE_INDEX, address = HardwareWallet.NO_ADDRESS, text = HardwareWallet.NO_TEXT, textArguments = [], allowUnlock = false, preventMessages = false, cancelOccurred = Common.NO_CANCEL_OCCURRED) {
+
+ // Set self
+ var self = this;
+
+ // Return promise
+ return new Promise(function(resolve, reject) {
+
+ // Return obtainined exclusive lock
+ return self.obtainExclusiveLock().then(function() {
+
+ // Check if connected
+ if(self.isConnected() === true) {
+
+ // Try
+ try {
+
+ // Return requesting starting transaction on the hardware wallet
+ return self.send(HardwareWalletDefinitions.MIMBLEWIMBLE_COIN_START_TRANSACTION_MESSAGE_TYPE, HardwareWallet.NO_PARAMETER, HardwareWallet.NO_PARAMETER, {
+
+ // Coin type
+ "Coin Type": Consensus.getWalletType(),
+
+ // Network type
+ "Network Type": Consensus.getNetworkType(),
+
+ // Account
+ "Account": new BigNumber(HardwareWallet.ACCOUNT),
+
+ // Index
+ "Index": new BigNumber(index),
+
+ // Output
+ "Output": output,
+
+ // Input
+ "Input": input,
+
+ // Fee
+ "Fee": fee,
+
+ // Secret nonce index
+ "Secret Nonce Index": secretNonceIndex,
+
+ // Address
+ "Address": (address !== HardwareWallet.NO_ADDRESS) ? (new TextEncoder()).encode(address) : new Uint8Array([])
+
+ }, [HardwareWalletDefinitions.LEDGER_SUCCESS_MESSAGE_TYPE, HardwareWalletDefinitions.TREZOR_SUCCESS_MESSAGE_TYPE], text, textArguments, allowUnlock, false, preventMessages, cancelOccurred).then(function(response) {
+
+ // Check if response is valid
+ if(response["length"] === 0) {
+
+ // Resolve
+ resolve();
+ }
+
+ // Otherwise
+ else {
+
+ // Release exclusive lock
+ self.releaseExclusiveLock();
+
+ // Reject
+ reject();
+ }
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Release exclusive lock
+ self.releaseExclusiveLock();
+
+ // Check transport's type
+ switch(self.transport.type) {
+
+ // Trezor type
+ case HardwareWalletDefinitions.TREZOR_TRANSPORT_TYPE:
+
+ // Check if failure occurred
+ if(Object.isObject(error) === true && "Message Type" in error === true && error["Message Type"] === HardwareWalletDefinitions.TREZOR_FAILURE_MESSAGE_TYPE) {
+
+ // Initialize error occurred
+ var errorOccurred = false;
+
+ // Try
+ try {
+
+ // Decode error
+ var decodedError = self.decode(error["Message Type"], error["Data"]);
+ }
+
+ // Catch errors
+ catch(decodeError) {
+
+ // Set error occurred
+ errorOccurred = true;
+ }
+
+ // Check if an error didn't occur
+ if(errorOccurred === false) {
+
+ // Check if user rejected the request
+ if(decodedError["length"] >= Uint8Array["BYTES_PER_ELEMENT"] && (decodedError[0] === HardwareWalletDefinitions.TREZOR_ACTION_CANCELED_FAILURE_TYPE || decodedError[0] === HardwareWalletDefinitions.TREZOR_PIN_CANCELED_FAILURE_TYPE)) {
+
+ // Reject canceled error
+ reject(Common.CANCELED_ERROR);
+
+ // Return
+ return;
+ }
+ }
+ }
+
+ // Break
+ break;
+ }
+
+ // Reject error
+ reject(error);
+ });
+ }
+
+ // Catch error
+ catch(error) {
+
+ // Release exclusive lock
+ self.releaseExclusiveLock();
+
+ // Reject error
+ reject(error);
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Release exclusive lock
+ self.releaseExclusiveLock();
+
+ // Reject disconnected error
+ reject(HardwareWallet.DISCONNECTED_ERROR);
+ }
+ });
+ });
+ }
+
+ // Include output in transaction
+ includeOutputInTransaction(value, identifier, switchType, text = HardwareWallet.NO_TEXT, textArguments = [], allowUnlock = false, preventMessages = false, cancelOccurred = Common.NO_CANCEL_OCCURRED) {
+
+ // Set self
+ var self = this;
+
+ // Return promise
+ return new Promise(function(resolve, reject) {
+
+ // Check if exclusive lock is locked
+ if(self.exclusiveLockObtained === true) {
+
+ // Check if connected
+ if(self.isConnected() === true) {
+
+ // Try
+ try {
+
+ // Return requesting including output in transaction on the hardware wallet
+ return self.send(HardwareWalletDefinitions.MIMBLEWIMBLE_COIN_CONTINUE_TRANSACTION_INCLUDE_OUTPUT_MESSAGE_TYPE, HardwareWallet.NO_PARAMETER, HardwareWallet.NO_PARAMETER, {
+
+ // Identifier
+ "Identifier": identifier.getValue(),
+
+ // Value
+ "Value": value,
+
+ // Switch type
+ "Switch Type": switchType
+
+ }, [HardwareWalletDefinitions.LEDGER_SUCCESS_MESSAGE_TYPE, HardwareWalletDefinitions.TREZOR_SUCCESS_MESSAGE_TYPE], text, textArguments, allowUnlock, false, preventMessages, cancelOccurred).then(function(response) {
+
+ // Check if response is valid
+ if(response["length"] === 0) {
+
+ // Resolve
+ resolve();
+ }
+
+ // Otherwise
+ else {
+
+ // Reject
+ reject();
+ }
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Check transport's type
+ switch(self.transport.type) {
+
+ // Trezor type
+ case HardwareWalletDefinitions.TREZOR_TRANSPORT_TYPE:
+
+ // Check if failure occurred
+ if(Object.isObject(error) === true && "Message Type" in error === true && error["Message Type"] === HardwareWalletDefinitions.TREZOR_FAILURE_MESSAGE_TYPE) {
+
+ // Initialize error occurred
+ var errorOccurred = false;
+
+ // Try
+ try {
+
+ // Decode error
+ var decodedError = self.decode(error["Message Type"], error["Data"]);
+ }
+
+ // Catch errors
+ catch(decodeError) {
+
+ // Set error occurred
+ errorOccurred = true;
+ }
+
+ // Check if an error didn't occur
+ if(errorOccurred === false) {
+
+ // Check if user rejected the request
+ if(decodedError["length"] >= Uint8Array["BYTES_PER_ELEMENT"] && (decodedError[0] === HardwareWalletDefinitions.TREZOR_ACTION_CANCELED_FAILURE_TYPE || decodedError[0] === HardwareWalletDefinitions.TREZOR_PIN_CANCELED_FAILURE_TYPE)) {
+
+ // Reject canceled error
+ reject(Common.CANCELED_ERROR);
+
+ // Return
+ return;
+ }
+ }
+ }
+
+ // Break
+ break;
+ }
+
+ // Reject error
+ reject(error);
+ });
+ }
+
+ // Catch errors
+ catch(error) {
+
+ // Reject error
+ reject(error);
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Reject disconnected error
+ reject(HardwareWallet.DISCONNECTED_ERROR);
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Reject
+ reject();
+ }
+ });
+ }
+
+ // Include input in transaction
+ includeInputInTransaction(value, identifier, switchType, text = HardwareWallet.NO_TEXT, textArguments = [], allowUnlock = false, preventMessages = false, cancelOccurred = Common.NO_CANCEL_OCCURRED) {
+
+ // Set self
+ var self = this;
+
+ // Return promise
+ return new Promise(function(resolve, reject) {
+
+ // Check if exclusive lock is locked
+ if(self.exclusiveLockObtained === true) {
+
+ // Check if connected
+ if(self.isConnected() === true) {
+
+ // Try
+ try {
+
+ // Return requesting including input in transaction on the hardware wallet
+ return self.send(HardwareWalletDefinitions.MIMBLEWIMBLE_COIN_CONTINUE_TRANSACTION_INCLUDE_INPUT_MESSAGE_TYPE, HardwareWallet.NO_PARAMETER, HardwareWallet.NO_PARAMETER, {
+
+ // Identifier
+ "Identifier": identifier.getValue(),
+
+ // Value
+ "Value": value,
+
+ // Switch type
+ "Switch Type": switchType
+
+ }, [HardwareWalletDefinitions.LEDGER_SUCCESS_MESSAGE_TYPE, HardwareWalletDefinitions.TREZOR_SUCCESS_MESSAGE_TYPE], text, textArguments, allowUnlock, false, preventMessages, cancelOccurred).then(function(response) {
+
+ // Check if response is valid
+ if(response["length"] === 0) {
+
+ // Resolve
+ resolve();
+ }
+
+ // Otherwise
+ else {
+
+ // Reject
+ reject();
+ }
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Check transport's type
+ switch(self.transport.type) {
+
+ // Trezor type
+ case HardwareWalletDefinitions.TREZOR_TRANSPORT_TYPE:
+
+ // Check if failure occurred
+ if(Object.isObject(error) === true && "Message Type" in error === true && error["Message Type"] === HardwareWalletDefinitions.TREZOR_FAILURE_MESSAGE_TYPE) {
+
+ // Initialize error occurred
+ var errorOccurred = false;
+
+ // Try
+ try {
+
+ // Decode error
+ var decodedError = self.decode(error["Message Type"], error["Data"]);
+ }
+
+ // Catch errors
+ catch(decodeError) {
+
+ // Set error occurred
+ errorOccurred = true;
+ }
+
+ // Check if an error didn't occur
+ if(errorOccurred === false) {
+
+ // Check if user rejected the request
+ if(decodedError["length"] >= Uint8Array["BYTES_PER_ELEMENT"] && (decodedError[0] === HardwareWalletDefinitions.TREZOR_ACTION_CANCELED_FAILURE_TYPE || decodedError[0] === HardwareWalletDefinitions.TREZOR_PIN_CANCELED_FAILURE_TYPE)) {
+
+ // Reject canceled error
+ reject(Common.CANCELED_ERROR);
+
+ // Return
+ return;
+ }
+ }
+ }
+
+ // Break
+ break;
+ }
+
+ // Reject error
+ reject(error);
+ });
+ }
+
+ // Catch errors
+ catch(error) {
+
+ // Reject error
+ reject(error);
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Reject disconnected error
+ reject(HardwareWallet.DISCONNECTED_ERROR);
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Reject
+ reject();
+ }
+ });
+ }
+
+ // Apply offset to transaction
+ applyOffsetToTransaction(offset, text = HardwareWallet.NO_TEXT, textArguments = [], allowUnlock = false, preventMessages = false, cancelOccurred = Common.NO_CANCEL_OCCURRED) {
+
+ // Set self
+ var self = this;
+
+ // Return promise
+ return new Promise(function(resolve, reject) {
+
+ // Check if exclusive lock is locked
+ if(self.exclusiveLockObtained === true) {
+
+ // Check if connected
+ if(self.isConnected() === true) {
+
+ // Return requesting applying an offset to the transaction on the hardware wallet
+ return self.send(HardwareWalletDefinitions.MIMBLEWIMBLE_COIN_CONTINUE_TRANSACTION_APPLY_OFFSET_MESSAGE_TYPE, HardwareWallet.NO_PARAMETER, HardwareWallet.NO_PARAMETER, {
+
+ // Offset
+ "Offset": offset
+
+ }, [HardwareWalletDefinitions.LEDGER_SUCCESS_MESSAGE_TYPE, HardwareWalletDefinitions.MIMBLEWIMBLE_COIN_TRANSACTION_SECRET_NONCE_INDEX_MESSAGE_TYPE], text, textArguments, allowUnlock, false, preventMessages, cancelOccurred).then(function(response) {
+
+ // Check if response is valid
+ if(response["length"] === 0 || response["length"] === 1) {
+
+ // Get secret nonce index from response
+ var secretNonceIndex = (response["length"] === 1) ? response[0] : HardwareWallet.NO_SECRET_NONCE_INDEX;
+
+ // Resolve secret nonce index
+ resolve(secretNonceIndex);
+ }
+
+ // Otherwise
+ else {
+
+ // Reject
+ reject();
+ }
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Check transport's type
+ switch(self.transport.type) {
+
+ // Trezor type
+ case HardwareWalletDefinitions.TREZOR_TRANSPORT_TYPE:
+
+ // Check if failure occurred
+ if(Object.isObject(error) === true && "Message Type" in error === true && error["Message Type"] === HardwareWalletDefinitions.TREZOR_FAILURE_MESSAGE_TYPE) {
+
+ // Initialize error occurred
+ var errorOccurred = false;
+
+ // Try
+ try {
+
+ // Decode error
+ var decodedError = self.decode(error["Message Type"], error["Data"]);
+ }
+
+ // Catch errors
+ catch(decodeError) {
+
+ // Set error occurred
+ errorOccurred = true;
+ }
+
+ // Check if an error didn't occur
+ if(errorOccurred === false) {
+
+ // Check if user rejected the request
+ if(decodedError["length"] >= Uint8Array["BYTES_PER_ELEMENT"] && (decodedError[0] === HardwareWalletDefinitions.TREZOR_ACTION_CANCELED_FAILURE_TYPE || decodedError[0] === HardwareWalletDefinitions.TREZOR_PIN_CANCELED_FAILURE_TYPE)) {
+
+ // Reject canceled error
+ reject(Common.CANCELED_ERROR);
+
+ // Return
+ return;
+ }
+ }
+ }
+
+ // Break
+ break;
+ }
+
+ // Reject error
+ reject(error);
+ });
+ }
+
+ // Otherwise
+ else {
+
+ // Reject disconnected error
+ reject(HardwareWallet.DISCONNECTED_ERROR);
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Reject
+ reject();
+ }
+ });
+ }
+
+ // Get transaction public key
+ getTransactionPublicKey(text = HardwareWallet.NO_TEXT, textArguments = [], allowUnlock = false, preventMessages = false, cancelOccurred = Common.NO_CANCEL_OCCURRED) {
+
+ // Set self
+ var self = this;
+
+ // Return promise
+ return new Promise(function(resolve, reject) {
+
+ // Check if exclusive lock is locked
+ if(self.exclusiveLockObtained === true) {
+
+ // Check if connected
+ if(self.isConnected() === true) {
+
+ // Return requesting getting the transaction public key from the hardware wallet
+ return self.send(HardwareWalletDefinitions.MIMBLEWIMBLE_COIN_CONTINUE_TRANSACTION_GET_PUBLIC_KEY_MESSAGE_TYPE, HardwareWallet.NO_PARAMETER, HardwareWallet.NO_PARAMETER, HardwareWallet.NO_DATA, [HardwareWalletDefinitions.LEDGER_SUCCESS_MESSAGE_TYPE, HardwareWalletDefinitions.MIMBLEWIMBLE_COIN_TRANSACTION_PUBLIC_KEY_MESSAGE_TYPE], text, textArguments, allowUnlock, false, preventMessages, cancelOccurred).then(function(response) {
+
+ // Check if response is valid
+ if(response["length"] === Crypto.SECP256K1_PUBLIC_KEY_LENGTH && Secp256k1Zkp.isValidPublicKey(response) === true) {
+
+ // Get public key from response
+ var publicKey = response;
+
+ // Resolve public key
+ resolve(publicKey);
+ }
+
+ // Otherwise
+ else {
+
+ // Reject
+ reject();
+ }
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Check transport's type
+ switch(self.transport.type) {
+
+ // Trezor type
+ case HardwareWalletDefinitions.TREZOR_TRANSPORT_TYPE:
+
+ // Check if failure occurred
+ if(Object.isObject(error) === true && "Message Type" in error === true && error["Message Type"] === HardwareWalletDefinitions.TREZOR_FAILURE_MESSAGE_TYPE) {
+
+ // Initialize error occurred
+ var errorOccurred = false;
+
+ // Try
+ try {
+
+ // Decode error
+ var decodedError = self.decode(error["Message Type"], error["Data"]);
+ }
+
+ // Catch errors
+ catch(decodeError) {
+
+ // Set error occurred
+ errorOccurred = true;
+ }
+
+ // Check if an error didn't occur
+ if(errorOccurred === false) {
+
+ // Check if user rejected the request
+ if(decodedError["length"] >= Uint8Array["BYTES_PER_ELEMENT"] && (decodedError[0] === HardwareWalletDefinitions.TREZOR_ACTION_CANCELED_FAILURE_TYPE || decodedError[0] === HardwareWalletDefinitions.TREZOR_PIN_CANCELED_FAILURE_TYPE)) {
+
+ // Reject canceled error
+ reject(Common.CANCELED_ERROR);
+
+ // Return
+ return;
+ }
+ }
+ }
+
+ // Break
+ break;
+ }
+
+ // Reject error
+ reject(error);
+ });
+ }
+
+ // Otherwise
+ else {
+
+ // Reject disconnected error
+ reject(HardwareWallet.DISCONNECTED_ERROR);
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Reject
+ reject();
+ }
+ });
+ }
+
+ // Get transaction public nonce
+ getTransactionPublicNonce(text = HardwareWallet.NO_TEXT, textArguments = [], allowUnlock = false, preventMessages = false, cancelOccurred = Common.NO_CANCEL_OCCURRED) {
+
+ // Set self
+ var self = this;
+
+ // Return promise
+ return new Promise(function(resolve, reject) {
+
+ // Check if exclusive lock is locked
+ if(self.exclusiveLockObtained === true) {
+
+ // Check if connected
+ if(self.isConnected() === true) {
+
+ // Return requesting getting the transaction public nonce from the hardware wallet
+ return self.send(HardwareWalletDefinitions.MIMBLEWIMBLE_COIN_CONTINUE_TRANSACTION_GET_PUBLIC_NONCE_MESSAGE_TYPE, HardwareWallet.NO_PARAMETER, HardwareWallet.NO_PARAMETER, HardwareWallet.NO_DATA, [HardwareWalletDefinitions.LEDGER_SUCCESS_MESSAGE_TYPE, HardwareWalletDefinitions.MIMBLEWIMBLE_COIN_TRANSACTION_PUBLIC_NONCE_MESSAGE_TYPE], text, textArguments, allowUnlock, false, preventMessages, cancelOccurred).then(function(response) {
+
+ // Check if response is valid
+ if(response["length"] === Crypto.SECP256K1_PUBLIC_KEY_LENGTH && Secp256k1Zkp.isValidPublicKey(response) === true) {
+
+ // Get public nonce from response
+ var publicNonce = response;
+
+ // Resolve public nonce
+ resolve(publicNonce);
+ }
+
+ // Otherwise
+ else {
+
+ // Reject
+ reject();
+ }
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Check transport's type
+ switch(self.transport.type) {
+
+ // Trezor type
+ case HardwareWalletDefinitions.TREZOR_TRANSPORT_TYPE:
+
+ // Check if failure occurred
+ if(Object.isObject(error) === true && "Message Type" in error === true && error["Message Type"] === HardwareWalletDefinitions.TREZOR_FAILURE_MESSAGE_TYPE) {
+
+ // Initialize error occurred
+ var errorOccurred = false;
+
+ // Try
+ try {
+
+ // Decode error
+ var decodedError = self.decode(error["Message Type"], error["Data"]);
+ }
+
+ // Catch errors
+ catch(decodeError) {
+
+ // Set error occurred
+ errorOccurred = true;
+ }
+
+ // Check if an error didn't occur
+ if(errorOccurred === false) {
+
+ // Check if user rejected the request
+ if(decodedError["length"] >= Uint8Array["BYTES_PER_ELEMENT"] && (decodedError[0] === HardwareWalletDefinitions.TREZOR_ACTION_CANCELED_FAILURE_TYPE || decodedError[0] === HardwareWalletDefinitions.TREZOR_PIN_CANCELED_FAILURE_TYPE)) {
+
+ // Reject canceled error
+ reject(Common.CANCELED_ERROR);
+
+ // Return
+ return;
+ }
+ }
+ }
+
+ // Break
+ break;
+ }
+
+ // Reject error
+ reject(error);
+ });
+ }
+
+ // Otherwise
+ else {
+
+ // Reject disconnected error
+ reject(HardwareWallet.DISCONNECTED_ERROR);
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Reject
+ reject();
+ }
+ });
+ }
+
+ // Get transaction message signature
+ getTransactionMessageSignature(message, text = HardwareWallet.NO_TEXT, textArguments = [], allowUnlock = false, preventMessages = false, cancelOccurred = Common.NO_CANCEL_OCCURRED) {
+
+ // Set self
+ var self = this;
+
+ // Return promise
+ return new Promise(function(resolve, reject) {
+
+ // Check if exclusive lock is locked
+ if(self.exclusiveLockObtained === true) {
+
+ // Check if connected
+ if(self.isConnected() === true) {
+
+ // Return requesting getting the transaction message signature from the hardware wallet
+ return self.send(HardwareWalletDefinitions.MIMBLEWIMBLE_COIN_CONTINUE_TRANSACTION_GET_MESSAGE_SIGNATURE_MESSAGE_TYPE, HardwareWallet.NO_PARAMETER, HardwareWallet.NO_PARAMETER, {
+
+ // Message
+ "Message": (new TextEncoder()).encode(message)
+
+ }, [HardwareWalletDefinitions.LEDGER_SUCCESS_MESSAGE_TYPE, HardwareWalletDefinitions.MIMBLEWIMBLE_COIN_TRANSACTION_MESSAGE_SIGNATURE_MESSAGE_TYPE], text, textArguments, allowUnlock, false, preventMessages, cancelOccurred).then(function(response) {
+
+ // Check if response is valid
+ if(response["length"] === Crypto.SINGLE_SIGNER_SIGNATURE_LENGTH && Secp256k1Zkp.isValidSingleSignerSignature(response) === true) {
+
+ // Get message signature from response
+ var messageSignature = response;
+
+ // Resolve signature
+ resolve(messageSignature);
+ }
+
+ // Otherwise
+ else {
+
+ // Reject
+ reject();
+ }
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Check transport's type
+ switch(self.transport.type) {
+
+ // Trezor type
+ case HardwareWalletDefinitions.TREZOR_TRANSPORT_TYPE:
+
+ // Check if failure occurred
+ if(Object.isObject(error) === true && "Message Type" in error === true && error["Message Type"] === HardwareWalletDefinitions.TREZOR_FAILURE_MESSAGE_TYPE) {
+
+ // Initialize error occurred
+ var errorOccurred = false;
+
+ // Try
+ try {
+
+ // Decode error
+ var decodedError = self.decode(error["Message Type"], error["Data"]);
+ }
+
+ // Catch errors
+ catch(decodeError) {
+
+ // Set error occurred
+ errorOccurred = true;
+ }
+
+ // Check if an error didn't occur
+ if(errorOccurred === false) {
+
+ // Check if user rejected the request
+ if(decodedError["length"] >= Uint8Array["BYTES_PER_ELEMENT"] && (decodedError[0] === HardwareWalletDefinitions.TREZOR_ACTION_CANCELED_FAILURE_TYPE || decodedError[0] === HardwareWalletDefinitions.TREZOR_PIN_CANCELED_FAILURE_TYPE)) {
+
+ // Reject canceled error
+ reject(Common.CANCELED_ERROR);
+
+ // Return
+ return;
+ }
+ }
+ }
+
+ // Break
+ break;
+ }
+
+ // Reject error
+ reject(error);
+ });
+ }
+
+ // Otherwise
+ else {
+
+ // Reject disconnected error
+ reject(HardwareWallet.DISCONNECTED_ERROR);
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Reject
+ reject();
+ }
+ });
+ }
+
+ // Get transaction information
+ getTransactionInformation(publicNonce, publicKey, features, lockHeight = Slate.NO_LOCK_HEIGHT, relativeHeight = Slate.NO_RELATIVE_HEIGHT, kernelCommit = HardwareWallet.NO_KERNEL_COMMIT, address = HardwareWallet.NO_ADDRESS, receiverSignature = Slate.NO_RECEIVER_SIGNATURE, text = HardwareWallet.NO_TEXT, textArguments = [], allowUnlock = false, preventMessages = false, cancelOccurred = Common.NO_CANCEL_OCCURRED) {
+
+ // Set self
+ var self = this;
+
+ // Return promise
+ return new Promise(function(resolve, reject) {
+
+ // Check if exclusive lock is locked
+ if(self.exclusiveLockObtained === true) {
+
+ // Check if connected
+ if(self.isConnected() === true) {
+
+ // Try
+ try {
+
+ // Check features
+ switch(features) {
+
+ // Coinbase or plain features
+ case SlateKernel.COINBASE_FEATURES:
+ case SlateKernel.PLAIN_FEATURES:
+
+ // Set kernel information to features
+ var kernelInformation = new Uint8Array([features]);
+
+ // Break
+ break;
+
+ // Height locked features
+ case SlateKernel.HEIGHT_LOCKED_FEATURES:
+
+ // Set kernel information to features followed by the lock height
+ var kernelInformation = Common.mergeArrays([
+
+ // Features
+ new Uint8Array([features]),
+
+ // Lock height
+ lockHeight.toBytes(BigNumber.LITTLE_ENDIAN, Common.BYTES_IN_A_UINT64)
+ ]);
+
+ // Break
+ break;
+
+ // No recent duplicate features
+ case SlateKernel.NO_RECENT_DUPLICATE_FEATURES:
+
+ // Set kernel features to features followed by the relative height
+ var kernelInformation = Common.mergeArrays([
+
+ // Features
+ new Uint8Array([features]),
+
+ // Relative height
+ relativeHeight.toBytes(BigNumber.LITTLE_ENDIAN, Common.BYTES_IN_A_UINT16)
+ ]);
+
+ // Break
+ break;
+ }
+ }
+
+ // Catch errors
+ catch(error) {
+
+ // Reject error
+ reject(error);
+
+ // Return
+ return;
+ }
+
+ // Check wallet type
+ switch(Consensus.getWalletType()) {
+
+ // MWC wallet
+ case Consensus.MWC_WALLET_TYPE:
+
+ // Check if address exists
+ if(address !== HardwareWallet.NO_ADDRESS) {
+
+ // Check address length
+ switch(address["length"]) {
+
+ // Tor address length
+ case Tor.ADDRESS_LENGTH:
+
+ // Set address type
+ var addressType = HardwareWallet.TOR_ADDRESS_TYPE;
+
+ // Break
+ break;
+
+ // MQS address length
+ case Mqs.ADDRESS_LENGTH:
+
+ // Set address type
+ var addressType = HardwareWallet.MQS_ADDRESS_TYPE;
+
+ // Break
+ break;
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Set address type
+ var addressType = HardwareWallet.MQS_ADDRESS_TYPE;
+ }
+
+ // Break
+ break;
+
+ // GRIN wallet
+ case Consensus.GRIN_WALLET_TYPE:
+
+ // Set address type
+ var addressType = HardwareWallet.SLATEPACK_ADDRESS_TYPE;
+
+ // Break
+ break;
+
+ // EPIC wallet
+ case Consensus.EPIC_WALLET_TYPE:
+
+ // Set address type
+ var addressType = HardwareWallet.TOR_ADDRESS_TYPE;
+
+ // Break
+ break;
+ }
+
+ // Return requesting finishing the transaction on the hardware wallet
+ return self.send(HardwareWalletDefinitions.MIMBLEWIMBLE_COIN_FINISH_TRANSACTION_MESSAGE_TYPE, addressType, HardwareWallet.NO_PARAMETER, {
+
+ // Address type
+ "Parameter One": addressType,
+
+ // Public nonce
+ "Public Nonce": publicNonce,
+
+ // Public key
+ "Public Key": publicKey,
+
+ // Kernel information
+ "Kernel Information": kernelInformation,
+
+ // Kernel commitment
+ "Kernel Commitment": (kernelCommit !== HardwareWallet.NO_KERNEL_COMMIT) ? kernelCommit : new Uint8Array([]),
+
+ // Payment proof
+ "Payment Proof": (receiverSignature !== Slate.NO_RECEIVER_SIGNATURE) ? receiverSignature : new Uint8Array([])
+
+ }, [HardwareWalletDefinitions.LEDGER_SUCCESS_MESSAGE_TYPE, HardwareWalletDefinitions.MIMBLEWIMBLE_COIN_TRANSACTION_SIGNATURE_AND_PAYMENT_PROOF_MESSAGE_TYPE], text, textArguments, allowUnlock, false, preventMessages, cancelOccurred).then(function(response) {
+
+ // Check if response is valid
+ if(response["length"] >= Crypto.SINGLE_SIGNER_SIGNATURE_LENGTH && Secp256k1Zkp.isValidSingleSignerSignature(response.subarray(0, Crypto.SINGLE_SIGNER_SIGNATURE_LENGTH)) === true) {
+
+ // Get signature from response
+ var signature = response.subarray(0, Crypto.SINGLE_SIGNER_SIGNATURE_LENGTH);
+
+ // Get payment proof from response
+ var paymentProof = (response["length"] > Crypto.SINGLE_SIGNER_SIGNATURE_LENGTH) ? response.subarray(Crypto.SINGLE_SIGNER_SIGNATURE_LENGTH) : HardwareWallet.NO_PAYMENT_PROOF;
+
+ // Check if payment proof is valid
+ if(paymentProof === HardwareWallet.NO_PAYMENT_PROOF || paymentProof["length"] <= Crypto.MAXIMUM_MESSAGE_HASH_SIGNATURE_LENGTH || paymentProof["length"] === Crypto.ED25519_SIGNATURE_LENGTH) {
+
+ // Resolve transaction information
+ resolve([
+
+ // Signature
+ signature,
+
+ // Payment proof
+ paymentProof
+ ]);
+ }
+
+ // Otherwise
+ else {
+
+ // Reject
+ reject();
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Reject
+ reject();
+ }
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Check transport's type
+ switch(self.transport.type) {
+
+ // Ledger type
+ case HardwareWalletDefinitions.LEDGER_TRANSPORT_TYPE:
+
+ // Check if user rejected the request
+ if(Object.isObject(error) === true && "Message Type" in error === true && error["Message Type"] === HardwareWalletDefinitions.LEDGER_USER_REJECTED_MESSAGE_TYPE) {
+
+ // Reject user rejected error
+ reject(HardwareWallet.USER_REJECTED_ERROR);
+ }
+
+ // Otherwise
+ else {
+
+ // Reject error
+ reject(error);
+ }
+
+ // Break
+ break;
+
+ // Trezor type
+ case HardwareWalletDefinitions.TREZOR_TRANSPORT_TYPE:
+
+ // Check if failure occurred
+ if(Object.isObject(error) === true && "Message Type" in error === true && error["Message Type"] === HardwareWalletDefinitions.TREZOR_FAILURE_MESSAGE_TYPE) {
+
+ // Initialize error occurred
+ var errorOccurred = false;
+
+ // Try
+ try {
+
+ // Decode error
+ var decodedError = self.decode(error["Message Type"], error["Data"]);
+ }
+
+ // Catch errors
+ catch(decodeError) {
+
+ // Set error occurred
+ errorOccurred = true;
+ }
+
+ // Check if an error didn't occur
+ if(errorOccurred === false) {
+
+ // Check if user rejected the request
+ if(decodedError["length"] >= Uint8Array["BYTES_PER_ELEMENT"] && (decodedError[0] === HardwareWalletDefinitions.TREZOR_ACTION_CANCELED_FAILURE_TYPE || decodedError[0] === HardwareWalletDefinitions.TREZOR_PIN_CANCELED_FAILURE_TYPE)) {
+
+ // Reject user rejected error
+ reject(HardwareWallet.USER_REJECTED_ERROR);
+
+ // Return
+ return;
+ }
+ }
+ }
+
+ // Reject error
+ reject(error);
+
+ // Break
+ break;
+ }
+ });
+ }
+
+ // Otherwise
+ else {
+
+ // Reject disconnected error
+ reject(HardwareWallet.DISCONNECTED_ERROR);
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Reject
+ reject();
+ }
+ });
+ }
+
+ // Cancel transaction
+ cancelTransaction() {
+
+ // Set self
+ var self = this;
+
+ // Return promise
+ return new Promise(function(resolve, reject) {
+
+ // Check if exclusive lock is locked
+ if(self.exclusiveLockObtained === true) {
+
+ // Release exclusive lock
+ self.releaseExclusiveLock();
+
+ // Resolve
+ resolve();
+ }
+
+ // Otherwise
+ else {
+
+ // Reject
+ reject();
+ }
+ });
+ }
+
+ // Complete transaction
+ completeTransaction() {
+
+ // Set self
+ var self = this;
+
+ // Return promise
+ return new Promise(function(resolve, reject) {
+
+ // Check if exclusive lock is locked
+ if(self.exclusiveLockObtained === true) {
+
+ // Release exclusive lock
+ self.releaseExclusiveLock();
+
+ // Resolve
+ resolve();
+ }
+
+ // Otherwise
+ else {
+
+ // Reject
+ reject();
+ }
+ });
+ }
+
+ // Set wallet key path
+ setWalletKeyPath(walletKeyPath) {
+
+ // Set wallet key path
+ this.walletKeyPath = walletKeyPath;
+ }
+
+ // Get wallet key path
+ getWalletKeyPath() {
+
+ // Return wallet key path
+ return this.walletKeyPath;
+ }
+
+ // Get available hardware wallet descriptors
+ static getAvailableHardwareWalletDescriptors() {
+
+ // Return promise
+ return new Promise(function(resolve, reject) {
+
+ // Return getting available hardware wallet descriptors
+ return HardwareWalletUsbTransport.list().then(function(availableHardwareWalletDescriptors) {
+
+ // Resolve available hardware wallet descriptors
+ resolve(availableHardwareWalletDescriptors);
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Reject error
+ reject(error);
+ });
+ });
+ }
+
+ // Is supported
+ static isSupported() {
+
+ // Return if USB or Bluetooth are supported
+ return "usb" in navigator === true || "bluetooth" in navigator === true;
+ }
+
+ // Any hardware wallet descriptor
+ static get ANY_HARDWARE_WALLET_DESCRIPTOR() {
+
+ // Return any hardware wallet descriptor
+ return null;
+ }
+
+ // Ledger hardware type
+ static get LEDGER_HARDWARE_TYPE() {
+
+ // Return Ledger hardware type
+ return 0;
+ }
+
+ // Trezor hardware type
+ static get TREZOR_HARDWARE_TYPE() {
+
+ // Return Trezor hardware type
+ return HardwareWallet.LEDGER_HARDWARE_TYPE + 1;
+ }
+
+ // Disconnect event
+ static get DISCONNECT_EVENT() {
+
+ // Return disconnect event
+ return "HardwareWalletDisconnectEvent";
+ }
+
+ // Before disconnect event
+ static get BEFORE_DISCONNECT_EVENT() {
+
+ // Return before disconnect event
+ return "HardwareWalletBeforeDisconnectEvent";
+ }
+
+ // Unlock event
+ static get UNLOCK_EVENT() {
+
+ // Return unlock event
+ return "HardwareWalletUnlockEvent";
+ }
+
+ // Device cancel event
+ static get DEVICE_CANCEL_EVENT() {
+
+ // Return device cancel event
+ return "HardwareWalletDeviceCancelEvent";
+ }
+
+ // Transaction information signature index
+ static get TRANSACTION_INFORMATION_SIGNATURE_INDEX() {
+
+ // Return transaction information signature index
+ return 0;
+ }
+
+ // Transaction information payment proof index
+ static get TRANSACTION_INFORMATION_PAYMENT_PROOF_INDEX() {
+
+ // Return transaction information payment proof index
+ return HardwareWallet.TRANSACTION_INFORMATION_SIGNATURE_INDEX + 1;
+ }
+
+ // No text
+ static get NO_TEXT() {
+
+ // Return no text
+ return null;
+ }
+
+ // Disconnected error
+ static get DISCONNECTED_ERROR() {
+
+ // Return disconnected error
+ return "HardwareWalletDisconnectedError";
+ }
+
+ // User rejected error
+ static get USER_REJECTED_ERROR() {
+
+ // Return user rejected error
+ return "HardwareWalletUserRejectedError";
+ }
+
+ // Encrypted slate nonce index
+ static get ENCRYPTED_SLATE_NONCE_INDEX() {
+
+ // Return encrypted slate nonce index
+ return 0;
+ }
+
+ // Encrypted slate data index
+ static get ENCRYPTED_SLATE_DATA_INDEX() {
+
+ // Return encrypted slate data index
+ return HardwareWallet.ENCRYPTED_SLATE_NONCE_INDEX + 1;
+ }
+
+ // Sending transaction message
+ static get SENDING_TRANSACTION_MESSAGE() {
+
+ // Return sending transaction message
+ return 0;
+ }
+
+ // Receiving transaction message
+ static get RECEIVING_TRANSACTION_MESSAGE() {
+
+ // Return receiving transaction message
+ return HardwareWallet.SENDING_TRANSACTION_MESSAGE + 1;
+ }
+
+ // Creating coinbase message
+ static get CREATING_COINBASE_MESSAGE() {
+
+ // Return creating coinbase message
+ return HardwareWallet.RECEIVING_TRANSACTION_MESSAGE + 1;
+ }
+
+ // USB connection type
+ static get USB_CONNECTION_TYPE() {
+
+ // Return USB connection type
+ return 0;
+ }
+
+ // Bluetooth connection type
+ static get BLUETOOTH_CONNECTION_TYPE() {
+
+ // Return Bluetooth connection type
+ return HardwareWallet.USB_CONNECTION_TYPE + 1;
+ }
+
+ // No secret nonce index
+ static get NO_SECRET_NONCE_INDEX() {
+
+ // Return no secret nonce index
+ return 0;
+ }
+
+ // Private
+
+ // Obtain exclusive lock
+ obtainExclusiveLock() {
+
+ // Set self
+ var self = this;
+
+ // Return promise
+ return new Promise(function(resolve, reject) {
+
+ // Check if exclusive lock is locked
+ if(self.exclusiveLockObtained === true) {
+
+ // Get current exclusive lock release event index
+ var index = self.exclusiveLockReleaseEventIndex++;
+
+ // Check if current current exclusive lock release event index is at the max safe integer
+ if(index === Number.MAX_SAFE_INTEGER)
+
+ // Reset current exclusive lock release event index
+ self.exclusiveLockReleaseEventIndex = 0;
+
+ // Exclusive lock release index event
+ $(self).on(HardwareWallet.EXCLUSIVE_LOCK_RELEASE_EVENT + "." + index.toFixed(), function() {
+
+ // Check if exclusive lock isn't locked
+ if(self.exclusiveLockObtained === false) {
+
+ // Turn off exclusive lock release index event
+ $(self).off(HardwareWallet.EXCLUSIVE_LOCK_RELEASE_EVENT + "." + index.toFixed());
+
+ // Lock exclusive lock
+ self.exclusiveLockObtained = true;
+
+ // Resolve
+ resolve();
+ }
+ });
+ }
+
+ // Otherwise
+ else {
+
+ // Lock exclusive lock
+ self.exclusiveLockObtained = true;
+
+ // Resolve
+ resolve();
+ }
+ });
+ }
+
+ // Release exclusive lock
+ releaseExclusiveLock() {
+
+ // Check if exclusive lock is locked
+ if(this.exclusiveLockObtained === true) {
+
+ // Set self
+ var self = this;
+
+ // Set timeout
+ setTimeout(function() {
+
+ // Unlock exclusive lock
+ self.exclusiveLockObtained = false;
+
+ // Trigger exclusive lock release event
+ $(self).trigger(HardwareWallet.EXCLUSIVE_LOCK_RELEASE_EVENT);
+ }, 0);
+ }
+ }
+
+ // Send
+ send(messageType, parameterOne, parameterTwo, data = HardwareWallet.NO_DATA, allowedResponseTypes = [], text = HardwareWallet.NO_TEXT, textArguments = [], allowUnlock = false, failOnLock = false, preventMessages = false, cancelOccurred = Common.NO_CANCEL_OCCURRED, forceSend = false, preventUnlockMessageDone = false, unlockMessageShown = false) {
+
+ // Set self
+ var self = this;
+
+ // Return promise
+ return new Promise(function(resolve, reject) {
+
+ // Check if cancel didn't occur or force sending
+ if(cancelOccurred === Common.NO_CANCEL_OCCURRED || cancelOccurred() === false || forceSend === true) {
+
+ // Return performing the instruction on the hardware wallet
+ return self.transport.send(messageType, parameterOne, parameterTwo, self.encode(messageType, data)).then(function(response) {
+
+ // Check if cancel didn't occur or force sending and unlock message isn't shown
+ if(cancelOccurred === Common.NO_CANCEL_OCCURRED || cancelOccurred() === false || (forceSend === true && unlockMessageShown === false)) {
+
+ // Check if the hardware wallet is locked and not set to fail on lock
+ if((self.transport.type === HardwareWalletDefinitions.LEDGER_TRANSPORT_TYPE && (response["Message Type"] === HardwareWalletDefinitions.LEDGER_APP_LOCKED_MESSAGE_TYPE || response["Message Type"] === HardwareWalletDefinitions.LEDGER_DEVICE_LOCKED_MESSAGE_TYPE)) && failOnLock === false) {
+
+ // Set locked
+ self.locked = true;
+
+ // Initialize canceled
+ var canceled = false;
+
+ // Check if showing message
+ if(text !== HardwareWallet.NO_TEXT) {
+
+ // Show hardware wallet unlock message
+ var showMessage = self.application.showHardwareWalletUnlockMessage(self, text, textArguments, allowUnlock, preventMessages, cancelOccurred);
+
+ // Catch errors while showing the message
+ showMessage.catch(function(error) {
+
+ // Set canceled
+ canceled = true;
+ });
+ }
+
+ // Resend
+ var resend = function() {
+
+ // Return promise
+ return new Promise(function(resolve, reject) {
+
+ // Set timeout
+ setTimeout(function() {
+
+ // Check if canceled
+ if(canceled === true) {
+
+ // Clear locked
+ self.locked = false;
+
+ // Reject canceled error
+ reject(Common.CANCELED_ERROR);
+ }
+
+ // Otherwise
+ else {
+
+ // Check if cancel didn't occur
+ if(cancelOccurred === Common.NO_CANCEL_OCCURRED || cancelOccurred() === false) {
+
+ // Return performing the instruction on the hardware wallet
+ return self.transport.send(messageType, parameterOne, parameterTwo, self.encode(messageType, data)).then(function(response) {
+
+ // Check if cancel didn't occur
+ if(cancelOccurred === Common.NO_CANCEL_OCCURRED || cancelOccurred() === false) {
+
+ // Check if the hardware wallet is locked and not set to fail on lock
+ if((self.transport.type === HardwareWalletDefinitions.LEDGER_TRANSPORT_TYPE && (response["Message Type"] === HardwareWalletDefinitions.LEDGER_APP_LOCKED_MESSAGE_TYPE || response["Message Type"] === HardwareWalletDefinitions.LEDGER_DEVICE_LOCKED_MESSAGE_TYPE)) && failOnLock === false) {
+
+ // Return resend
+ return resend().then(function(response) {
+
+ // Check if cancel didn't occur
+ if(cancelOccurred === Common.NO_CANCEL_OCCURRED || cancelOccurred() === false) {
+
+ // Resolve response
+ resolve(response);
+ }
+
+ // Otherwise
+ else {
+
+ // Reject canceled error
+ reject(Common.CANCELED_ERROR);
+ }
+
+ // Catch error
+ }).catch(function(error) {
+
+ // Check if cancel didn't occur
+ if(cancelOccurred === Common.NO_CANCEL_OCCURRED || cancelOccurred() === false) {
+
+ // Reject error
+ reject(error);
+ }
+
+ // Otherwise
+ else {
+
+ // Reject canceled error
+ reject(Common.CANCELED_ERROR);
+ }
+ });
+ }
+
+ // Otherwise check if response type isn't allowed
+ else if(response["Message Type"] !== ((Array.isArray(allowedResponseTypes) === true) ? allowedResponseTypes[self.transport.type] : allowedResponseTypes)) {
+
+ // Clear locked
+ self.locked = false;
+
+ // Check if response data isn't used
+ if(response["Message Type"] !== HardwareWalletDefinitions.TREZOR_FAILURE_MESSAGE_TYPE) {
+
+ // Securely clear response data
+ response["Data"].fill(0);
+ }
+
+ // Reject response
+ reject(response);
+ }
+
+ // Otherwise
+ else {
+
+ // Clear locked
+ self.locked = false;
+
+ // Try
+ try {
+
+ // Decode response
+ var decodedResponse = self.decode(response["Message Type"], response["Data"]);
+ }
+
+ // Catch errors
+ catch(error) {
+
+ // Securely clear response data
+ response["Data"].fill(0);
+
+ // Reject error
+ reject(error);
+
+ // Return
+ return;
+ }
+
+ // Securely clear response data
+ response["Data"].fill(0);
+
+ // Trigger unlock event
+ $(self).trigger(HardwareWallet.UNLOCK_EVENT);
+
+ // Check if showing message and not canceled
+ if(text !== HardwareWallet.NO_TEXT && canceled === false) {
+
+ // Return waiting until showing message has finished
+ return showMessage.then(function() {
+
+ // Resolve decoded response
+ resolve(decodedResponse);
+
+ // Catch errors
+ }).catch(function() {
+
+ // Resolve decoded response
+ resolve(decodedResponse);
+ });
+ }
+
+ // Otherwise
+ else {
+
+ // Resolve decoded response
+ resolve(decodedResponse);
+ }
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Securely clear response data
+ response["Data"].fill(0);
+
+ // Clear locked
+ self.locked = false;
+
+ // Reject canceled error
+ reject(Common.CANCELED_ERROR);
+ }
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Check if cancel didn't occur
+ if(cancelOccurred === Common.NO_CANCEL_OCCURRED || cancelOccurred() === false) {
+
+ // Check if error is that the device was disconnected
+ if(typeof error === "object" && error !== null && (("code" in error === true && error["code"] === HardwareWallet.NETWORK_ERROR_CODE) || ("name" in error === true && error["name"] === "NetworkError"))) {
+
+ // Clear locked
+ self.locked = false;
+
+ // Trigger before disconnect event
+ $(self).trigger(HardwareWallet.BEFORE_DISCONNECT_EVENT);
+
+ // Check if showing message and not canceled
+ if(text !== HardwareWallet.NO_TEXT && canceled === false) {
+
+ // Return waiting until showing message has finished
+ return showMessage.then(function() {
+
+ // Reject disconnected error
+ reject(HardwareWallet.DISCONNECTED_ERROR);
+
+ // Catch errors
+ }).catch(function() {
+
+ // Reject disconnected error
+ reject(HardwareWallet.DISCONNECTED_ERROR);
+ });
+ }
+
+ // Otherwise
+ else {
+
+ // Reject disconnected error
+ reject(HardwareWallet.DISCONNECTED_ERROR);
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Clear locked
+ self.locked = false;
+
+ // Reject error
+ reject(error);
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Clear locked
+ self.locked = false;
+
+ // Reject canceled error
+ reject(Common.CANCELED_ERROR);
+ }
+ });
+ }
+
+ // Otherwise
+ else {
+
+ // Clear locked
+ self.locked = false;
+
+ // Reject canceled error
+ reject(Common.CANCELED_ERROR);
+ }
+ }
+ }, HardwareWallet.RESEND_REQUEST_DELAY_MILLISECONDS);
+ });
+ };
+
+ // Return resend
+ return resend().then(function(response) {
+
+ // Check if cancel didn't occur
+ if(cancelOccurred === Common.NO_CANCEL_OCCURRED || cancelOccurred() === false) {
+
+ // Resolve response
+ resolve(response);
+ }
+
+ // Otherwise
+ else {
+
+ // Reject canceled error
+ reject(Common.CANCELED_ERROR);
+ }
+
+ // Catch error
+ }).catch(function(error) {
+
+ // Check if cancel didn't occur
+ if(cancelOccurred === Common.NO_CANCEL_OCCURRED || cancelOccurred() === false) {
+
+ // Reject error
+ reject(error);
+ }
+
+ // Otherwise
+ else {
+
+ // Reject canceled error
+ reject(Common.CANCELED_ERROR);
+ }
+ });
+ }
+
+ // Otherwise check if hardware wallet requires button acknowledgment
+ else if(self.transport.type === HardwareWalletDefinitions.TREZOR_TRANSPORT_TYPE && response["Message Type"] === HardwareWalletDefinitions.TREZOR_BUTTON_REQUEST_MESSAGE_TYPE) {
+
+ // Initialize error occurred
+ var errorOccurred = false;
+
+ // Try
+ try {
+
+ // Decode response
+ var decodedResponse = self.decode(response["Message Type"], response["Data"]);
+ }
+
+ // Catch errors
+ catch(error) {
+
+ // Set error occurred
+ errorOccurred = true;
+ }
+
+ // Check if an error didn't occur
+ if(errorOccurred === false) {
+
+ // Check if device is locked or needs a passphrase and not set to fail on lock
+ if(decodedResponse["length"] >= Uint8Array["BYTES_PER_ELEMENT"] && (decodedResponse[0] === HardwareWalletDefinitions.TREZOR_PIN_ENTRY_BUTTON_REQUEST_TYPE || decodedResponse[0] === HardwareWalletDefinitions.TREZOR_PASSPHRASE_ENTRY_BUTTON_REQUEST_TYPE) && failOnLock === false) {
+
+ // Check if unlock message isn't shown
+ if(unlockMessageShown === false) {
+
+ // Set locked
+ self.locked = true;
+ }
+
+ // Initialize canceled
+ var canceled = false;
+
+ // Initialize prevent cancel
+ var preventCancel = false;
+
+ // Check if showing message and unlock message isn't shown
+ if(text !== HardwareWallet.NO_TEXT && unlockMessageShown === false) {
+
+ // Show hardware wallet unlock message
+ var showMessage = self.application.showHardwareWalletUnlockMessage(self, text, textArguments, allowUnlock, preventMessages, cancelOccurred);
+
+ // Catch errors while showing the message
+ showMessage.catch(function(error) {
+
+ // Check if not preventing cancel
+ if(preventCancel === false) {
+
+ // Set canceled
+ canceled = true;
+
+ // Clear locked
+ self.locked = false;
+
+ // Reset transport device and catch errors
+ self.transport.device.reset().catch(function(error) {
+
+ // Finally
+ }).finally(function() {
+
+ // Reject canceled error
+ reject(Common.CANCELED_ERROR);
+ });
+ }
+ });
+ }
+
+ // Return sending button acknowledge response
+ return self.send(HardwareWalletDefinitions.TREZOR_BUTTON_ACKNOWLEDGE_MESSAGE_TYPE, HardwareWallet.NO_PARAMETER, HardwareWallet.NO_PARAMETER, HardwareWallet.NO_DATA, allowedResponseTypes, text, textArguments, allowUnlock, failOnLock, preventMessages, function() {
+
+ // Return if cancel occurred or canceled
+ return (cancelOccurred !== Common.NO_CANCEL_OCCURRED && cancelOccurred() === true) || canceled === true;
+
+ }, true, preventUnlockMessageDone, true).then(function(response) {
+
+ // Check if not canceled
+ if(canceled === false) {
+
+ // Set prevent cancel
+ preventCancel = true;
+
+ // Check if unlock message isn't shown
+ if(unlockMessageShown === false) {
+
+ // Clear locked
+ self.locked = false;
+ }
+
+ // Check if cancel didn't occur
+ if(cancelOccurred === Common.NO_CANCEL_OCCURRED || cancelOccurred() === false) {
+
+ // Check if unlock message isn't shown
+ if(unlockMessageShown === false) {
+
+ // Trigger unlock event
+ $(self).trigger(HardwareWallet.UNLOCK_EVENT);
+ }
+
+ // Check if showing message and unlock message isn't shown
+ if(text !== HardwareWallet.NO_TEXT && unlockMessageShown === false) {
+
+ // Return waiting until showing message has finished
+ return showMessage.then(function() {
+
+ // Resolve response
+ resolve(response);
+
+ // Catch errors
+ }).catch(function() {
+
+ // Resolve response
+ resolve(response);
+ });
+ }
+
+ // Otherwise
+ else {
+
+ // Resolve response
+ resolve(response);
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Reject canceled error
+ reject(Common.CANCELED_ERROR);
+ }
+ }
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Check if not canceled
+ if(canceled === false) {
+
+ // Set prevent cancel
+ preventCancel = true;
+
+ // Check if unlock message isn't shown
+ if(unlockMessageShown === false) {
+
+ // Clear locked
+ self.locked = false;
+ }
+
+ // Check if cancel didn't occur
+ if(cancelOccurred === Common.NO_CANCEL_OCCURRED || cancelOccurred() === false) {
+
+ // Check if unlock message isn't shown
+ if(unlockMessageShown === false) {
+
+ // Check if failure occurred
+ if(Object.isObject(error) === true && "Message Type" in error === true && error["Message Type"] === HardwareWalletDefinitions.TREZOR_FAILURE_MESSAGE_TYPE) {
+
+ // Initialize error occurred
+ var errorOccurred = false;
+
+ // Try
+ try {
+
+ // Decode error
+ var decodedError = self.decode(error["Message Type"], error["Data"]);
+ }
+
+ // Catch errors
+ catch(decodeError) {
+
+ // Set error occurred
+ errorOccurred = true;
+ }
+
+ // Check if an error didn't occur
+ if(errorOccurred === false) {
+
+ // Check if user rejected the request
+ if(decodedError["length"] >= Uint8Array["BYTES_PER_ELEMENT"] && (decodedError[0] === HardwareWalletDefinitions.TREZOR_ACTION_CANCELED_FAILURE_TYPE || decodedError[0] === HardwareWalletDefinitions.TREZOR_PIN_CANCELED_FAILURE_TYPE)) {
+
+ // Trigger device cancel event
+ $(self).trigger(HardwareWallet.DEVICE_CANCEL_EVENT);
+ }
+ }
+ }
+ }
+
+ // Reject error
+ reject(error);
+ }
+
+ // Otherwise
+ else {
+
+ // Reject canceled error
+ reject(Common.CANCELED_ERROR);
+ }
+ }
+ });
+ }
+ }
+
+ // Return sending button acknowledge response
+ return self.send(HardwareWalletDefinitions.TREZOR_BUTTON_ACKNOWLEDGE_MESSAGE_TYPE, HardwareWallet.NO_PARAMETER, HardwareWallet.NO_PARAMETER, HardwareWallet.NO_DATA, allowedResponseTypes, text, textArguments, allowUnlock, failOnLock, preventMessages, cancelOccurred, true).then(function(response) {
+
+ // Check if cancel didn't occur
+ if(cancelOccurred === Common.NO_CANCEL_OCCURRED || cancelOccurred() === false) {
+
+ // Resolve response
+ resolve(response);
+ }
+
+ // Otherwise
+ else {
+
+ // Reject canceled error
+ reject(Common.CANCELED_ERROR);
+ }
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Check if cancel didn't occur
+ if(cancelOccurred === Common.NO_CANCEL_OCCURRED || cancelOccurred() === false) {
+
+ // Reject error
+ reject(error);
+ }
+
+ // Otherwise
+ else {
+
+ // Reject canceled error
+ reject(Common.CANCELED_ERROR);
+ }
+ });
+ }
+
+ // Otherwise check if hardware wallet requires passphrase acknowledgment
+ else if(self.transport.type === HardwareWalletDefinitions.TREZOR_TRANSPORT_TYPE && response["Message Type"] === HardwareWalletDefinitions.TREZOR_PASSPHRASE_REQUEST_MESSAGE_TYPE) {
+
+ // Return sending passphrase acknowledge response
+ return self.send(HardwareWalletDefinitions.TREZOR_PASSPHRASE_ACKNOWLEDGE_MESSAGE_TYPE, HardwareWallet.NO_PARAMETER, HardwareWallet.NO_PARAMETER, {
+
+ // Passphrase
+ "Passphrase": ""
+
+ }, allowedResponseTypes, text, textArguments, allowUnlock, failOnLock, preventMessages, cancelOccurred, true).then(function(response) {
+
+ // Check if cancel didn't occur
+ if(cancelOccurred === Common.NO_CANCEL_OCCURRED || cancelOccurred() === false) {
+
+ // Resolve response
+ resolve(response);
+ }
+
+ // Otherwise
+ else {
+
+ // Reject canceled error
+ reject(Common.CANCELED_ERROR);
+ }
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Check if cancel didn't occur
+ if(cancelOccurred === Common.NO_CANCEL_OCCURRED || cancelOccurred() === false) {
+
+ // Reject error
+ reject(error);
+ }
+
+ // Otherwise
+ else {
+
+ // Reject canceled error
+ reject(Common.CANCELED_ERROR);
+ }
+ });
+ }
+
+ // Otherwise check if hardware wallet requires pin matrix acknowledgment
+ else if(self.transport.type === HardwareWalletDefinitions.TREZOR_TRANSPORT_TYPE && response["Message Type"] === HardwareWalletDefinitions.TREZOR_PIN_MATRIX_REQUEST_MESSAGE_TYPE) {
+
+ // Check if not set to fail on lock and text exists
+ if(failOnLock === false && text !== HardwareWallet.NO_TEXT) {
+
+ // Set locked
+ self.locked = true;
+
+ // Initialize disconnected
+ var disconnected = false;
+
+ // Transport on disconnect
+ var disconnectCallback = self.transport.on("disconnect", function() {
+
+ // Set disconnected
+ disconnected = true;
+
+ // Clear locked
+ self.locked = false;
+
+ // Trigger before disconnect event
+ $(self).trigger(HardwareWallet.BEFORE_DISCONNECT_EVENT);
+ });
+
+ // Return showing hardware wallet unlock message
+ return self.application.showHardwareWalletUnlockMessage(self, text, textArguments, allowUnlock, preventMessages, cancelOccurred).then(function(alphabeticPin) {
+
+ // Check if disconnected
+ if(disconnected === true) {
+
+ // Check if not preventing unlock message done and not preventing messages
+ if(preventUnlockMessageDone === false && preventMessages === false) {
+
+ // Return hiding application hardware unlock message
+ return self.application.hardwareWalletUnlockMessageDone(preventMessages, cancelOccurred).then(function() {
+
+ // Reject disconnected error
+ reject(HardwareWallet.DISCONNECTED_ERROR);
+ });
+ }
+
+ // Otherwise
+ else {
+
+ // Reject disconnected error
+ reject(HardwareWallet.DISCONNECTED_ERROR);
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Turn off transport on disconnect
+ self.transport.off("disconnect", disconnectCallback);
+
+ // Clear locked
+ self.locked = false;
+
+ // Return sending pin matrix acknowledge response
+ return self.send(HardwareWalletDefinitions.TREZOR_PIN_MATRIX_ACKNOWLEDGE_MESSAGE_TYPE, HardwareWallet.NO_PARAMETER, HardwareWallet.NO_PARAMETER, {
+
+ // Pin
+ "Pin": HardwareWallet.alphabeticPinToPin(Common.removeWhitespace(alphabeticPin))
+
+ }, allowedResponseTypes, text, textArguments, allowUnlock, failOnLock, preventMessages, cancelOccurred, true, true).then(function(response) {
+
+ // Check if cancel didn't occur
+ if(cancelOccurred === Common.NO_CANCEL_OCCURRED || cancelOccurred() === false) {
+
+ // Check if not preventing unlock message done
+ if(preventUnlockMessageDone === false) {
+
+ // Return hiding application hardware unlock message
+ return self.application.hardwareWalletUnlockMessageDone(preventMessages, cancelOccurred).then(function() {
+
+ // Resolve response
+ resolve(response);
+ });
+ }
+
+ // Otherwise
+ else {
+
+ // Resolve response
+ resolve(response);
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Check if not preventing unlock message done
+ if(preventUnlockMessageDone === false) {
+
+ // Return hiding application hardware unlock message
+ return self.application.hardwareWalletUnlockMessageDone(preventMessages, cancelOccurred).then(function() {
+
+ // Reject canceled error
+ reject(Common.CANCELED_ERROR);
+ });
+ }
+
+ // Otherwise
+ else {
+
+ // Reject canceled error
+ reject(Common.CANCELED_ERROR);
+ }
+ }
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Check if cancel didn't occur
+ if(cancelOccurred === Common.NO_CANCEL_OCCURRED || cancelOccurred() === false) {
+
+ // Check if failure occurred
+ if(Object.isObject(error) === true && "Message Type" in error === true && error["Message Type"] === HardwareWalletDefinitions.TREZOR_FAILURE_MESSAGE_TYPE) {
+
+ // Initialize error occurred
+ var errorOccurred = false;
+
+ // Try
+ try {
+
+ // Decode error
+ var decodedError = self.decode(error["Message Type"], error["Data"]);
+ }
+
+ // Catch errors
+ catch(decodeError) {
+
+ // Set error occurred
+ errorOccurred = true;
+ }
+
+ // Check if an error didn't occur
+ if(errorOccurred === false) {
+
+ // Check if pin was incorrect
+ if(decodedError["length"] >= Uint8Array["BYTES_PER_ELEMENT"] && (decodedError[0] === HardwareWalletDefinitions.TREZOR_PIN_CANCELED_FAILURE_TYPE || decodedError[0] === HardwareWalletDefinitions.TREZOR_PIN_INVALID_FAILURE_TYPE)) {
+
+ // Return hiding application hardware unlock message
+ return self.application.hardwareWalletUnlockMessageDone(preventMessages, cancelOccurred, false).then(function() {
+
+ // Check if not preventing messages
+ if(preventMessages === false) {
+
+ // Message before replace hardware wallet event
+ $(self.application.message).on(Message.BEFORE_REPLACE_EVENT + ".hardwareWallet", function(event, messageType, messageData) {
+
+ // Check if message type is hardware wallet disconnect message
+ if(messageType === Application.HARDWARE_WALLET_DISCONNECT_MESSAGE) {
+
+ // Cancel replacing message
+ self.application.message.cancelReplace();
+
+ // Return false to stop other replace message
+ return false;
+ }
+ });
+ }
+
+ // Return resending message
+ return self.send(messageType, parameterOne, parameterTwo, data, allowedResponseTypes, (decodedError[0] === HardwareWalletDefinitions.TREZOR_PIN_CANCELED_FAILURE_TYPE) ? Language.getDefaultTranslation('Invalid pin.') : Language.getDefaultTranslation('Incorrect pin.'), [], allowUnlock, failOnLock, true, cancelOccurred, true, true).then(function(response) {
+
+ // Turn off message before replace hardware wallet event
+ $(self.application.message).off(Message.BEFORE_REPLACE_EVENT + ".hardwareWallet");
+
+ // Check if cancel didn't occur
+ if(cancelOccurred === Common.NO_CANCEL_OCCURRED || cancelOccurred() === false) {
+
+ // Check if not preventing unlock message done
+ if(preventUnlockMessageDone === false) {
+
+ // Return hiding application hardware unlock message
+ return self.application.hardwareWalletUnlockMessageDone(preventMessages, cancelOccurred).then(function() {
+
+ // Resolve response
+ resolve(response);
+ });
+ }
+
+ // Otherwise
+ else {
+
+ // Resolve response
+ resolve(response);
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Check if not preventing unlock message done
+ if(preventUnlockMessageDone === false) {
+
+ // Return hiding application hardware unlock message
+ return self.application.hardwareWalletUnlockMessageDone(preventMessages, cancelOccurred).then(function() {
+
+ // Reject canceled error
+ reject(Common.CANCELED_ERROR);
+ });
+ }
+
+ // Otherwise
+ else {
+
+ // Reject canceled error
+ reject(Common.CANCELED_ERROR);
+ }
+ }
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Turn off message before replace hardware wallet event
+ $(self.application.message).off(Message.BEFORE_REPLACE_EVENT + ".hardwareWallet");
+
+ // Check if cancel didn't occur
+ if(cancelOccurred === Common.NO_CANCEL_OCCURRED || cancelOccurred() === false) {
+
+ // Check if not preventing unlock message done and not preventing messages
+ if(preventUnlockMessageDone === false && preventMessages === false) {
+
+ // Return hiding application hardware unlock message
+ return self.application.hardwareWalletUnlockMessageDone(preventMessages, cancelOccurred).then(function() {
+
+ // Reject error
+ reject(error);
+ });
+ }
+
+ // Otherwise
+ else {
+
+ // Reject error
+ reject(error);
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Check if not preventing unlock message done
+ if(preventUnlockMessageDone === false) {
+
+ // Return hiding application hardware unlock message
+ return self.application.hardwareWalletUnlockMessageDone(preventMessages, cancelOccurred).then(function() {
+
+ // Reject canceled error
+ reject(Common.CANCELED_ERROR);
+ });
+ }
+
+ // Otherwise
+ else {
+
+ // Reject canceled error
+ reject(Common.CANCELED_ERROR);
+ }
+ }
+ });
+ });
+ }
+ }
+ }
+
+ // Check if not preventing unlock message done
+ if(preventUnlockMessageDone === false) {
+
+ // Return hiding application hardware unlock message
+ return self.application.hardwareWalletUnlockMessageDone(preventMessages, cancelOccurred).then(function() {
+
+ // Reject error
+ reject(error);
+ });
+ }
+
+ // Otherwise
+ else {
+
+ // Reject error
+ reject(error);
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Check if not preventing unlock message done
+ if(preventUnlockMessageDone === false) {
+
+ // Return hiding application hardware unlock message
+ return self.application.hardwareWalletUnlockMessageDone(preventMessages, cancelOccurred).then(function() {
+
+ // Reject canceled error
+ reject(Common.CANCELED_ERROR);
+ });
+ }
+
+ // Otherwise
+ else {
+
+ // Reject canceled error
+ reject(Common.CANCELED_ERROR);
+ }
+ }
+ });
+ }
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Check if disconnected
+ if(disconnected === true) {
+
+ // Reject disconnected error
+ reject(HardwareWallet.DISCONNECTED_ERROR);
+ }
+
+ // Otherwise
+ else {
+
+ // Turn off transport on disconnect
+ self.transport.off("disconnect", disconnectCallback);
+
+ // Clear locked
+ self.locked = false;
+
+ // Return sending pin matrix acknowledge response
+ return self.send(HardwareWalletDefinitions.TREZOR_PIN_MATRIX_ACKNOWLEDGE_MESSAGE_TYPE, HardwareWallet.NO_PARAMETER, HardwareWallet.NO_PARAMETER, {
+
+ // Pin
+ "Pin": HardwareWallet.INVALID_PIN
+
+ }, allowedResponseTypes, text, textArguments, allowUnlock, failOnLock, preventMessages, cancelOccurred, true).then(function(response) {
+
+ // Check if cancel didn't occur
+ if(cancelOccurred === Common.NO_CANCEL_OCCURRED || cancelOccurred() === false) {
+
+ // Reject error
+ reject(error);
+ }
+
+ // Otherwise
+ else {
+
+ // Reject canceled error
+ reject(Common.CANCELED_ERROR);
+ }
+
+ // Catch errors
+ }).catch(function() {
+
+ // Check if cancel didn't occur
+ if(cancelOccurred === Common.NO_CANCEL_OCCURRED || cancelOccurred() === false) {
+
+ // Reject error
+ reject(error);
+ }
+
+ // Otherwise
+ else {
+
+ // Reject canceled error
+ reject(Common.CANCELED_ERROR);
+ }
+ });
+ }
+ });
+ }
+
+ // Otherwise
+ else {
+
+ // Return sending pin matrix acknowledge response
+ return self.send(HardwareWalletDefinitions.TREZOR_PIN_MATRIX_ACKNOWLEDGE_MESSAGE_TYPE, HardwareWallet.NO_PARAMETER, HardwareWallet.NO_PARAMETER, {
+
+ // Pin
+ "Pin": HardwareWallet.INVALID_PIN
+
+ }, allowedResponseTypes, text, textArguments, allowUnlock, failOnLock, preventMessages, cancelOccurred, true).then(function(response) {
+
+ // Check if cancel didn't occur
+ if(cancelOccurred === Common.NO_CANCEL_OCCURRED || cancelOccurred() === false) {
+
+ // Resolve response
+ resolve(response);
+ }
+
+ // Otherwise
+ else {
+
+ // Reject canceled error
+ reject(Common.CANCELED_ERROR);
+ }
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Check if cancel didn't occur
+ if(cancelOccurred === Common.NO_CANCEL_OCCURRED || cancelOccurred() === false) {
+
+ // Reject error
+ reject(error);
+ }
+
+ // Otherwise
+ else {
+
+ // Reject canceled error
+ reject(Common.CANCELED_ERROR);
+ }
+ });
+ }
+ }
+
+ // Otherwise check if response type isn't allowed
+ else if(response["Message Type"] !== ((Array.isArray(allowedResponseTypes) === true) ? allowedResponseTypes[self.transport.type] : allowedResponseTypes)) {
+
+ // Check if cancel didn't occur
+ if(cancelOccurred === Common.NO_CANCEL_OCCURRED || cancelOccurred() === false) {
+
+ // Check if response data isn't used
+ if(response["Message Type"] !== HardwareWalletDefinitions.TREZOR_FAILURE_MESSAGE_TYPE) {
+
+ // Securely clear response data
+ response["Data"].fill(0);
+ }
+
+ // Reject response
+ reject(response);
+ }
+
+ // Otherwise
+ else {
+
+ // Securely clear response data
+ response["Data"].fill(0);
+
+ // Reject canceled error
+ reject(Common.CANCELED_ERROR);
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Check if cancel didn't occur
+ if(cancelOccurred === Common.NO_CANCEL_OCCURRED || cancelOccurred() === false) {
+
+ // Try
+ try {
+
+ // Decode response
+ var decodedResponse = self.decode(response["Message Type"], response["Data"]);
+ }
+
+ // Catch errors
+ catch(error) {
+
+ // Securely clear response data
+ response["Data"].fill(0);
+
+ // Reject error
+ reject(error);
+
+ // Return
+ return;
+ }
+
+ // Securely clear response data
+ response["Data"].fill(0);
+
+ // Resolve decoded response
+ resolve(decodedResponse);
+ }
+
+ // Otherwise
+ else {
+
+ // Securely clear response data
+ response["Data"].fill(0);
+
+ // Reject canceled error
+ reject(Common.CANCELED_ERROR);
+ }
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Securely clear response data
+ response["Data"].fill(0);
+
+ // Check if unlock message isn't shown
+ if(unlockMessageShown === false) {
+
+ // Check if hardware wallet requires button acknowledgment
+ if(self.transport.type === HardwareWalletDefinitions.TREZOR_TRANSPORT_TYPE && response["Message Type"] === HardwareWalletDefinitions.TREZOR_BUTTON_REQUEST_MESSAGE_TYPE) {
+
+ // Return sending button acknowledge response and catch errors
+ return self.send(HardwareWalletDefinitions.TREZOR_BUTTON_ACKNOWLEDGE_MESSAGE_TYPE, HardwareWallet.NO_PARAMETER, HardwareWallet.NO_PARAMETER, HardwareWallet.NO_DATA, [], HardwareWallet.NO_TEXT, [], false, true).catch(function(error) {
+
+ // Finally
+ }).finally(function() {
+
+ // Reject canceled error
+ reject(Common.CANCELED_ERROR);
+ });
+ }
+
+ // Otherwise check if hardware wallet requires passphrase acknowledgment
+ else if(self.transport.type === HardwareWalletDefinitions.TREZOR_TRANSPORT_TYPE && response["Message Type"] === HardwareWalletDefinitions.TREZOR_PASSPHRASE_REQUEST_MESSAGE_TYPE) {
+
+ // Return sending passphrase acknowledge response
+ return self.send(HardwareWalletDefinitions.TREZOR_PASSPHRASE_ACKNOWLEDGE_MESSAGE_TYPE, HardwareWallet.NO_PARAMETER, HardwareWallet.NO_PARAMETER, {
+
+ // Passphrase
+ "Passphrase": ""
+
+ }, [], HardwareWallet.NO_TEXT, [], false, true).catch(function(error) {
+
+ // Finally
+ }).finally(function() {
+
+ // Reject canceled error
+ reject(Common.CANCELED_ERROR);
+ });
+ }
+
+ // Otherwise check if hardware wallet requires pin matrix acknowledgment
+ else if(self.transport.type === HardwareWalletDefinitions.TREZOR_TRANSPORT_TYPE && response["Message Type"] === HardwareWalletDefinitions.TREZOR_PIN_MATRIX_REQUEST_MESSAGE_TYPE) {
+
+ // Return sending pin matrix acknowledge response
+ return self.send(HardwareWalletDefinitions.TREZOR_PIN_MATRIX_ACKNOWLEDGE_MESSAGE_TYPE, HardwareWallet.NO_PARAMETER, HardwareWallet.NO_PARAMETER, {
+
+ // Pin
+ "Pin": HardwareWallet.INVALID_PIN
+
+ }, [], HardwareWallet.NO_TEXT, [], false, true).catch(function(error) {
+
+ // Finally
+ }).finally(function() {
+
+ // Reject canceled error
+ reject(Common.CANCELED_ERROR);
+ });
+ }
+ }
+
+ // Reject canceled error
+ reject(Common.CANCELED_ERROR);
+ }
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Check if cancel didn't occur
+ if(cancelOccurred === Common.NO_CANCEL_OCCURRED || cancelOccurred() === false) {
+
+ // Check if error is that the device was disconnected
+ if(typeof error === "object" && error !== null && (("code" in error === true && error["code"] === HardwareWallet.NETWORK_ERROR_CODE) || ("name" in error === true && error["name"] === "NetworkError"))) {
+
+ // Reject disconnected error
+ reject(HardwareWallet.DISCONNECTED_ERROR);
+ }
+
+ // Otherwise
+ else {
+
+ // Reject error
+ reject(error);
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Reject canceled error
+ reject(Common.CANCELED_ERROR);
+ }
+ });
+ }
+
+ // Otherwise
+ else {
+
+ // Reject canceled error
+ reject(Common.CANCELED_ERROR);
+ }
+ });
+ }
+
+ // Encode
+ encode(messageType, data) {
+
+ // Check if data is no data
+ if(data === HardwareWallet.NO_DATA) {
+
+ // Return nothing
+ return new Uint8Array([]);
+ }
+
+ // Check transport's type
+ switch(this.transport.type) {
+
+ // Ledger type
+ case HardwareWalletDefinitions.LEDGER_TRANSPORT_TYPE:
+
+ // Get message schema
+ var messageSchema = HardwareWalletDefinitions.SCHEMA[messageType.toFixed()];
+
+ // Initialize result
+ var result = new Uint8Array([]);
+
+ // Go through all fields in the message schema
+ for(var fieldNumber in messageSchema) {
+
+ if(messageSchema.hasOwnProperty(fieldNumber) === true) {
+
+ // Check if field isn't ignored
+ if(HardwareWalletDefinitions.LEDGER_IGNORE_FIELD_NAMES.indexOf(messageSchema[fieldNumber]["Name"]) === Common.INDEX_NOT_FOUND) {
+
+ // Go through all values in the data
+ for(var name in data) {
+
+ if(data.hasOwnProperty(name) === true) {
+
+ // Check if data is for the field
+ if(name === messageSchema[fieldNumber]["Name"]) {
+
+ // Check if data is a big number
+ if(data[name] instanceof BigNumber === true) {
+
+ // Set field payload
+ var fieldPayload = data[name].toBytes(BigNumber.LITTLE_ENDIAN, messageSchema[fieldNumber]["Size"]);
+ }
+
+ // Otherwise check if data is bytes
+ else if(data[name] instanceof Uint8Array === true) {
+
+ // Set field payload
+ var fieldPayload = data[name];
+ }
+
+ // Otherwise check if data is a string
+ else if(typeof data[name] === "string") {
+
+ // Set field payload
+ var fieldPayload = (new TextEncoder()).encode(data[name]);
+ }
+
+ // Otherwise check if data is a number
+ else if(typeof data[name] === "number") {
+
+ // Set field payload
+ var fieldPayload = new Uint8Array([data[name]]);
+ }
+
+ // Append field payload to the result
+ result = Common.mergeArrays([
+
+ // Result
+ result,
+
+ // Field payload
+ fieldPayload
+ ]);
+
+ // Break
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // Return result
+ return result;
+
+ // Trezor type
+ case HardwareWalletDefinitions.TREZOR_TRANSPORT_TYPE:
+
+ // Return data encoded as Protocol Buffers
+ return ProtocolBuffers.encode(messageType, data, HardwareWalletDefinitions.SCHEMA);
+ }
+ }
+
+ // Decode
+ decode(messageType, data, transportType = HardwareWallet.NO_TRANSPORT_TYPE) {
+
+ // Check transport's type
+ switch((transportType !== HardwareWallet.NO_TRANSPORT_TYPE) ? transportType : this.transport.type) {
+
+ // Ledger type
+ case HardwareWalletDefinitions.LEDGER_TRANSPORT_TYPE:
+
+ // Return data
+ return new Uint8Array(data);
+
+ // Trezor type
+ case HardwareWalletDefinitions.TREZOR_TRANSPORT_TYPE:
+
+ // Decode data as Protocol Buffers
+ var protocolBuffers = ProtocolBuffers.decode(messageType, data, HardwareWalletDefinitions.SCHEMA);
+
+ // Get message schema
+ var messageSchema = HardwareWalletDefinitions.SCHEMA[messageType.toFixed()];
+
+ // Initialize result
+ var result = new Uint8Array([]);
+
+ // Go through all fields in the message schema
+ for(var fieldNumber in messageSchema) {
+
+ if(messageSchema.hasOwnProperty(fieldNumber) === true) {
+
+ // Check if field doesn't exist in the Protocol Buffers
+ if(messageSchema[fieldNumber]["Name"] in protocolBuffers === false) {
+
+ // Check if field is optional
+ if("Optional" in messageSchema[fieldNumber] === true && messageSchema[fieldNumber]["Optional"] === true) {
+
+ // Go to next field
+ continue;
+ }
+
+ // Check if field's type is bool
+ if(messageSchema[fieldNumber]["Type"] === ProtocolBuffers.BOOL_SCHEMA_DATA_TYPE) {
+
+ // Set field payload to false
+ protocolBuffers[messageSchema[fieldNumber]["Name"]] = [false];
+ }
+
+ // Otherwise
+ else {
+
+ // Securely clear result
+ result.fill(0);
+
+ // Throw error
+ throw "Field doesn't exist in the Protocol Buffers.";
+ }
+ }
+
+ // Get field payload
+ var fieldPayload = protocolBuffers[messageSchema[fieldNumber]["Name"]];
+
+ // Set include length
+ var includeLength = false;
+
+ // Check field's expected type
+ switch(messageSchema[fieldNumber]["Type"]) {
+
+ // Uint
+ case ProtocolBuffers.UINT_SCHEMA_DATA_TYPE:
+
+ // Set field data
+ var fieldData = fieldPayload[fieldPayload["length"] - 1].toBytes(BigNumber.BIG_ENDIAN, messageSchema[fieldNumber]["Size"]);
+
+ // Break
+ break;
+
+ // Bool
+ case ProtocolBuffers.BOOL_SCHEMA_DATA_TYPE:
+
+ // Set field data
+ var fieldData = new Uint8Array([(fieldPayload[fieldPayload["length"] - 1] === true) ? 1 : 0]);
+
+ // Break
+ break;
+
+ // Enum
+ case ProtocolBuffers.ENUM_SCHEMA_DATA_TYPE:
+
+ // Set field data
+ var fieldData = new Uint8Array(fieldPayload);
+
+ // Check if more than one value exists in the field payload
+ if(fieldPayload["length"] > 1) {
+
+ // Check if field length is too big
+ if(fieldPayload["length"] > Common.BYTE_MAX_VALUE) {
+
+ // Throw error
+ throw "Field length is too big.";
+ }
+
+ // Set field data to include field length
+ fieldData = Common.mergeArrays([new Uint8Array([fieldPayload["length"]]), fieldData]);
+ }
+
+ // Break
+ break;
+
+ // String
+ case ProtocolBuffers.STRING_SCHEMA_DATA_TYPE:
+
+ // Set field data
+ var fieldData = (new TextEncoder()).encode(fieldPayload[fieldPayload["length"] - 1]);
+
+ // Set include length
+ includeLength = true;
+
+ // Check if field length is too big
+ if(fieldData["length"] > Common.BYTE_MAX_VALUE) {
+
+ // Throw error
+ throw "Field length is too big.";
+ }
+
+ // Break
+ break;
+
+ // Bytes
+ case ProtocolBuffers.BYTES_SCHEMA_DATA_TYPE:
+
+ // Set field data
+ var fieldData = fieldPayload[fieldPayload["length"] - 1];
+
+ // Break
+ break;
+
+ // Sint
+ case ProtocolBuffers.SINT_SCHEMA_DATA_TYPE:
+
+ // Throw error
+ throw "Field type not supported.";
+ }
+
+ // Append field data to the result
+ var currentResult = new Uint8Array(result["length"] + ((includeLength === true) ? Uint8Array["BYTES_PER_ELEMENT"] : 0) + fieldData["length"]);
+ currentResult.set(result);
+
+ if(includeLength === true) {
+ currentResult[result["length"]] = fieldData["length"];
+ }
+
+ currentResult.set(fieldData, result["length"] + ((includeLength === true) ? Uint8Array["BYTES_PER_ELEMENT"] : 0));
+ result.fill(0);
+ result = currentResult;
+ }
+ }
+
+ // Return result
+ return result;
+ }
+ }
+
+ // Get minimum compatible version
+ getMinimumCompatibleVersion() {
+
+ // Check transport's type
+ switch(this.transport.type) {
+
+ // Ledger type
+ case HardwareWalletDefinitions.LEDGER_TRANSPORT_TYPE:
+
+ // Return minimum compatible version
+ return "7.4.1";
+
+ // Trezor type
+ case HardwareWalletDefinitions.TREZOR_TRANSPORT_TYPE:
+
+ // Check transport's product name
+ switch(this.transport["deviceModel"]["productName"]) {
+
+ // Trezor Model One
+ case "Trezor Model One":
+
+ // Return minimum compatible version
+ return "1.12.2";
+
+ // Trezor Model T, Trezor Safe 3, Safe 5, or default
+ case "Trezor Model T":
+ case "Trezor Safe 3":
+ case "Trezor Safe 5":
+ default:
+
+ // Return minimum compatible version
+ return "2.6.4";
+ }
+ }
+ }
+
+ // Alphabetic pin to pin
+ static alphabeticPinToPin(alphabeticPin) {
+
+ // Check if alphabetic pin is empty
+ if(alphabeticPin["length"] === 0) {
+
+ // Return invalid pin
+ return HardwareWallet.INVALID_PIN;
+ }
+
+ // Initialize result
+ var result = "";
+
+ // Go through all characters in the alphabetic pin
+ for(var i = 0; i < alphabeticPin["length"]; ++i) {
+
+ // Check if character is valid
+ if(alphabeticPin[i] in HardwareWallet.ALPHABETIC_PIN_CHARACTERS === true) {
+
+ // Append pin character to result
+ result += HardwareWallet.ALPHABETIC_PIN_CHARACTERS[alphabeticPin[i]];
+ }
+
+ // Otherwise
+ else {
+
+ // Return invalid pin
+ return HardwareWallet.INVALID_PIN;
+ }
+ }
+
+ // Return result
+ return result;
+ }
+
+ // Alphabetic pin characters
+ static get ALPHABETIC_PIN_CHARACTERS() {
+
+ // Return alphabetic pin characters
+ return {
+
+ // Upper case a
+ "A": "7",
+
+ // Upper case b
+ "B": "8",
+
+ // Upper case c
+ "C": "9",
+
+ // Upper case d
+ "D": "4",
+
+ // Upper case e
+ "E": "5",
+
+ // Upper case f
+ "F": "6",
+
+ // Upper case g
+ "G": "1",
+
+ // Upper case h
+ "H": "2",
+
+ // Upper case i
+ "I": "3",
+
+ // Lower case a
+ "a": "7",
+
+ // Lower case b
+ "b": "8",
+
+ // Lower case c
+ "c": "9",
+
+ // Lower case d
+ "d": "4",
+
+ // Lower case e
+ "e": "5",
+
+ // Lower case f
+ "f": "6",
+
+ // Lower case g
+ "g": "1",
+
+ // Lower case h
+ "h": "2",
+
+ // Lower case i
+ "i": "3"
+ };
+ }
+
+ // Invalid pin
+ static get INVALID_PIN() {
+
+ // Return invalid pin
+ return "A";
+ }
+
+ // Is compatible version
+ static isCompatibleVersion(version, minimumCompatibleVersion) {
+
+ // Get version parts from the version
+ var versionParts = version.match(HardwareWallet.VERSION_STRING_PATTERN);
+
+ // Check if getting version parts was successful
+ if(versionParts !== Common.NO_MATCH_FOUND) {
+
+ // Get minimum compatible version parts
+ var minimumCompatibleVersionParts = minimumCompatibleVersion.match(HardwareWallet.VERSION_STRING_PATTERN);
+
+ // Go through all version parts
+ for(var i = 0; i < versionParts["length"]; ++i) {
+
+ // Check if version part is greater than the minimum compatible version part
+ if((new BigNumber(versionParts[i])).isGreaterThan(minimumCompatibleVersionParts[i]) === true) {
+
+ // Return true
+ return true;
+ }
+
+ // Otherwise check if version part is less than the minimum compatible version part
+ else if((new BigNumber(versionParts[i])).isLessThan(minimumCompatibleVersionParts[i]) === true) {
+
+ // Return false
+ return false;
+ }
+ }
+
+ // Return true
+ return true;
+ }
+
+ // Return false
+ return false;
+ }
+
+ // Exclusive lock release event
+ static get EXCLUSIVE_LOCK_RELEASE_EVENT() {
+
+ // Return exclusive lock release event
+ return "HardwareWalletExclusiveLockReleaseEvent";
+ }
+
+ // No transport
+ static get NO_TRANSPORT() {
+
+ // Return no transport
+ return null;
+ }
+
+ // No root public key
+ static get NO_ROOT_PUBLIC_KEY() {
+
+ // Return no root public key
+ return null;
+ }
+
+ // No seed cookie
+ static get NO_SEED_COOKIE() {
+
+ // Return no seed cookie
+ return null;
+ }
+
+ // No address
+ static get NO_ADDRESS() {
+
+ // Return no address
+ return null;
+ }
+
+ // No kernel commit
+ static get NO_KERNEL_COMMIT() {
+
+ // Return no kernel commit
+ return null;
+ }
+
+ // No payment proof
+ static get NO_PAYMENT_PROOF() {
+
+ // Return no payment proof
+ return null;
+ }
+
+ // Application name
+ static get APPLICATION_NAME() {
+
+ // Check wallet type
+ switch(Consensus.getWalletType()) {
+
+ // MWC wallet
+ case Consensus.MWC_WALLET_TYPE:
+
+ // Check network type
+ switch(Consensus.getNetworkType()) {
+
+ // Mainnet network type
+ case Consensus.MAINNET_NETWORK_TYPE:
+
+ // Return application name
+ return "MimbleWimble Coin";
+
+ // Testnet network type
+ case Consensus.TESTNET_NETWORK_TYPE:
+
+ // Return application name
+ return "MimbleWimble Coin Floonet";
+ }
+
+ // Break
+ break;
+
+ // GRIN wallet
+ case Consensus.GRIN_WALLET_TYPE:
+
+ // Check network type
+ switch(Consensus.getNetworkType()) {
+
+ // Mainnet network type
+ case Consensus.MAINNET_NETWORK_TYPE:
+
+ // Return application name
+ return "Grin";
+
+ // Testnet network type
+ case Consensus.TESTNET_NETWORK_TYPE:
+
+ // Return application name
+ return "Grin Testnet";
+ }
+
+ // Break
+ break;
+
+ // EPIC wallet
+ case Consensus.EPIC_WALLET_TYPE:
+
+ // Check network type
+ switch(Consensus.getNetworkType()) {
+
+ // Mainnet network type
+ case Consensus.MAINNET_NETWORK_TYPE:
+
+ // Return application name
+ return "Epic Cash";
+
+ // Testnet network type
+ case Consensus.TESTNET_NETWORK_TYPE:
+
+ // Return application name
+ return "Epic Cash Floonet";
+ }
+
+ // Break
+ break;
+ }
+ }
+
+ // MimbleWimble Coin capable
+ static get MIMBLEWIMBLE_COIN_CAPABLE() {
+
+ // Return MimbleWimble Coin capable
+ return 0xC7;
+ }
+
+ // Version string pattern
+ static get VERSION_STRING_PATTERN() {
+
+ // Return hex string pattern
+ return /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/u;
+ }
+
+ // Seed cookie length
+ static get SEED_COOKIE_LENGTH() {
+
+ // Return seed cookie length
+ return 64;
+ }
+
+ // No parameter
+ static get NO_PARAMETER() {
+
+ // Return no parameter
+ return 0;
+ }
+
+ // Account
+ static get ACCOUNT() {
+
+ // Return account
+ return 0;
+ }
+
+ // MQS address type
+ static get MQS_ADDRESS_TYPE() {
+
+ // Return MQS address type
+ return 0;
+ }
+
+ // Tor address type
+ static get TOR_ADDRESS_TYPE() {
+
+ // Return Tor address type
+ return HardwareWallet.MQS_ADDRESS_TYPE + 1;
+ }
+
+ // Slatepack address type
+ static get SLATEPACK_ADDRESS_TYPE() {
+
+ // Return Slatepack address type
+ return HardwareWallet.TOR_ADDRESS_TYPE + 1;
+ }
+
+ // No data
+ static get NO_DATA() {
+
+ // Return no data
+ return null;
+ }
+
+ // Resend request delay milliseconds
+ static get RESEND_REQUEST_DELAY_MILLISECONDS() {
+
+ // Return resend request delay milliseconds
+ return 50;
+ }
+
+ // Not found error code
+ static get NOT_FOUND_ERROR_CODE() {
+
+ // Return not found error code
+ return 8;
+ }
+
+ // Invalid state error code
+ static get INVALID_STATE_ERROR_CODE() {
+
+ // Return invalid state error code
+ return 11;
+ }
+
+ // Network error code
+ static get NETWORK_ERROR_CODE() {
+
+ // Return network error code
+ return 19;
+ }
+
+ // No salt
+ static get NO_SALT() {
+
+ // Return no salt
+ return null;
+ }
+
+ // Encryption and decryption maximum chunk size
+ static get ENCRYPTION_AND_DECRYPTION_MAXIMUM_CHUNK_SIZE() {
+
+ // Return encryption and decryption maximum chunk size
+ return 64;
+ }
+
+ // No transport type
+ static get NO_TRANSPORT_TYPE() {
+
+ // Return no transport type
+ return null;
+ }
+}
+
+
+// Main function
+
+// Set global object's hardware wallet
+globalThis["HardwareWallet"] = HardwareWallet;
diff --git a/scripts/hardware_wallet_bluetooth_transport.js b/scripts/hardware_wallet_bluetooth_transport.js
new file mode 100755
index 0000000..6422638
--- /dev/null
+++ b/scripts/hardware_wallet_bluetooth_transport.js
@@ -0,0 +1,1277 @@
+// Use strict
+"use strict";
+
+
+// Classes
+
+// HardwareWallet Bluetooth transport class
+class HardwareWalletBluetoothTransport {
+
+ // Public
+
+ // Constructor
+ constructor(connection, writeCharacteristic, notifyCharacteristic, mtu, productName) {
+
+ // Set connection
+ this.connection = connection;
+
+ // Set write characteristic
+ this.writeCharacteristic = writeCharacteristic;
+
+ // Set notify characteristic
+ this.notifyCharacteristic = notifyCharacteristic;
+
+ // Set MTU
+ this.mtu = mtu;
+
+ // Set allow disconnect event to true
+ this.allowDisconnectEvent = true;
+
+ // Set type to Ledger
+ this.type = HardwareWalletDefinitions.LEDGER_TRANSPORT_TYPE;
+
+ // Set device model
+ this["deviceModel"] = {
+
+ // Product name
+ "productName": productName
+ };
+ }
+
+ // On
+ on(event, callback) {
+
+ // Check event
+ switch(event) {
+
+ // Disconnect
+ case "disconnect":
+
+ // Set self
+ var self = this;
+
+ // Create callback once
+ var callbackOnce = function() {
+
+ // Remove GATT server disconnected event
+ self.connection["device"].removeEventListener("gattserverdisconnected", callbackOnce);
+
+ // Check if disconnect event is allowed
+ if(self.allowDisconnectEvent === true) {
+
+ // Call callback
+ callback();
+ }
+ };
+
+ // Device GATT server disconnected event
+ this.connection["device"].addEventListener("gattserverdisconnected", callbackOnce);
+
+ // Return callback once
+ return callbackOnce;
+ }
+ }
+
+ // Off
+ off(event, callback) {
+
+ // Check event
+ switch(event) {
+
+ // Disconnect
+ case "disconnect":
+
+ // Remove GATT server disconnected event
+ this.connection["device"].removeEventListener("gattserverdisconnected", callback);
+
+ // Break
+ break;
+ }
+ }
+
+ // Close
+ close() {
+
+ // Clear allow disconnect event
+ this.allowDisconnectEvent = false;
+
+ // Check if connection is connected
+ if(this.connection["connected"] === true) {
+
+ // Disconnect connection
+ this.connection.disconnect();
+ }
+
+ // Return promise
+ return new Promise(function(resolve, reject) {
+
+ // Resolve
+ resolve();
+ });
+ }
+
+ // Send
+ send(messageType, parameterOne, parameterTwo, data) {
+
+ // Set self
+ var self = this;
+
+ // Return promise
+ return new Promise(function(resolve, reject) {
+
+ // Check if connection is connected
+ if(self.connection["connected"] === true) {
+
+ // Create header
+ var header = new Uint8Array([messageType >>> HardwareWalletBluetoothTransport.BITS_IN_A_BYTE, messageType, parameterOne, parameterTwo, data["length"]]);
+
+ // Create payload
+ var payload = new Uint8Array(header["length"] + data["length"]);
+ payload.set(header);
+ payload.set(data, header["length"]);
+
+ // Return sending request to the device
+ return HardwareWalletBluetoothTransport.sendRequest(self.connection, self.writeCharacteristic, self.notifyCharacteristic, HardwareWalletBluetoothTransport.LEDGER_SEND_REQUEST_COMMAND_TAG, payload, self.mtu).then(function(response) {
+
+ // Check if connection is connected
+ if(self.connection["connected"] === true) {
+
+ // Check if response contains a message type
+ if(response["length"] >= HardwareWalletBluetoothTransport.MESSAGE_TYPE_LENGTH) {
+
+ // Get message type
+ var messageType = (response[response["length"] - HardwareWalletBluetoothTransport.MESSAGE_TYPE_LENGTH] << HardwareWalletBluetoothTransport.BITS_IN_A_BYTE) | response[response["length"] - (HardwareWalletBluetoothTransport.MESSAGE_TYPE_LENGTH - 1)];
+
+ // Resolve
+ resolve({
+
+ // Message type
+ "Message Type": messageType,
+
+ // Data
+ "Data": response.subarray(0, response["length"] - HardwareWalletBluetoothTransport.MESSAGE_TYPE_LENGTH)
+ });
+ }
+
+ // Otherwise
+ else {
+
+ // Securely clear response
+ response.fill(0);
+
+ // Reject
+ reject();
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Securely clear response
+ response.fill(0);
+
+ // Reject error
+ reject(new DOMException("", "NetworkError"));
+ }
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Check if connection is connected
+ if(self.connection["connected"] === true) {
+
+ // Reject error
+ reject(error);
+ }
+
+ // Otherwise
+ else {
+
+ // Reject error
+ reject(new DOMException("", "NetworkError"));
+ }
+ });
+ }
+
+ // Otherwise
+ else {
+
+ // Reject error
+ reject(new DOMException("", "NetworkError"));
+ }
+ });
+ }
+
+ // Request
+ static request(device = HardwareWalletBluetoothTransport.NO_DEVICE) {
+
+ // Return promise
+ return new Promise(function(resolve, reject) {
+
+ // Get device
+ var getDevice = function() {
+
+ // Return promise
+ return new Promise(function(resolve, reject) {
+
+ // Check if no device was provided
+ if(device === HardwareWalletBluetoothTransport.NO_DEVICE) {
+
+ // Return getting device
+ return navigator["bluetooth"].requestDevice({
+
+ // Filters
+ "filters": HardwareWalletBluetoothTransport.DEVICES.map(function(device) {
+
+ // Return device's service UUID
+ return {
+
+ // Services
+ "services": [device["Service UUID"]]
+ };
+ })
+ }).then(function(device) {
+
+ // Check if device isn't connected
+ if(device["gatt"]["connected"] === false) {
+
+ // Resolve device
+ resolve(device);
+ }
+
+ // Otherwise
+ else {
+
+ // Reject error
+ reject(new DOMException("", "InvalidStateError"));
+ }
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Reject error
+ reject(error);
+ });
+ }
+
+ // Otherwise
+ else {
+
+ // Resolve device
+ resolve(device);
+ }
+ });
+ };
+
+ // Return getting device
+ return getDevice().then(function(device) {
+
+ // Get connection
+ var getConnection = function() {
+
+ // Return promise
+ return new Promise(function(resolve, reject) {
+
+ // Check if device's connection is already connected
+ if(device["gatt"]["connected"] === true) {
+
+ // Resolve connection
+ resolve(device["gatt"]);
+ }
+
+ // Otherwise
+ else {
+
+ // Return getting connection to the device
+ return device["gatt"].connect().then(function(connection) {
+
+ // Check if connection is connected
+ if(connection["connected"] === true) {
+
+ // Resolve connection
+ resolve(connection);
+ }
+
+ // Otherwise
+ else {
+
+ // Reject
+ reject();
+ }
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Reject error
+ reject(error);
+ });
+ }
+ });
+ };
+
+ // Return getting connection
+ return getConnection().then(function(connection) {
+
+ // Check if connection is connected
+ if(connection["connected"] === true) {
+
+ // Initialize timeout occurred
+ var timeoutOccurred = false;
+
+ // Connection timeout
+ var connectTimeout = setTimeout(function() {
+
+ // Set timeout occurred
+ timeoutOccurred = true;
+
+ // Check if connection is connected
+ if(connection["connected"] === true) {
+
+ // Disconnect connection
+ connection.disconnect();
+ }
+
+ // Reject
+ reject();
+
+ }, HardwareWalletBluetoothTransport.CONNECT_TIMEOUT_DURATION_MILLISECONDS);
+
+ // Return getting connection's services
+ return connection.getPrimaryServices().then(function(services) {
+
+ // Check if a timeout didn't occur
+ if(timeoutOccurred === false) {
+
+ // Clear connect timeout
+ clearTimeout(connectTimeout);
+
+ // Check if connection is connected
+ if(connection["connected"] === true) {
+
+ // Initialize device found
+ var deviceFound = false;
+
+ // Go through all services
+ for(var i = 0; i < services["length"] && deviceFound === false; ++i) {
+
+ // Check if service has a UUID
+ if("uuid" in services[i] === true) {
+
+ // Go through all devices
+ for(var j = 0; j < HardwareWalletBluetoothTransport.DEVICES["length"]; ++j) {
+
+ // Check if device's service UUID is the service's UUID
+ if(HardwareWalletBluetoothTransport.DEVICES[j]["Service UUID"] === services[i]["uuid"]) {
+
+ // Set device found
+ deviceFound = true;
+
+ // Set device index
+ var deviceIndex = j;
+
+ // Set service
+ var service = services[i];
+
+ // Break
+ break;
+ }
+ }
+ }
+ }
+
+ // Check if device was found
+ if(deviceFound === true) {
+
+ // Return getting service's notify characteristic
+ return service.getCharacteristic(HardwareWalletBluetoothTransport.DEVICES[deviceIndex]["Notify Characteristic UUID"]).then(function(notifyCharacteristic) {
+
+ // Check if connection is connected
+ if(connection["connected"] === true) {
+
+ // Return getting service's write characteristic
+ return service.getCharacteristic(HardwareWalletBluetoothTransport.DEVICES[deviceIndex]["Write Characteristic UUID"]).then(function(writeCharacteristic) {
+
+ // Check if connection is connected
+ if(connection["connected"] === true) {
+
+ // Return starting notify characteristic's notifications
+ return notifyCharacteristic.startNotifications().then(function() {
+
+ // Check if connection is connected
+ if(connection["connected"] === true) {
+
+ // Disconnected handler
+ var disconnectedHandler = function() {
+
+ // Remove GATT server disconnected event
+ device.removeEventListener("gattserverdisconnected", disconnectedHandler);
+
+ // Stop notifications and catch errors
+ notifyCharacteristic.stopNotifications().catch(function(error) {
+
+ });
+ };
+
+ // Device GATT server disconnected event
+ device.addEventListener("gattserverdisconnected", disconnectedHandler);
+
+ // Return getting MTU from the device
+ return HardwareWalletBluetoothTransport.sendRequest(connection, writeCharacteristic, notifyCharacteristic, HardwareWalletBluetoothTransport.LEDGER_GET_MTU_COMMAND_TAG).then(function(response) {
+
+ // Check if connection is connected
+ if(connection["connected"] === true) {
+
+ // Check if response is valid
+ if(response["length"] === 1) {
+
+ // Get MTU from response
+ var mtu = Math.min(response[0], HardwareWalletBluetoothTransport.MAXIMUM_MTU);
+
+ // Check if MTU is valid
+ if(mtu >= HardwareWalletBluetoothTransport.MINIMUM_MTU) {
+
+ // Create transport
+ var transport = new HardwareWalletBluetoothTransport(connection, writeCharacteristic, notifyCharacteristic, mtu, HardwareWalletBluetoothTransport.DEVICES[deviceIndex]["Product Name"]);
+
+ // Resolve transport
+ resolve(transport);
+ }
+
+ // Otherwise
+ else {
+
+ // Disconnect connection
+ connection.disconnect();
+
+ // Reject
+ reject();
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Disconnect connection
+ connection.disconnect();
+
+ // Reject
+ reject();
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Reject
+ reject();
+ }
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Check if connection is connected
+ if(connection["connected"] === true) {
+
+ // Disconnect connection
+ connection.disconnect();
+ }
+
+ // Reject error
+ reject(error);
+ });
+ }
+
+ // Otherwise
+ else {
+
+ // Return stopping notifications and catch errors
+ return notifyCharacteristic.stopNotifications().catch(function(error) {
+
+ // Finally
+ }).finally(function() {
+
+ // Reject
+ reject();
+ });
+ }
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Check if connection is connected
+ if(connection["connected"] === true) {
+
+ // Disconnect connection
+ connection.disconnect();
+ }
+
+ // Reject error
+ reject(error);
+ });
+ }
+
+ // Otherwise
+ else {
+
+ // Reject
+ reject();
+ }
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Check if connection is connected
+ if(connection["connected"] === true) {
+
+ // Disconnect connection
+ connection.disconnect();
+ }
+
+ // Reject error
+ reject(error);
+ });
+ }
+
+ // Otherwise
+ else {
+
+ // Reject
+ reject();
+ }
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Check if connection is connected
+ if(connection["connected"] === true) {
+
+ // Disconnect connection
+ connection.disconnect();
+ }
+
+ // Reject error
+ reject(error);
+ });
+ }
+
+ // Otherwise
+ else {
+
+ // Check if connection is connected
+ if(connection["connected"] === true) {
+
+ // Disconnect connection
+ connection.disconnect();
+ }
+
+ // Reject
+ reject();
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Reject
+ reject();
+ }
+ }
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Check if a timeout didn't occur
+ if(timeoutOccurred === false) {
+
+ // Clear connect timeout
+ clearTimeout(connectTimeout);
+
+ // Check if connection is connected
+ if(connection["connected"] === true) {
+
+ // Disconnect connection
+ connection.disconnect();
+ }
+
+ // Check if disconnected error occurred
+ if(typeof error === "object" && error !== null && (("code" in error === true && error["code"] === HardwareWalletBluetoothTransport.NETWORK_ERROR_CODE) || ("name" in error === true && error["name"] === "NetworkError"))) {
+
+ // Return requesting transport
+ return HardwareWalletBluetoothTransport.request(device).then(function(transport) {
+
+ // Resolve transport
+ resolve(transport);
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Reject error
+ reject(error);
+ });
+ }
+
+ // Otherwise
+ else {
+
+ // Reject error
+ reject(error);
+ }
+ }
+ });
+ }
+
+ // Otherwise
+ else {
+
+ // Reject
+ reject();
+ }
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Check if device's connection is connected
+ if(device["gatt"]["connected"] === true) {
+
+ // Disconnect device's connection
+ device["gatt"].disconnect();
+ }
+
+ // Reject error
+ reject(error);
+ });
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Reject error
+ reject(error);
+ });
+ });
+ }
+
+ // Private
+
+ // Create packets
+ static createPackets(commandTag, payload = HardwareWalletBluetoothTransport.NO_PAYLOAD, mtu = HardwareWalletBluetoothTransport.DEFAULT_MTU) {
+
+ // Initialize packets
+ var packets = [];
+
+ // Check if payload doesn't exist
+ if(payload === HardwareWalletBluetoothTransport.NO_PAYLOAD) {
+
+ // Set payload to an empty array
+ payload = new Uint8Array([]);
+ }
+
+ // Initialize payload offset
+ var payloadOffset = 0;
+
+ // Go through all packets required to send the payload
+ for(var i = 0; i === 0 || payloadOffset !== payload["length"]; ++i) {
+
+ // Check if at the first packet
+ if(i === 0) {
+
+ // Create header
+ var header = new Uint8Array([commandTag, i >>> HardwareWalletBluetoothTransport.BITS_IN_A_BYTE, i, payload["length"] >>> HardwareWalletBluetoothTransport.BITS_IN_A_BYTE, payload["length"]]);
+ }
+
+ // Otherwise
+ else {
+
+ // Create header
+ var header = new Uint8Array([commandTag, i >>> HardwareWalletBluetoothTransport.BITS_IN_A_BYTE, i]);
+ }
+
+ // Get payload part length
+ var payloadPartLength = Math.min(payload["length"] - payloadOffset, mtu - header["length"]);
+
+ // Create packet
+ var packet = new Uint8Array(header["length"] + payloadPartLength);
+ packet.set(header);
+ packet.set(payload.subarray(payloadOffset, payloadOffset + payloadPartLength), header["length"]);
+
+ // Append packet to list
+ packets.push(packet);
+
+ // Update payload offset
+ payloadOffset += payloadPartLength;
+ }
+
+ // Return packets
+ return packets;
+ }
+
+ // Send request
+ static sendRequest(connection, writeCharacteristic, notifyCharacteristic, commandTag, payload = HardwareWalletBluetoothTransport.NO_PAYLOAD, mtu = HardwareWalletBluetoothTransport.DEFAULT_MTU) {
+
+ // Return promise
+ return new Promise(function(resolve, reject) {
+
+ // Check if connection is connected
+ if(connection["connected"] === true) {
+
+ // Initialize response
+ var response = new Uint8Array([]);
+
+ // Initialize response size
+ var responseSize;
+
+ // Initialize first response packet
+ var firstResponsePacket = true;
+
+ // Initialize sequence index
+ var lastSequenceIndex;
+
+ // Process response packet
+ var processResponsePacket = function(event) {
+
+ // Get response packet
+ var responsePacket = new Uint8Array(event["target"]["value"]["buffer"]);
+
+ // Check if response packet is too short
+ if((firstResponsePacket === true && responsePacket["length"] < HardwareWalletBluetoothTransport.LEDGER_FIRST_PACKET_HEADER_LENGTH) || (firstResponsePacket === false && responsePacket["length"] <= HardwareWalletBluetoothTransport.LEDGER_NEXT_PACKETS_HEADER_LENGTH)) {
+
+ // Remove GATT server disconnected event
+ connection["device"].removeEventListener("gattserverdisconnected", disconnectedHandler);
+
+ // Remove notify characteristic value changed event
+ notifyCharacteristic.removeEventListener("characteristicvaluechanged", processResponsePacket);
+
+ // Securely clear response packet
+ responsePacket.fill(0);
+
+ // Securely clear response
+ response.fill(0);
+
+ // Check if connection is connected
+ if(connection["connected"] === true) {
+
+ // Reject
+ reject();
+ }
+
+ // Otherwise
+ else {
+
+ // Reject error
+ reject(new DOMException("", "NetworkError"));
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Get tag
+ var tag = responsePacket[0];
+
+ // Check if tag is invalid
+ if(tag !== commandTag) {
+
+ // Remove GATT server disconnected event
+ connection["device"].removeEventListener("gattserverdisconnected", disconnectedHandler);
+
+ // Remove notify characteristic value changed event
+ notifyCharacteristic.removeEventListener("characteristicvaluechanged", processResponsePacket);
+
+ // Securely clear response packet
+ responsePacket.fill(0);
+
+ // Securely clear response
+ response.fill(0);
+
+ // Check if connection is connected
+ if(connection["connected"] === true) {
+
+ // Reject
+ reject();
+ }
+
+ // Otherwise
+ else {
+
+ // Reject error
+ reject(new DOMException("", "NetworkError"));
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Get sequence index
+ var sequenceIndex = (responsePacket[Uint8Array["BYTES_PER_ELEMENT"]] << HardwareWalletBluetoothTransport.BITS_IN_A_BYTE) | responsePacket[Uint8Array["BYTES_PER_ELEMENT"] + 1];
+
+ // Check if first response packet
+ if(firstResponsePacket === true) {
+
+ // Check if sequence index is invalid
+ if(sequenceIndex !== 0) {
+
+ // Remove GATT server disconnected event
+ connection["device"].removeEventListener("gattserverdisconnected", disconnectedHandler);
+
+ // Remove notify characteristic value changed event
+ notifyCharacteristic.removeEventListener("characteristicvaluechanged", processResponsePacket);
+
+ // Securely clear response packet
+ responsePacket.fill(0);
+
+ // Securely clear response
+ response.fill(0);
+
+ // Check if connection is connected
+ if(connection["connected"] === true) {
+
+ // Reject
+ reject();
+ }
+
+ // Otherwise
+ else {
+
+ // Reject error
+ reject(new DOMException("", "NetworkError"));
+ }
+
+ // Return
+ return;
+ }
+
+ // Clear first response packet
+ firstResponsePacket = false;
+
+ // Get response size
+ responseSize = (responsePacket[Uint8Array["BYTES_PER_ELEMENT"] + Uint16Array["BYTES_PER_ELEMENT"]] << HardwareWalletBluetoothTransport.BITS_IN_A_BYTE) | responsePacket[Uint8Array["BYTES_PER_ELEMENT"] + Uint16Array["BYTES_PER_ELEMENT"] + 1];
+
+ // Get response part
+ var responsePart = responsePacket.subarray(HardwareWalletBluetoothTransport.LEDGER_FIRST_PACKET_HEADER_LENGTH);
+ }
+
+ // Otherwise
+ else {
+
+ // Check if sequence index is invalid
+ if(sequenceIndex !== lastSequenceIndex + 1) {
+
+ // Remove GATT server disconnected event
+ connection["device"].removeEventListener("gattserverdisconnected", disconnectedHandler);
+
+ // Remove notify characteristic value changed event
+ notifyCharacteristic.removeEventListener("characteristicvaluechanged", processResponsePacket);
+
+ // Securely clear response packet
+ responsePacket.fill(0);
+
+ // Securely clear response
+ response.fill(0);
+
+ // Check if connection is connected
+ if(connection["connected"] === true) {
+
+ // Reject
+ reject();
+ }
+
+ // Otherwise
+ else {
+
+ // Reject error
+ reject(new DOMException("", "NetworkError"));
+ }
+
+ // Return
+ return;
+ }
+
+ // Get response part
+ var responsePart = responsePacket.subarray(HardwareWalletBluetoothTransport.LEDGER_NEXT_PACKETS_HEADER_LENGTH);
+ }
+
+ // Update last sequence index
+ lastSequenceIndex = sequenceIndex;
+
+ // Append response part to response
+ var currentResponse = new Uint8Array(response["length"] + responsePart["length"]);
+ currentResponse.set(response);
+ currentResponse.set(responsePart, response["length"]);
+ response.fill(0);
+ responsePart.fill(0);
+ response = currentResponse;
+
+ // Check if response is too large
+ if(response["length"] > responseSize) {
+
+ // Remove GATT server disconnected event
+ connection["device"].removeEventListener("gattserverdisconnected", disconnectedHandler);
+
+ // Remove notify characteristic value changed event
+ notifyCharacteristic.removeEventListener("characteristicvaluechanged", processResponsePacket);
+
+ // Securely clear response
+ response.fill(0);
+
+ // Check if connection is connected
+ if(connection["connected"] === true) {
+
+ // Reject
+ reject();
+ }
+
+ // Otherwise
+ else {
+
+ // Reject error
+ reject(new DOMException("", "NetworkError"));
+ }
+ }
+
+ // Otherwise check if entire response has been received
+ else if(response["length"] === responseSize) {
+
+ // Remove GATT server disconnected event
+ connection["device"].removeEventListener("gattserverdisconnected", disconnectedHandler);
+
+ // Remove notify characteristic value changed event
+ notifyCharacteristic.removeEventListener("characteristicvaluechanged", processResponsePacket);
+
+ // Check if connection is connected
+ if(connection["connected"] === true) {
+
+ // Resolve response
+ resolve(response);
+ }
+
+ // Otherwise
+ else {
+
+ // Reject error
+ reject(new DOMException("", "NetworkError"));
+ }
+ }
+ }
+ }
+ };
+
+ // Disconnected handler
+ var disconnectedHandler = function() {
+
+ // Remove GATT server disconnected event
+ connection["device"].removeEventListener("gattserverdisconnected", disconnectedHandler);
+
+ // Remove notify characteristic value changed event
+ notifyCharacteristic.removeEventListener("characteristicvaluechanged", processResponsePacket);
+
+ // Securely clear response
+ response.fill(0);
+
+ // Reject error
+ reject(new DOMException("", "NetworkError"));
+ };
+
+ // Notify characteristic value changed event
+ notifyCharacteristic.addEventListener("characteristicvaluechanged", processResponsePacket);
+
+ // Device GATT server disconnected event
+ connection["device"].addEventListener("gattserverdisconnected", disconnectedHandler);
+
+ // Get packets
+ var packets = HardwareWalletBluetoothTransport.createPackets(commandTag, payload, mtu);
+
+ // Send packet
+ var sendPacket = new Promise(function(resolve, reject) {
+
+ // Check if connection is connected
+ if(connection["connected"] === true) {
+
+ // Resolve
+ resolve();
+ }
+
+ // Otherwise
+ else {
+
+ // Reject error
+ reject(new DOMException("", "NetworkError"));
+ }
+ });
+
+ // Initialize sending packets
+ var sendingPackets = [sendPacket];
+
+ // Go through all packets
+ for(var i = 0; i < packets["length"]; ++i) {
+
+ // Get packet
+ let packet = packets[i];
+
+ // Send next pack after previous packet is send
+ sendPacket = sendPacket.then(function() {
+
+ // Return promise
+ return new Promise(function(resolve, reject) {
+
+ // Check if connection is connected
+ if(connection["connected"] === true) {
+
+ // Return writing packet
+ return writeCharacteristic.writeValueWithResponse(packet).then(function() {
+
+ // Check if connection is connected
+ if(connection["connected"] === true) {
+
+ // Resolve
+ resolve();
+ }
+
+ // Otherwise
+ else {
+
+ // Reject error
+ reject(new DOMException("", "NetworkError"));
+ }
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Check if connection is connected
+ if(connection["connected"] === true) {
+
+ // Reject error
+ reject(error);
+ }
+
+ // Otherwise
+ else {
+
+ // Reject error
+ reject(new DOMException("", "NetworkError"));
+ }
+ });
+ }
+
+ // Otherwise
+ else {
+
+ // Reject error
+ reject(new DOMException("", "NetworkError"));
+ }
+ });
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Return promise
+ return new Promise(function(resolve, reject) {
+
+ // Check if connection is connected
+ if(connection["connected"] === true) {
+
+ // Reject error
+ reject(error);
+ }
+
+ // Otherwise
+ else {
+
+ // Reject error
+ reject(new DOMException("", "NetworkError"));
+ }
+ });
+ });
+
+ // Append sending packet to list
+ sendingPackets.push(sendPacket);
+ }
+
+ // Return sending all packets and catch errors
+ return Promise.all(sendingPackets).catch(function(error) {
+
+ // Remove GATT server disconnected event
+ connection["device"].removeEventListener("gattserverdisconnected", disconnectedHandler);
+
+ // Remove notify characteristic value changed event
+ notifyCharacteristic.removeEventListener("characteristicvaluechanged", processResponsePacket);
+
+ // Securely clear response
+ response.fill(0);
+
+ // Check if connection is connected
+ if(connection["connected"] === true) {
+
+ // Reject error
+ reject(error);
+ }
+
+ // Otherwise
+ else {
+
+ // Reject error
+ reject(new DOMException("", "NetworkError"));
+ }
+ });
+ }
+
+ // Otherwise
+ else {
+
+ // Reject error
+ reject(new DOMException("", "NetworkError"));
+ }
+ });
+ }
+
+ // Devices
+ static get DEVICES() {
+
+ // Return devices
+ return [
+
+ // Ledger Nano X
+ {
+
+ // Product name
+ "Product Name": "Ledger Nano X",
+
+ // Service UUID
+ "Service UUID": "13d63400-2c97-0004-0000-4c6564676572",
+
+ // Notify characteristic UUID
+ "Notify Characteristic UUID": "13d63400-2c97-0004-0001-4c6564676572",
+
+ // Write characteristic UUID
+ "Write Characteristic UUID": "13d63400-2c97-0004-0002-4c6564676572"
+ },
+
+ // Ledger Stax
+ {
+
+ // Product name
+ "Product Name": "Ledger Stax",
+
+ // Service UUID
+ "Service UUID": "13d63400-2c97-6004-0000-4c6564676572",
+
+ // Notify characteristic UUID
+ "Notify Characteristic UUID": "13d63400-2c97-6004-0001-4c6564676572",
+
+ // Write characteristic UUID
+ "Write Characteristic UUID": "13d63400-2c97-6004-0002-4c6564676572"
+ },
+
+ // Ledger Flex
+ {
+
+ // Product name
+ "Product Name": "Ledger Flex",
+
+ // Service UUID
+ "Service UUID": "13d63400-2c97-3004-0000-4c6564676572",
+
+ // Notify characteristic UUID
+ "Notify Characteristic UUID": "13d63400-2c97-3004-0001-4c6564676572",
+
+ // Write characteristic UUID
+ "Write Characteristic UUID": "13d63400-2c97-3004-0002-4c6564676572"
+ }
+ ];
+ }
+
+ // Default MTU
+ static get DEFAULT_MTU() {
+
+ // Return default MTU
+ return 20;
+ }
+
+ // Minimum MTU
+ static get MINIMUM_MTU() {
+
+ // Return minimum MTU
+ return 6;
+ }
+
+ // Maximum MTU
+ static get MAXIMUM_MTU() {
+
+ // Return maximum MTU
+ return 100;
+ }
+
+ // No payload
+ static get NO_PAYLOAD() {
+
+ // Return no payload
+ return null;
+ }
+
+ // Ledger get MTU command tag
+ static get LEDGER_GET_MTU_COMMAND_TAG() {
+
+ // Return Ledger get MTU command tag
+ return 0x08;
+ }
+
+ // Ledger send request command tag
+ static get LEDGER_SEND_REQUEST_COMMAND_TAG() {
+
+ // Return Ledger send request command tag
+ return 0x05;
+ }
+
+ // Message type length
+ static get MESSAGE_TYPE_LENGTH() {
+
+ // Return message type length
+ return Uint16Array["BYTES_PER_ELEMENT"];
+ }
+
+ // No device
+ static get NO_DEVICE() {
+
+ // Return no device
+ return null;
+ }
+
+ // Connect timeout duration milliseconds
+ static get CONNECT_TIMEOUT_DURATION_MILLISECONDS() {
+
+ // Return connect timeout duration milliseconds
+ return 4000;
+ }
+
+ // Bits in a byte
+ static get BITS_IN_A_BYTE() {
+
+ // Rerurn bits in a byte
+ return 8;
+ }
+
+ // Ledger first packet header length
+ static get LEDGER_FIRST_PACKET_HEADER_LENGTH() {
+
+ // Return Ledger first packet header length
+ return 5;
+ }
+
+ // Ledger next packets header length
+ static get LEDGER_NEXT_PACKETS_HEADER_LENGTH() {
+
+ // Return Ledger next packets header length
+ return 3;
+ }
+
+ // Network error code
+ static get NETWORK_ERROR_CODE() {
+
+ // Return network error code
+ return 19;
+ }
+}
+
+
+// Main function
+
+// Set global object's hardware wallet Bluetooth transport
+globalThis["HardwareWalletBluetoothTransport"] = HardwareWalletBluetoothTransport;
diff --git a/scripts/hardware_wallet_definitions.js b/scripts/hardware_wallet_definitions.js
new file mode 100755
index 0000000..0c760eb
--- /dev/null
+++ b/scripts/hardware_wallet_definitions.js
@@ -0,0 +1,2066 @@
+// Use strict
+"use strict";
+
+
+// Classes
+
+// Hardware wallet definitions class
+class HardwareWalletDefinitions {
+
+ // Public
+
+ // Ledger transport type
+ static get LEDGER_TRANSPORT_TYPE() {
+
+ // Return Ledger transport type
+ return 0;
+ }
+
+ // Trezor transport type
+ static get TREZOR_TRANSPORT_TYPE() {
+
+ // Return Trezor type
+ return HardwareWalletDefinitions.LEDGER_TRANSPORT_TYPE + 1;
+ }
+
+ // Ledger get application information message type
+ static get LEDGER_GET_APPLICATION_INFORMATION_MESSAGE_TYPE() {
+
+ // Return Ledger get application information message type
+ return 0xB001;
+ }
+
+ // Ledger device locked message type
+ static get LEDGER_DEVICE_LOCKED_MESSAGE_TYPE() {
+
+ // Return Ledger device locked message type
+ return 0x5515;
+ }
+
+ // Ledger success message type
+ static get LEDGER_SUCCESS_MESSAGE_TYPE() {
+
+ // Return Ledger success message type
+ return 0x9000;
+ }
+
+ // Ledger user rejected message type
+ static get LEDGER_USER_REJECTED_MESSAGE_TYPE() {
+
+ // Return Ledger user rejected message type
+ return 0xB103;
+ }
+
+ // Ledger app locked message type
+ static get LEDGER_APP_LOCKED_MESSAGE_TYPE() {
+
+ // Return Ledger app locked message type
+ return 0xD102;
+ }
+
+ // Trezor initialize message type
+ static get TREZOR_INITIALIZE_MESSAGE_TYPE() {
+
+ // Return Trezor initialize message type
+ return 0x0000;
+ }
+
+ // Trezor success message type
+ static get TREZOR_SUCCESS_MESSAGE_TYPE() {
+
+ // Return Trezor success message type
+ return 0x0002;
+ }
+
+ // Trezor failure message type
+ static get TREZOR_FAILURE_MESSAGE_TYPE() {
+
+ // Return Trezor failure message type
+ return 0x0003;
+ }
+
+ // Trezor Load device message type
+ static get TREZOR_LOAD_DEVICE_MESSAGE_TYPE() {
+
+ // Return Trezor load device message type
+ return 0x000D;
+ }
+
+ // Trezor features message type
+ static get TREZOR_FEATURES_MESSAGE_TYPE() {
+
+ // Return Trezor features message type
+ return 0x0011;
+ }
+
+ // Trezor pin matrix request message type
+ static get TREZOR_PIN_MATRIX_REQUEST_MESSAGE_TYPE() {
+
+ // Return Trezor pin matrix request message type
+ return 0x0012;
+ }
+
+ // Trezor pin matrix acknowledge message type
+ static get TREZOR_PIN_MATRIX_ACKNOWLEDGE_MESSAGE_TYPE() {
+
+ // Return Trezor pin matrix acknowledge message type
+ return 0x0013;
+ }
+
+ // Trezor lock device message type
+ static get TREZOR_LOCK_DEVICE_MESSAGE_TYPE() {
+
+ // Return Trezor lock device message type
+ return 0x0018;
+ }
+
+ // Trezor apply settings message type
+ static get TREZOR_APPLY_SETTINGS_MESSAGE_TYPE() {
+
+ // Return Trezor apply settings message type
+ return 0x0019;
+ }
+
+ // Trezor button request message type
+ static get TREZOR_BUTTON_REQUEST_MESSAGE_TYPE() {
+
+ // Return Trezor button request message type
+ return 0x001A;
+ }
+
+ // Trezor button acknowledge message type
+ static get TREZOR_BUTTON_ACKNOWLEDGE_MESSAGE_TYPE() {
+
+ // Return Trezor button acknowledge message type
+ return 0x001B;
+ }
+
+ // Trezor passphrase request message type
+ static get TREZOR_PASSPHRASE_REQUEST_MESSAGE_TYPE() {
+
+ // Return Trezor passphrase request message type
+ return 0x0029;
+ }
+
+ // Trezor passphrase acknowledge message type
+ static get TREZOR_PASSPHRASE_ACKNOWLEDGE_MESSAGE_TYPE() {
+
+ // Return Trezor passphrase acknowledge message type
+ return 0x002A;
+ }
+
+ // Trezor action canceled failure type
+ static get TREZOR_ACTION_CANCELED_FAILURE_TYPE() {
+
+ // Return Trezor action canceled failure type
+ return 0x04;
+ }
+
+ // Trezor pin canceled failure type
+ static get TREZOR_PIN_CANCELED_FAILURE_TYPE() {
+
+ // Return Trezor pin canceled failure type
+ return 0x06;
+ }
+
+ // Trezor pin invalid failure type
+ static get TREZOR_PIN_INVALID_FAILURE_TYPE() {
+
+ // Return Trezor pin invalid failure type
+ return 0x07;
+ }
+
+ // Trezor passphrase entry button request type
+ static get TREZOR_PASSPHRASE_ENTRY_BUTTON_REQUEST_TYPE() {
+
+ // Return Trezor passphrase entry button request type
+ return 0x13;
+ }
+
+ // Trezor pin entry button request type
+ static get TREZOR_PIN_ENTRY_BUTTON_REQUEST_TYPE() {
+
+ // Return Trezor pin entry button request type
+ return 0x14;
+ }
+
+ // MimbleWimble Coin get root public key message type
+ static get MIMBLEWIMBLE_COIN_GET_ROOT_PUBLIC_KEY_MESSAGE_TYPE() {
+
+ // Return MimbleWimble Coin get root public key message type
+ return 0xC700;
+ }
+
+ // MimbleWimble Coin root public key message type
+ static get MIMBLEWIMBLE_COIN_ROOT_PUBLIC_KEY_MESSAGE_TYPE() {
+
+ // Return MimbleWimble Coin root public key message type
+ return HardwareWalletDefinitions.MIMBLEWIMBLE_COIN_GET_ROOT_PUBLIC_KEY_MESSAGE_TYPE | HardwareWalletDefinitions.MESSAGE_RESPONSE_MASK;
+ }
+
+ // MimbleWimble Coin get address message type
+ static get MIMBLEWIMBLE_COIN_GET_ADDRESS_MESSAGE_TYPE() {
+
+ // Return MimbleWimble Coin get address message type
+ return 0xC701;
+ }
+
+ // MimbleWimble Coin address message type
+ static get MIMBLEWIMBLE_COIN_ADDRESS_MESSAGE_TYPE() {
+
+ // Return MimbleWimble Coin address message type
+ return HardwareWalletDefinitions.MIMBLEWIMBLE_COIN_GET_ADDRESS_MESSAGE_TYPE | HardwareWalletDefinitions.MESSAGE_RESPONSE_MASK;
+ }
+
+ // MimbleWimble Coin get seed cookie message type
+ static get MIMBLEWIMBLE_COIN_GET_SEED_COOKIE_MESSAGE_TYPE() {
+
+ // Return MimbleWimble Coin get seed cookie message type
+ return 0xC702;
+ }
+
+ // MimbleWimble Coin seed cookie message type
+ static get MIMBLEWIMBLE_COIN_SEED_COOKIE_MESSAGE_TYPE() {
+
+ // Return MimbleWimble Coin seed cookie message type
+ return HardwareWalletDefinitions.MIMBLEWIMBLE_COIN_GET_SEED_COOKIE_MESSAGE_TYPE | HardwareWalletDefinitions.MESSAGE_RESPONSE_MASK;
+ }
+
+ // MimbleWimble Coin get commitment message type
+ static get MIMBLEWIMBLE_COIN_GET_COMMITMENT_MESSAGE_TYPE() {
+
+ // Return MimbleWimble Coin get commitment message type
+ return 0xC703;
+ }
+
+ // MimbleWimble Coin commitment message type
+ static get MIMBLEWIMBLE_COIN_COMMITMENT_MESSAGE_TYPE() {
+
+ // Return MimbleWimble Coin commitment message type
+ return HardwareWalletDefinitions.MIMBLEWIMBLE_COIN_GET_COMMITMENT_MESSAGE_TYPE | HardwareWalletDefinitions.MESSAGE_RESPONSE_MASK;
+ }
+
+ // MimbleWimble Coin get bulletproof components message type
+ static get MIMBLEWIMBLE_COIN_GET_BULLETPROOF_COMPONENTS_MESSAGE_TYPE() {
+
+ // Return MimbleWimble Coin get bulletproof components message type
+ return 0xC704;
+ }
+
+ // MimbleWimble Coin bulletproof components message type
+ static get MIMBLEWIMBLE_COIN_BULLETPROOF_COMPONENTS_MESSAGE_TYPE() {
+
+ // Return MimbleWimble Coin bulletproof components message type
+ return HardwareWalletDefinitions.MIMBLEWIMBLE_COIN_GET_BULLETPROOF_COMPONENTS_MESSAGE_TYPE | HardwareWalletDefinitions.MESSAGE_RESPONSE_MASK;
+ }
+
+ // MimbleWimble Coin verify root public key message type
+ static get MIMBLEWIMBLE_COIN_VERIFY_ROOT_PUBLIC_KEY_MESSAGE_TYPE() {
+
+ // Return MimbleWimble Coin verify root public key message type
+ return 0xC705;
+ }
+
+ // MimbleWimble Coin verify address message type
+ static get MIMBLEWIMBLE_COIN_VERIFY_ADDRESS_MESSAGE_TYPE() {
+
+ // Return MimbleWimble Coin verify address message type
+ return 0xC706;
+ }
+
+ // MimbleWimble Coin start encrypting slate message type
+ static get MIMBLEWIMBLE_COIN_START_ENCRYPTING_SLATE_MESSAGE_TYPE() {
+
+ // Return MimbleWimble Coin start encrypting slate message type
+ return 0xC707;
+ }
+
+ // MimbleWimble Coin encrypted slate nonce message type
+ static get MIMBLEWIMBLE_COIN_ENCRYPTED_SLATE_NONCE_AND_SALT_MESSAGE_TYPE() {
+
+ // Return MimbleWimble Coin encrypted slate nonce and salt message type
+ return HardwareWalletDefinitions.MIMBLEWIMBLE_COIN_START_ENCRYPTING_SLATE_MESSAGE_TYPE | HardwareWalletDefinitions.MESSAGE_RESPONSE_MASK;
+ }
+
+ // MimbleWimble Coin continue encrypting slate message type
+ static get MIMBLEWIMBLE_COIN_CONTINUE_ENCRYPTING_SLATE_MESSAGE_TYPE() {
+
+ // Return MimbleWimble Coin continue encrypting slate message type
+ return 0xC708;
+ }
+
+ // MimbleWimble Coin encrypted slate data message type
+ static get MIMBLEWIMBLE_COIN_ENCRYPTED_SLATE_DATA_MESSAGE_TYPE() {
+
+ // Return MimbleWimble Coin encrypted slate data message type
+ return HardwareWalletDefinitions.MIMBLEWIMBLE_COIN_CONTINUE_ENCRYPTING_SLATE_MESSAGE_TYPE | HardwareWalletDefinitions.MESSAGE_RESPONSE_MASK;
+ }
+
+ // MimbleWimble Coin finish encrypting slate message type
+ static get MIMBLEWIMBLE_COIN_FINISH_ENCRYPTING_SLATE_MESSAGE_TYPE() {
+
+ // Return MimbleWimble Coin finish encrypting slate message type
+ return 0xC709;
+ }
+
+ // MimbleWimble Coin encrypted slate tag and signature message type
+ static get MIMBLEWIMBLE_COIN_ENCRYPTED_SLATE_TAG_AND_SIGNATURE_MESSAGE_TYPE() {
+
+ // Return MimbleWimble Coin encrypted slate tag and signature message type
+ return HardwareWalletDefinitions.MIMBLEWIMBLE_COIN_FINISH_ENCRYPTING_SLATE_MESSAGE_TYPE | HardwareWalletDefinitions.MESSAGE_RESPONSE_MASK;
+ }
+
+ // MimbleWimble Coin start decrypting slate message type
+ static get MIMBLEWIMBLE_COIN_START_DECRYPTING_SLATE_MESSAGE_TYPE() {
+
+ // Return MimbleWimble Coin start decrypting slate message type
+ return 0xC70A;
+ }
+
+ // MimbleWimble Coin continue decrypting slate message type
+ static get MIMBLEWIMBLE_COIN_CONTINUE_DECRYPTING_SLATE_MESSAGE_TYPE() {
+
+ // Return MimbleWimble Coin continue decrypting slate message type
+ return 0xC70B;
+ }
+
+ // MimbleWimble Coin decrypted slate data message type
+ static get MIMBLEWIMBLE_COIN_DECRYPTED_SLATE_DATA_MESSAGE_TYPE() {
+
+ // Return MimbleWimble Coin decrypted slate data message type
+ return HardwareWalletDefinitions.MIMBLEWIMBLE_COIN_CONTINUE_DECRYPTING_SLATE_MESSAGE_TYPE | HardwareWalletDefinitions.MESSAGE_RESPONSE_MASK;
+ }
+
+ // MimbleWimble Coin finish decrypting slate message type
+ static get MIMBLEWIMBLE_COIN_FINISH_DECRYPTING_SLATE_MESSAGE_TYPE() {
+
+ // Return MimbleWimble Coin finish decrypting slate message type
+ return 0xC70C;
+ }
+
+ // MimbleWimble Coin decrypted slate AES key message type
+ static get MIMBLEWIMBLE_COIN_DECRYPTED_SLATE_AES_KEY_MESSAGE_TYPE() {
+
+ // Return MimbleWimble Coin decrypted slate AES key message type
+ return HardwareWalletDefinitions.MIMBLEWIMBLE_COIN_FINISH_DECRYPTING_SLATE_MESSAGE_TYPE | HardwareWalletDefinitions.MESSAGE_RESPONSE_MASK;
+ }
+
+ // MimbleWimble Coin start transaction message type
+ static get MIMBLEWIMBLE_COIN_START_TRANSACTION_MESSAGE_TYPE() {
+
+ // Return MimbleWimble Coin start transaction message type
+ return 0xC70D;
+ }
+
+ // MimbleWimble Coin continue transaction include output message type
+ static get MIMBLEWIMBLE_COIN_CONTINUE_TRANSACTION_INCLUDE_OUTPUT_MESSAGE_TYPE() {
+
+ // Return MimbleWimble Coin continue transaction include output message type
+ return 0xC70E;
+ }
+
+ // MimbleWimble Coin continue transaction include input message type
+ static get MIMBLEWIMBLE_COIN_CONTINUE_TRANSACTION_INCLUDE_INPUT_MESSAGE_TYPE() {
+
+ // Return MimbleWimble Coin continue transaction include input message type
+ return 0xC70F;
+ }
+
+ // MimbleWimble Coin continue transaction apply offset message type
+ static get MIMBLEWIMBLE_COIN_CONTINUE_TRANSACTION_APPLY_OFFSET_MESSAGE_TYPE() {
+
+ // Return MimbleWimble Coin continue transaction apply offset message type
+ return 0xC710;
+ }
+
+ // MimbleWimble Coin transaction secret nonce index message type
+ static get MIMBLEWIMBLE_COIN_TRANSACTION_SECRET_NONCE_INDEX_MESSAGE_TYPE() {
+
+ // Return MimbleWimble Coin transaction secret nonce index message type
+ return HardwareWalletDefinitions.MIMBLEWIMBLE_COIN_CONTINUE_TRANSACTION_APPLY_OFFSET_MESSAGE_TYPE | HardwareWalletDefinitions.MESSAGE_RESPONSE_MASK;
+ }
+
+ // MimbleWimble Coin continue transaction get public key message type
+ static get MIMBLEWIMBLE_COIN_CONTINUE_TRANSACTION_GET_PUBLIC_KEY_MESSAGE_TYPE() {
+
+ // Return MimbleWimble Coin continue transaction get public key message type
+ return 0xC711;
+ }
+
+ // MimbleWimble Coin transaction public key message type
+ static get MIMBLEWIMBLE_COIN_TRANSACTION_PUBLIC_KEY_MESSAGE_TYPE() {
+
+ // Return MimbleWimble Coin transaction public key message type
+ return HardwareWalletDefinitions.MIMBLEWIMBLE_COIN_CONTINUE_TRANSACTION_GET_PUBLIC_KEY_MESSAGE_TYPE | HardwareWalletDefinitions.MESSAGE_RESPONSE_MASK;
+ }
+
+ // MimbleWimble Coin continue transaction get public nonce message type
+ static get MIMBLEWIMBLE_COIN_CONTINUE_TRANSACTION_GET_PUBLIC_NONCE_MESSAGE_TYPE() {
+
+ // Return MimbleWimble Coin continue transaction get public nonce message type
+ return 0xC712;
+ }
+
+ // MimbleWimble Coin transaction public nonce message type
+ static get MIMBLEWIMBLE_COIN_TRANSACTION_PUBLIC_NONCE_MESSAGE_TYPE() {
+
+ // Return MimbleWimble Coin transaction public nonce message type
+ return HardwareWalletDefinitions.MIMBLEWIMBLE_COIN_CONTINUE_TRANSACTION_GET_PUBLIC_NONCE_MESSAGE_TYPE | HardwareWalletDefinitions.MESSAGE_RESPONSE_MASK;
+ }
+
+ // MimbleWimble Coin continue transaction get message signature message type
+ static get MIMBLEWIMBLE_COIN_CONTINUE_TRANSACTION_GET_MESSAGE_SIGNATURE_MESSAGE_TYPE() {
+
+ // Return MimbleWimble Coin continue transaction get message signature message type
+ return 0xC713;
+ }
+
+ // MimbleWimble Coin transaction message signature message type
+ static get MIMBLEWIMBLE_COIN_TRANSACTION_MESSAGE_SIGNATURE_MESSAGE_TYPE() {
+
+ // Return MimbleWimble Coin transaction message signature message type
+ return HardwareWalletDefinitions.MIMBLEWIMBLE_COIN_CONTINUE_TRANSACTION_GET_MESSAGE_SIGNATURE_MESSAGE_TYPE | HardwareWalletDefinitions.MESSAGE_RESPONSE_MASK;
+ }
+
+ // MimbleWimble Coin finish transaction message type
+ static get MIMBLEWIMBLE_COIN_FINISH_TRANSACTION_MESSAGE_TYPE() {
+
+ // Return MimbleWimble Coin finish transaction message type
+ return 0xC714;
+ }
+
+ // MimbleWimble Coin transaction signature and payment proof message type
+ static get MIMBLEWIMBLE_COIN_TRANSACTION_SIGNATURE_AND_PAYMENT_PROOF_MESSAGE_TYPE() {
+
+ // Return MimbleWimble Coin transaction signature and payment proof message type
+ return HardwareWalletDefinitions.MIMBLEWIMBLE_COIN_FINISH_TRANSACTION_MESSAGE_TYPE | HardwareWalletDefinitions.MESSAGE_RESPONSE_MASK;
+ }
+
+ // MimbleWimble Coin get MQS challenge signature message type
+ static get MIMBLEWIMBLE_COIN_GET_MQS_CHALLENGE_SIGNATURE_MESSAGE_TYPE() {
+
+ // Return MimbleWimble Coin get MQS challenge signature message type
+ return 0xC715;
+ }
+
+ // MimbleWimble Coin MQS challenge signature message type
+ static get MIMBLEWIMBLE_COIN_MQS_CHALLENGE_SIGNATURE_MESSAGE_TYPE() {
+
+ // Return MimbleWimble Coin MQS challenge signature message type
+ return HardwareWalletDefinitions.MIMBLEWIMBLE_COIN_GET_MQS_CHALLENGE_SIGNATURE_MESSAGE_TYPE | HardwareWalletDefinitions.MESSAGE_RESPONSE_MASK;
+ }
+
+ // MimbleWimble Coin get login challenge signature message type
+ static get MIMBLEWIMBLE_COIN_GET_LOGIN_CHALLENGE_SIGNATURE_MESSAGE_TYPE() {
+
+ // Return MimbleWimble Coin get login challenge signature message type
+ return 0xC716;
+ }
+
+ // MimbleWimble Coin login challenge signature message type
+ static get MIMBLEWIMBLE_COIN_LOGIN_CHALLENGE_SIGNATURE_MESSAGE_TYPE() {
+
+ // Return MimbleWimble Coin login challenge signature message type
+ return HardwareWalletDefinitions.MIMBLEWIMBLE_COIN_GET_LOGIN_CHALLENGE_SIGNATURE_MESSAGE_TYPE | HardwareWalletDefinitions.MESSAGE_RESPONSE_MASK;
+ }
+
+ // Schema
+ static get SCHEMA() {
+
+ // Return schema
+ return {
+
+ // Trezor success
+ [HardwareWalletDefinitions.TREZOR_SUCCESS_MESSAGE_TYPE.toFixed()]: {},
+
+ // Trezor failure
+ [HardwareWalletDefinitions.TREZOR_FAILURE_MESSAGE_TYPE.toFixed()]: {
+
+ // Failure type
+ "1": {
+
+ // Name
+ "Name": "Failure Type",
+
+ // Type
+ "Type": ProtocolBuffers.ENUM_SCHEMA_DATA_TYPE
+ }
+ },
+
+ // Trezor load device
+ [HardwareWalletDefinitions.TREZOR_LOAD_DEVICE_MESSAGE_TYPE.toFixed()]: {
+
+ // Mnemonic
+ "1": {
+
+ // Name
+ "Name": "Mnemonic",
+
+ // Type
+ "Type": ProtocolBuffers.STRING_SCHEMA_DATA_TYPE
+ },
+
+ // Pin
+ "3": {
+
+ // Name
+ "Name": "Pin",
+
+ // Type
+ "Type": ProtocolBuffers.STRING_SCHEMA_DATA_TYPE
+ }
+ },
+
+ // Trezor features
+ [HardwareWalletDefinitions.TREZOR_FEATURES_MESSAGE_TYPE.toFixed()]: {
+
+ // Major version
+ "2": {
+
+ // Name
+ "Name": "Major Version",
+
+ // Type
+ "Type": ProtocolBuffers.UINT_SCHEMA_DATA_TYPE,
+
+ // Size
+ "Size": Common.BYTES_IN_A_UINT32
+ },
+
+ // Minor version
+ "3": {
+
+ // Name
+ "Name": "Minor Version",
+
+ // Type
+ "Type": ProtocolBuffers.UINT_SCHEMA_DATA_TYPE,
+
+ // Size
+ "Size": Common.BYTES_IN_A_UINT32
+ },
+
+ // Patch version
+ "4": {
+
+ // Name
+ "Name": "Patch Version",
+
+ // Type
+ "Type": ProtocolBuffers.UINT_SCHEMA_DATA_TYPE,
+
+ // Size
+ "Size": Common.BYTES_IN_A_UINT32
+ },
+
+ // Pin protection
+ "7": {
+
+ // Name
+ "Name": "Pin Protection",
+
+ // Type
+ "Type": ProtocolBuffers.BOOL_SCHEMA_DATA_TYPE
+ },
+
+ // Passphrase protection
+ "8": {
+
+ // Name
+ "Name": "Passphrase Protection",
+
+ // Type
+ "Type": ProtocolBuffers.BOOL_SCHEMA_DATA_TYPE
+ },
+
+ // Initialized
+ "12": {
+
+ // Name
+ "Name": "Initialized",
+
+ // Type
+ "Type": ProtocolBuffers.BOOL_SCHEMA_DATA_TYPE
+ },
+
+ // Unlocked
+ "16": {
+
+ // Name
+ "Name": "Unlocked",
+
+ // Type
+ "Type": ProtocolBuffers.BOOL_SCHEMA_DATA_TYPE
+ },
+
+ // Model
+ "21": {
+
+ // Name
+ "Name": "Model",
+
+ // Type
+ "Type": ProtocolBuffers.STRING_SCHEMA_DATA_TYPE
+ },
+
+ // Capabilities
+ "30": {
+
+ // Name
+ "Name": "Capabilities",
+
+ // Type
+ "Type": ProtocolBuffers.ENUM_SCHEMA_DATA_TYPE
+ },
+
+ // Passphrase always on device
+ "36": {
+
+ // Name
+ "Name": "Passphrase Always On Device",
+
+ // Type
+ "Type": ProtocolBuffers.BOOL_SCHEMA_DATA_TYPE
+ }
+ },
+
+ // Trezor pin matrix acknowledge
+ [HardwareWalletDefinitions.TREZOR_PIN_MATRIX_ACKNOWLEDGE_MESSAGE_TYPE.toFixed()]: {
+
+ // Pin
+ "1": {
+
+ // Name
+ "Name": "Pin",
+
+ // Type
+ "Type": ProtocolBuffers.STRING_SCHEMA_DATA_TYPE
+ }
+ },
+
+ // Trezor apply settings request
+ [HardwareWalletDefinitions.TREZOR_APPLY_SETTINGS_MESSAGE_TYPE.toFixed()]: {
+
+ // Use passphrase
+ "3": {
+
+ // Name
+ "Name": "Use Passphrase",
+
+ // Type
+ "Type": ProtocolBuffers.BOOL_SCHEMA_DATA_TYPE
+ },
+
+ // Passphrase always on device
+ "8": {
+
+ // Name
+ "Name": "Passphrase Always On Device",
+
+ // Type
+ "Type": ProtocolBuffers.BOOL_SCHEMA_DATA_TYPE
+ }
+ },
+
+ // Trezor button request
+ [HardwareWalletDefinitions.TREZOR_BUTTON_REQUEST_MESSAGE_TYPE.toFixed()]: {
+
+ // Button request type
+ "1": {
+
+ // Name
+ "Name": "Button Request Type",
+
+ // Type
+ "Type": ProtocolBuffers.ENUM_SCHEMA_DATA_TYPE
+ }
+ },
+
+ // Trezor passphrase acknowledge
+ [HardwareWalletDefinitions.TREZOR_PASSPHRASE_ACKNOWLEDGE_MESSAGE_TYPE.toFixed()]: {
+
+ // Passphrase
+ "1": {
+
+ // Name
+ "Name": "Passphrase",
+
+ // Type
+ "Type": ProtocolBuffers.STRING_SCHEMA_DATA_TYPE
+ }
+ },
+
+ // MimbleWimble Coin get root public key
+ [HardwareWalletDefinitions.MIMBLEWIMBLE_COIN_GET_ROOT_PUBLIC_KEY_MESSAGE_TYPE.toFixed()]: {
+
+ // Coin type
+ "1": {
+
+ // Name
+ "Name": "Coin Type",
+
+ // Type
+ "Type": ProtocolBuffers.ENUM_SCHEMA_DATA_TYPE
+ },
+
+ // Network type
+ "2": {
+
+ // Name
+ "Name": "Network Type",
+
+ // Type
+ "Type": ProtocolBuffers.ENUM_SCHEMA_DATA_TYPE
+ },
+
+ // Account
+ "3": {
+
+ // Name
+ "Name": "Account",
+
+ // Type
+ "Type": ProtocolBuffers.UINT_SCHEMA_DATA_TYPE,
+
+ // Size
+ "Size": Common.BYTES_IN_A_UINT32
+ }
+ },
+
+ // MimbleWimble Coin root public key
+ [HardwareWalletDefinitions.MIMBLEWIMBLE_COIN_ROOT_PUBLIC_KEY_MESSAGE_TYPE.toFixed()]: {
+
+ // Root public key
+ "1": {
+
+ // Name
+ "Name": "Root Public Key",
+
+ // Type
+ "Type": ProtocolBuffers.BYTES_SCHEMA_DATA_TYPE
+ }
+ },
+
+ // MimbleWimble Coin get address
+ [HardwareWalletDefinitions.MIMBLEWIMBLE_COIN_GET_ADDRESS_MESSAGE_TYPE.toFixed()]: {
+
+ // Coin type
+ "1": {
+
+ // Name
+ "Name": "Coin Type",
+
+ // Type
+ "Type": ProtocolBuffers.ENUM_SCHEMA_DATA_TYPE
+ },
+
+ // Network type
+ "2": {
+
+ // Name
+ "Name": "Network Type",
+
+ // Type
+ "Type": ProtocolBuffers.ENUM_SCHEMA_DATA_TYPE
+ },
+
+ // Address type
+ "3": {
+
+ // Name
+ "Name": "Parameter One",
+
+ // Type
+ "Type": ProtocolBuffers.ENUM_SCHEMA_DATA_TYPE
+ },
+
+ // Account
+ "4": {
+
+ // Name
+ "Name": "Account",
+
+ // Type
+ "Type": ProtocolBuffers.UINT_SCHEMA_DATA_TYPE,
+
+ // Size
+ "Size": Common.BYTES_IN_A_UINT32
+ },
+
+ // Index
+ "5": {
+
+ // Name
+ "Name": "Index",
+
+ // Type
+ "Type": ProtocolBuffers.UINT_SCHEMA_DATA_TYPE,
+
+ // Size
+ "Size": Common.BYTES_IN_A_UINT32
+ }
+ },
+
+ // MimbleWimble Coin address
+ [HardwareWalletDefinitions.MIMBLEWIMBLE_COIN_ADDRESS_MESSAGE_TYPE.toFixed()]: {
+
+ // Address
+ "1": {
+
+ // Name
+ "Name": "Address",
+
+ // Type
+ "Type": ProtocolBuffers.STRING_SCHEMA_DATA_TYPE
+ }
+ },
+
+ // MimbleWimble Coin get seed cookie
+ [HardwareWalletDefinitions.MIMBLEWIMBLE_COIN_GET_SEED_COOKIE_MESSAGE_TYPE.toFixed()]: {
+
+ // Coin type
+ "1": {
+
+ // Name
+ "Name": "Coin Type",
+
+ // Type
+ "Type": ProtocolBuffers.ENUM_SCHEMA_DATA_TYPE
+ },
+
+ // Network type
+ "2": {
+
+ // Name
+ "Name": "Network Type",
+
+ // Type
+ "Type": ProtocolBuffers.ENUM_SCHEMA_DATA_TYPE
+ },
+
+ // Account
+ "3": {
+
+ // Name
+ "Name": "Account",
+
+ // Type
+ "Type": ProtocolBuffers.UINT_SCHEMA_DATA_TYPE,
+
+ // Size
+ "Size": Common.BYTES_IN_A_UINT32
+ }
+ },
+
+ // MimbleWimble Coin seed cookie
+ [HardwareWalletDefinitions.MIMBLEWIMBLE_COIN_SEED_COOKIE_MESSAGE_TYPE.toFixed()]: {
+
+ // Seed cookie
+ "1": {
+
+ // Name
+ "Name": "Seed Cookie",
+
+ // Type
+ "Type": ProtocolBuffers.BYTES_SCHEMA_DATA_TYPE
+ }
+ },
+
+ // MimbleWimble Coin get commitment
+ [HardwareWalletDefinitions.MIMBLEWIMBLE_COIN_GET_COMMITMENT_MESSAGE_TYPE.toFixed()]: {
+
+ // Coin type
+ "1": {
+
+ // Name
+ "Name": "Coin Type",
+
+ // Type
+ "Type": ProtocolBuffers.ENUM_SCHEMA_DATA_TYPE
+ },
+
+ // Network type
+ "2": {
+
+ // Name
+ "Name": "Network Type",
+
+ // Type
+ "Type": ProtocolBuffers.ENUM_SCHEMA_DATA_TYPE
+ },
+
+ // Account
+ "3": {
+
+ // Name
+ "Name": "Account",
+
+ // Type
+ "Type": ProtocolBuffers.UINT_SCHEMA_DATA_TYPE,
+
+ // Size
+ "Size": Common.BYTES_IN_A_UINT32
+ },
+
+ // Identifier
+ "4": {
+
+ // Name
+ "Name": "Identifier",
+
+ // Type
+ "Type": ProtocolBuffers.BYTES_SCHEMA_DATA_TYPE
+ },
+
+ // Value
+ "5": {
+
+ // Name
+ "Name": "Value",
+
+ // Type
+ "Type": ProtocolBuffers.UINT_SCHEMA_DATA_TYPE,
+
+ // Size
+ "Size": Common.BYTES_IN_A_UINT64
+ },
+
+ // Switch type
+ "6": {
+
+ // Name
+ "Name": "Switch Type",
+
+ // Type
+ "Type": ProtocolBuffers.ENUM_SCHEMA_DATA_TYPE
+ }
+ },
+
+ // MimbleWimble Coin commitment
+ [HardwareWalletDefinitions.MIMBLEWIMBLE_COIN_COMMITMENT_MESSAGE_TYPE.toFixed()]: {
+
+ // Commitment
+ "1": {
+
+ // Name
+ "Name": "Commitment",
+
+ // Type
+ "Type": ProtocolBuffers.BYTES_SCHEMA_DATA_TYPE
+ }
+ },
+
+ // MimbleWimble Coin get bulletproof components
+ [HardwareWalletDefinitions.MIMBLEWIMBLE_COIN_GET_BULLETPROOF_COMPONENTS_MESSAGE_TYPE.toFixed()]: {
+
+ // Coin type
+ "1": {
+
+ // Name
+ "Name": "Coin Type",
+
+ // Type
+ "Type": ProtocolBuffers.ENUM_SCHEMA_DATA_TYPE
+ },
+
+ // Network type
+ "2": {
+
+ // Name
+ "Name": "Network Type",
+
+ // Type
+ "Type": ProtocolBuffers.ENUM_SCHEMA_DATA_TYPE
+ },
+
+ // Message type
+ "3": {
+
+ // Name
+ "Name": "Parameter One",
+
+ // Type
+ "Type": ProtocolBuffers.ENUM_SCHEMA_DATA_TYPE
+ },
+
+ // Account
+ "4": {
+
+ // Name
+ "Name": "Account",
+
+ // Type
+ "Type": ProtocolBuffers.UINT_SCHEMA_DATA_TYPE,
+
+ // Size
+ "Size": Common.BYTES_IN_A_UINT32
+ },
+
+ // Identifier
+ "5": {
+
+ // Name
+ "Name": "Identifier",
+
+ // Type
+ "Type": ProtocolBuffers.BYTES_SCHEMA_DATA_TYPE
+ },
+
+ // Value
+ "6": {
+
+ // Name
+ "Name": "Value",
+
+ // Type
+ "Type": ProtocolBuffers.UINT_SCHEMA_DATA_TYPE,
+
+ // Size
+ "Size": Common.BYTES_IN_A_UINT64
+ },
+
+ // Switch type
+ "7": {
+
+ // Name
+ "Name": "Switch Type",
+
+ // Type
+ "Type": ProtocolBuffers.ENUM_SCHEMA_DATA_TYPE
+ }
+ },
+
+ // MimbleWimble Coin bulletproof components
+ [HardwareWalletDefinitions.MIMBLEWIMBLE_COIN_BULLETPROOF_COMPONENTS_MESSAGE_TYPE.toFixed()]: {
+
+ // Tau x
+ "1": {
+
+ // Name
+ "Name": "Tau X",
+
+ // Type
+ "Type": ProtocolBuffers.BYTES_SCHEMA_DATA_TYPE
+ },
+
+ // T one
+ "2": {
+
+ // Name
+ "Name": "T One",
+
+ // Type
+ "Type": ProtocolBuffers.BYTES_SCHEMA_DATA_TYPE
+ },
+
+ // T two
+ "3": {
+
+ // Name
+ "Name": "T Two",
+
+ // Type
+ "Type": ProtocolBuffers.BYTES_SCHEMA_DATA_TYPE
+ }
+ },
+
+ // MimbleWimble Coin verify root public key
+ [HardwareWalletDefinitions.MIMBLEWIMBLE_COIN_VERIFY_ROOT_PUBLIC_KEY_MESSAGE_TYPE.toFixed()]: {
+
+ // Coin type
+ "1": {
+
+ // Name
+ "Name": "Coin Type",
+
+ // Type
+ "Type": ProtocolBuffers.ENUM_SCHEMA_DATA_TYPE
+ },
+
+ // Network type
+ "2": {
+
+ // Name
+ "Name": "Network Type",
+
+ // Type
+ "Type": ProtocolBuffers.ENUM_SCHEMA_DATA_TYPE
+ },
+
+ // Account
+ "3": {
+
+ // Name
+ "Name": "Account",
+
+ // Type
+ "Type": ProtocolBuffers.UINT_SCHEMA_DATA_TYPE,
+
+ // Size
+ "Size": Common.BYTES_IN_A_UINT32
+ }
+ },
+
+ // MimbleWimble Coin verify address
+ [HardwareWalletDefinitions.MIMBLEWIMBLE_COIN_VERIFY_ADDRESS_MESSAGE_TYPE.toFixed()]: {
+
+ // Coin type
+ "1": {
+
+ // Name
+ "Name": "Coin Type",
+
+ // Type
+ "Type": ProtocolBuffers.ENUM_SCHEMA_DATA_TYPE
+ },
+
+ // Network type
+ "2": {
+
+ // Name
+ "Name": "Network Type",
+
+ // Type
+ "Type": ProtocolBuffers.ENUM_SCHEMA_DATA_TYPE
+ },
+
+ // Address type
+ "3": {
+
+ // Name
+ "Name": "Parameter One",
+
+ // Type
+ "Type": ProtocolBuffers.ENUM_SCHEMA_DATA_TYPE
+ },
+
+ // Account
+ "4": {
+
+ // Name
+ "Name": "Account",
+
+ // Type
+ "Type": ProtocolBuffers.UINT_SCHEMA_DATA_TYPE,
+
+ // Size
+ "Size": Common.BYTES_IN_A_UINT32
+ },
+
+ // Index
+ "5": {
+
+ // Name
+ "Name": "Index",
+
+ // Type
+ "Type": ProtocolBuffers.UINT_SCHEMA_DATA_TYPE,
+
+ // Size
+ "Size": Common.BYTES_IN_A_UINT32
+ }
+ },
+
+ // MimbleWimble Coin start encrypting slate
+ [HardwareWalletDefinitions.MIMBLEWIMBLE_COIN_START_ENCRYPTING_SLATE_MESSAGE_TYPE.toFixed()]: {
+
+ // Coin type
+ "1": {
+
+ // Name
+ "Name": "Coin Type",
+
+ // Type
+ "Type": ProtocolBuffers.ENUM_SCHEMA_DATA_TYPE
+ },
+
+ // Network type
+ "2": {
+
+ // Name
+ "Name": "Network Type",
+
+ // Type
+ "Type": ProtocolBuffers.ENUM_SCHEMA_DATA_TYPE
+ },
+
+ // Account
+ "3": {
+
+ // Name
+ "Name": "Account",
+
+ // Type
+ "Type": ProtocolBuffers.UINT_SCHEMA_DATA_TYPE,
+
+ // Size
+ "Size": Common.BYTES_IN_A_UINT32
+ },
+
+ // Index
+ "4": {
+
+ // Name
+ "Name": "Index",
+
+ // Type
+ "Type": ProtocolBuffers.UINT_SCHEMA_DATA_TYPE,
+
+ // Size
+ "Size": Common.BYTES_IN_A_UINT32
+ },
+
+ // Recipient address
+ "5": {
+
+ // Name
+ "Name": "Recipient Address",
+
+ // Type
+ "Type": ProtocolBuffers.BYTES_SCHEMA_DATA_TYPE
+ }
+ },
+
+ // MimbleWimble Coin encrypted slate nonce
+ [HardwareWalletDefinitions.MIMBLEWIMBLE_COIN_ENCRYPTED_SLATE_NONCE_AND_SALT_MESSAGE_TYPE.toFixed()]: {
+
+ // Nonce
+ "1": {
+
+ // Name
+ "Name": "Nonce",
+
+ // Type
+ "Type": ProtocolBuffers.BYTES_SCHEMA_DATA_TYPE
+ },
+
+ // Salt
+ "2": {
+
+ // Name
+ "Name": "Salt",
+
+ // Type
+ "Type": ProtocolBuffers.BYTES_SCHEMA_DATA_TYPE,
+
+ // Optional
+ "Optional": true
+ }
+ },
+
+ // MimbleWimble Coin continue encrypting slate
+ [HardwareWalletDefinitions.MIMBLEWIMBLE_COIN_CONTINUE_ENCRYPTING_SLATE_MESSAGE_TYPE.toFixed()]: {
+
+ // Data
+ "1": {
+
+ // Name
+ "Name": "Data",
+
+ // Type
+ "Type": ProtocolBuffers.BYTES_SCHEMA_DATA_TYPE
+ }
+ },
+
+ // MimbleWimble Coin encrypted slate data
+ [HardwareWalletDefinitions.MIMBLEWIMBLE_COIN_ENCRYPTED_SLATE_DATA_MESSAGE_TYPE.toFixed()]: {
+
+ // Encrypted data
+ "1": {
+
+ // Name
+ "Name": "Encrypted Data",
+
+ // Type
+ "Type": ProtocolBuffers.BYTES_SCHEMA_DATA_TYPE
+ }
+ },
+
+ // MimbleWimble Coin finish encrypting slate
+ [HardwareWalletDefinitions.MIMBLEWIMBLE_COIN_FINISH_ENCRYPTING_SLATE_MESSAGE_TYPE.toFixed()]: {},
+
+ // MimbleWimble Coin encrypted slate tag and signature
+ [HardwareWalletDefinitions.MIMBLEWIMBLE_COIN_ENCRYPTED_SLATE_TAG_AND_SIGNATURE_MESSAGE_TYPE.toFixed()]: {
+
+ // Tag
+ "1": {
+
+ // Name
+ "Name": "Tag",
+
+ // Type
+ "Type": ProtocolBuffers.BYTES_SCHEMA_DATA_TYPE
+ },
+
+ // MQS message signature
+ "2": {
+
+ // Name
+ "Name": "MQS Message Signature",
+
+ // Type
+ "Type": ProtocolBuffers.BYTES_SCHEMA_DATA_TYPE,
+
+ // Optional
+ "Optional": true
+ }
+ },
+
+ // MimbleWimble Coin start decrypting slate
+ [HardwareWalletDefinitions.MIMBLEWIMBLE_COIN_START_DECRYPTING_SLATE_MESSAGE_TYPE.toFixed()]: {
+
+ // Coin type
+ "1": {
+
+ // Name
+ "Name": "Coin Type",
+
+ // Type
+ "Type": ProtocolBuffers.ENUM_SCHEMA_DATA_TYPE
+ },
+
+ // Network type
+ "2": {
+
+ // Name
+ "Name": "Network Type",
+
+ // Type
+ "Type": ProtocolBuffers.ENUM_SCHEMA_DATA_TYPE
+ },
+
+ // Account
+ "3": {
+
+ // Name
+ "Name": "Account",
+
+ // Type
+ "Type": ProtocolBuffers.UINT_SCHEMA_DATA_TYPE,
+
+ // Size
+ "Size": Common.BYTES_IN_A_UINT32
+ },
+
+ // Index
+ "4": {
+
+ // Name
+ "Name": "Index",
+
+ // Type
+ "Type": ProtocolBuffers.UINT_SCHEMA_DATA_TYPE,
+
+ // Size
+ "Size": Common.BYTES_IN_A_UINT32
+ },
+
+ // Nonce
+ "5": {
+
+ // Name
+ "Name": "Nonce",
+
+ // Type
+ "Type": ProtocolBuffers.BYTES_SCHEMA_DATA_TYPE
+ },
+
+ // Sender address or ephemeral X25519 public key
+ "6": {
+
+ // Name
+ "Name": "Sender Address Or Ephemeral X25519 Public Key",
+
+ // Type
+ "Type": ProtocolBuffers.BYTES_SCHEMA_DATA_TYPE
+ },
+
+ // Salt or encrypted file key
+ "7": {
+
+ // Name
+ "Name": "Salt Or Encrypted File Key",
+
+ // Type
+ "Type": ProtocolBuffers.BYTES_SCHEMA_DATA_TYPE,
+
+ // Optional
+ "Optional": true
+ },
+
+ // Payload nonce
+ "8": {
+
+ // Name
+ "Name": "Payload Nonce",
+
+ // Type
+ "Type": ProtocolBuffers.BYTES_SCHEMA_DATA_TYPE,
+
+ // Optional
+ "Optional": true
+ }
+ },
+
+ // MimbleWimble Coin continue decrypting slate
+ [HardwareWalletDefinitions.MIMBLEWIMBLE_COIN_CONTINUE_DECRYPTING_SLATE_MESSAGE_TYPE.toFixed()]: {
+
+ // Encrypted data
+ "1": {
+
+ // Name
+ "Name": "Encrypted Data",
+
+ // Type
+ "Type": ProtocolBuffers.BYTES_SCHEMA_DATA_TYPE
+ }
+ },
+
+ // MimbleWimble Coin decrypted slate data
+ [HardwareWalletDefinitions.MIMBLEWIMBLE_COIN_DECRYPTED_SLATE_DATA_MESSAGE_TYPE.toFixed()]: {
+
+ // Data
+ "1": {
+
+ // Name
+ "Name": "Data",
+
+ // Type
+ "Type": ProtocolBuffers.BYTES_SCHEMA_DATA_TYPE
+ }
+ },
+
+ // MimbleWimble Coin finish decrypting slate
+ [HardwareWalletDefinitions.MIMBLEWIMBLE_COIN_FINISH_DECRYPTING_SLATE_MESSAGE_TYPE.toFixed()]: {
+
+ // Tag
+ "1": {
+
+ // Name
+ "Name": "Tag",
+
+ // Type
+ "Type": ProtocolBuffers.BYTES_SCHEMA_DATA_TYPE
+ }
+ },
+
+ // MimbleWimble Coin decrypted slate AES key
+ [HardwareWalletDefinitions.MIMBLEWIMBLE_COIN_DECRYPTED_SLATE_AES_KEY_MESSAGE_TYPE.toFixed()]: {
+
+ // AES key
+ "1": {
+
+ // Name
+ "Name": "AES Key",
+
+ // Type
+ "Type": ProtocolBuffers.BYTES_SCHEMA_DATA_TYPE
+ }
+ },
+
+ // MimbleWimble Coin start transaction
+ [HardwareWalletDefinitions.MIMBLEWIMBLE_COIN_START_TRANSACTION_MESSAGE_TYPE.toFixed()]: {
+
+ // Coin type
+ "1": {
+
+ // Name
+ "Name": "Coin Type",
+
+ // Type
+ "Type": ProtocolBuffers.ENUM_SCHEMA_DATA_TYPE
+ },
+
+ // Network type
+ "2": {
+
+ // Name
+ "Name": "Network Type",
+
+ // Type
+ "Type": ProtocolBuffers.ENUM_SCHEMA_DATA_TYPE
+ },
+
+ // Account
+ "3": {
+
+ // Name
+ "Name": "Account",
+
+ // Type
+ "Type": ProtocolBuffers.UINT_SCHEMA_DATA_TYPE,
+
+ // Size
+ "Size": Common.BYTES_IN_A_UINT32
+ },
+
+ // Index
+ "4": {
+
+ // Name
+ "Name": "Index",
+
+ // Type
+ "Type": ProtocolBuffers.UINT_SCHEMA_DATA_TYPE,
+
+ // Size
+ "Size": Common.BYTES_IN_A_UINT32
+ },
+
+ // Output
+ "5": {
+
+ // Name
+ "Name": "Output",
+
+ // Type
+ "Type": ProtocolBuffers.UINT_SCHEMA_DATA_TYPE,
+
+ // Size
+ "Size": Common.BYTES_IN_A_UINT64
+ },
+
+ // Input
+ "6": {
+
+ // Name
+ "Name": "Input",
+
+ // Type
+ "Type": ProtocolBuffers.UINT_SCHEMA_DATA_TYPE,
+
+ // Size
+ "Size": Common.BYTES_IN_A_UINT64
+ },
+
+ // Fee
+ "7": {
+
+ // Name
+ "Name": "Fee",
+
+ // Type
+ "Type": ProtocolBuffers.UINT_SCHEMA_DATA_TYPE,
+
+ // Size
+ "Size": Common.BYTES_IN_A_UINT64
+ },
+
+ // Secret nonce index
+ "8": {
+
+ // Name
+ "Name": "Secret Nonce Index",
+
+ // Type
+ "Type": ProtocolBuffers.ENUM_SCHEMA_DATA_TYPE
+ },
+
+ // Address
+ "9": {
+
+ // Name
+ "Name": "Address",
+
+ // Type
+ "Type": ProtocolBuffers.BYTES_SCHEMA_DATA_TYPE,
+
+ // Optional
+ "Optional": true
+ }
+ },
+
+ // MimbleWimble Coin continue transaction include output
+ [HardwareWalletDefinitions.MIMBLEWIMBLE_COIN_CONTINUE_TRANSACTION_INCLUDE_OUTPUT_MESSAGE_TYPE.toFixed()]: {
+
+ // Identifier
+ "1": {
+
+ // Name
+ "Name": "Identifier",
+
+ // Type
+ "Type": ProtocolBuffers.BYTES_SCHEMA_DATA_TYPE
+ },
+
+ // Value
+ "2": {
+
+ // Name
+ "Name": "Value",
+
+ // Type
+ "Type": ProtocolBuffers.UINT_SCHEMA_DATA_TYPE,
+
+ // Size
+ "Size": Common.BYTES_IN_A_UINT64
+ },
+
+ // Switch type
+ "3": {
+
+ // Name
+ "Name": "Switch Type",
+
+ // Type
+ "Type": ProtocolBuffers.ENUM_SCHEMA_DATA_TYPE
+ }
+ },
+
+ // MimbleWimble Coin continue transaction include input
+ [HardwareWalletDefinitions.MIMBLEWIMBLE_COIN_CONTINUE_TRANSACTION_INCLUDE_INPUT_MESSAGE_TYPE.toFixed()]: {
+
+ // Identifier
+ "1": {
+
+ // Name
+ "Name": "Identifier",
+
+ // Type
+ "Type": ProtocolBuffers.BYTES_SCHEMA_DATA_TYPE
+ },
+
+ // Value
+ "2": {
+
+ // Name
+ "Name": "Value",
+
+ // Type
+ "Type": ProtocolBuffers.UINT_SCHEMA_DATA_TYPE,
+
+ // Size
+ "Size": Common.BYTES_IN_A_UINT64
+ },
+
+ // Switch type
+ "3": {
+
+ // Name
+ "Name": "Switch Type",
+
+ // Type
+ "Type": ProtocolBuffers.ENUM_SCHEMA_DATA_TYPE
+ }
+ },
+
+ // MimbleWimble Coin continue transaction apply offset
+ [HardwareWalletDefinitions.MIMBLEWIMBLE_COIN_CONTINUE_TRANSACTION_APPLY_OFFSET_MESSAGE_TYPE.toFixed()]: {
+
+ // Offset
+ "1": {
+
+ // Name
+ "Name": "Offset",
+
+ // Type
+ "Type": ProtocolBuffers.BYTES_SCHEMA_DATA_TYPE
+ }
+ },
+
+ // MimbleWimble Coin transaction secret nonce index
+ [HardwareWalletDefinitions.MIMBLEWIMBLE_COIN_TRANSACTION_SECRET_NONCE_INDEX_MESSAGE_TYPE.toFixed()]: {
+
+ // Secret nonce index
+ "1": {
+
+ // Name
+ "Name": "Secret Nonce Index",
+
+ // Type
+ "Type": ProtocolBuffers.ENUM_SCHEMA_DATA_TYPE,
+
+ // Optional
+ "Optional": true
+ }
+ },
+
+ // MimbleWimble Coin continue transaction get public key
+ [HardwareWalletDefinitions.MIMBLEWIMBLE_COIN_CONTINUE_TRANSACTION_GET_PUBLIC_KEY_MESSAGE_TYPE.toFixed()]: {},
+
+ // MimbleWimble Coin transaction public key
+ [HardwareWalletDefinitions.MIMBLEWIMBLE_COIN_TRANSACTION_PUBLIC_KEY_MESSAGE_TYPE.toFixed()]: {
+
+ // Public key
+ "1": {
+
+ // Name
+ "Name": "Public Key",
+
+ // Type
+ "Type": ProtocolBuffers.BYTES_SCHEMA_DATA_TYPE
+ }
+ },
+
+ // MimbleWimble Coin continue transaction get public nonce
+ [HardwareWalletDefinitions.MIMBLEWIMBLE_COIN_CONTINUE_TRANSACTION_GET_PUBLIC_NONCE_MESSAGE_TYPE.toFixed()]: {},
+
+ // MimbleWimble Coin transaction public nonce
+ [HardwareWalletDefinitions.MIMBLEWIMBLE_COIN_TRANSACTION_PUBLIC_NONCE_MESSAGE_TYPE.toFixed()]: {
+
+ // Public nonce
+ "1": {
+
+ // Name
+ "Name": "Public Nonce",
+
+ // Type
+ "Type": ProtocolBuffers.BYTES_SCHEMA_DATA_TYPE
+ }
+ },
+
+ // MimbleWimble Coin continue transaction get message signature
+ [HardwareWalletDefinitions.MIMBLEWIMBLE_COIN_CONTINUE_TRANSACTION_GET_MESSAGE_SIGNATURE_MESSAGE_TYPE.toFixed()]: {
+
+ // Message
+ "1": {
+
+ // Name
+ "Name": "Message",
+
+ // Type
+ "Type": ProtocolBuffers.BYTES_SCHEMA_DATA_TYPE
+ }
+ },
+
+ // MimbleWimble Coin transaction message signature
+ [HardwareWalletDefinitions.MIMBLEWIMBLE_COIN_TRANSACTION_MESSAGE_SIGNATURE_MESSAGE_TYPE.toFixed()]: {
+
+ // Message signature
+ "1": {
+
+ // Name
+ "Name": "Message Signature",
+
+ // Type
+ "Type": ProtocolBuffers.BYTES_SCHEMA_DATA_TYPE
+ }
+ },
+
+ // MimbleWimble Coin finish transaction
+ [HardwareWalletDefinitions.MIMBLEWIMBLE_COIN_FINISH_TRANSACTION_MESSAGE_TYPE.toFixed()]: {
+
+ // Address type
+ "1": {
+
+ // Name
+ "Name": "Parameter One",
+
+ // Type
+ "Type": ProtocolBuffers.ENUM_SCHEMA_DATA_TYPE
+ },
+
+ // Public nonce
+ "2": {
+
+ // Name
+ "Name": "Public Nonce",
+
+ // Type
+ "Type": ProtocolBuffers.BYTES_SCHEMA_DATA_TYPE
+ },
+
+ // Public key
+ "3": {
+
+ // Name
+ "Name": "Public Key",
+
+ // Type
+ "Type": ProtocolBuffers.BYTES_SCHEMA_DATA_TYPE
+ },
+
+ // Kernel information
+ "4": {
+
+ // Name
+ "Name": "Kernel Information",
+
+ // Type
+ "Type": ProtocolBuffers.BYTES_SCHEMA_DATA_TYPE
+ },
+
+ // Kernel commitment
+ "5": {
+
+ // Name
+ "Name": "Kernel Commitment",
+
+ // Type
+ "Type": ProtocolBuffers.BYTES_SCHEMA_DATA_TYPE,
+
+ // Optional
+ "Optional": true
+ },
+
+ // Payment proof
+ "6": {
+
+ // Name
+ "Name": "Payment Proof",
+
+ // Type
+ "Type": ProtocolBuffers.BYTES_SCHEMA_DATA_TYPE,
+
+ // Optional
+ "Optional": true
+ }
+ },
+
+ // MimbleWimble Coin transaction signature and payment proof
+ [HardwareWalletDefinitions.MIMBLEWIMBLE_COIN_TRANSACTION_SIGNATURE_AND_PAYMENT_PROOF_MESSAGE_TYPE.toFixed()]: {
+
+ // Signature
+ "1": {
+
+ // Name
+ "Name": "Signature",
+
+ // Type
+ "Type": ProtocolBuffers.BYTES_SCHEMA_DATA_TYPE
+ },
+
+ // Payment proof
+ "2": {
+
+ // Name
+ "Name": "Payment Proof",
+
+ // Type
+ "Type": ProtocolBuffers.BYTES_SCHEMA_DATA_TYPE,
+
+ // Optional
+ "Optional": true
+ }
+ },
+
+ // MimbleWimble Coin get MQS challenge signature
+ [HardwareWalletDefinitions.MIMBLEWIMBLE_COIN_GET_MQS_CHALLENGE_SIGNATURE_MESSAGE_TYPE.toFixed()]: {
+
+ // Coin type
+ "1": {
+
+ // Name
+ "Name": "Coin Type",
+
+ // Type
+ "Type": ProtocolBuffers.ENUM_SCHEMA_DATA_TYPE
+ },
+
+ // Network type
+ "2": {
+
+ // Name
+ "Name": "Network Type",
+
+ // Type
+ "Type": ProtocolBuffers.ENUM_SCHEMA_DATA_TYPE
+ },
+
+ // Account
+ "3": {
+
+ // Name
+ "Name": "Account",
+
+ // Type
+ "Type": ProtocolBuffers.UINT_SCHEMA_DATA_TYPE,
+
+ // Size
+ "Size": Common.BYTES_IN_A_UINT32
+ },
+
+ // Index
+ "4": {
+
+ // Name
+ "Name": "Index",
+
+ // Type
+ "Type": ProtocolBuffers.UINT_SCHEMA_DATA_TYPE,
+
+ // Size
+ "Size": Common.BYTES_IN_A_UINT32
+ },
+
+ // Timestamp
+ "5": {
+
+ // Name
+ "Name": "Timestamp",
+
+ // Type
+ "Type": ProtocolBuffers.UINT_SCHEMA_DATA_TYPE,
+
+ // Size
+ "Size": Common.BYTES_IN_A_UINT64,
+
+ // Optional
+ "Optional": true
+ },
+
+ // Time zone offset
+ "6": {
+
+ // Name
+ "Name": "Time Zone Offset",
+
+ // Type
+ "Type": ProtocolBuffers.SINT_SCHEMA_DATA_TYPE,
+
+ // Size
+ "Size": Common.BYTES_IN_A_UINT16,
+
+ // Optional
+ "Optional": true
+ }
+ },
+
+ // MimbleWimble Coin MQS challenge signature
+ [HardwareWalletDefinitions.MIMBLEWIMBLE_COIN_MQS_CHALLENGE_SIGNATURE_MESSAGE_TYPE.toFixed()]: {
+
+ // MQS challenge signature
+ "1": {
+
+ // Name
+ "Name": "MQS Challenge Signature",
+
+ // Type
+ "Type": ProtocolBuffers.BYTES_SCHEMA_DATA_TYPE
+ }
+ },
+
+ // MimbleWimble Coin get login challenge signature
+ [HardwareWalletDefinitions.MIMBLEWIMBLE_COIN_GET_LOGIN_CHALLENGE_SIGNATURE_MESSAGE_TYPE.toFixed()]: {
+
+ // Coin type
+ "1": {
+
+ // Name
+ "Name": "Coin Type",
+
+ // Type
+ "Type": ProtocolBuffers.ENUM_SCHEMA_DATA_TYPE
+ },
+
+ // Network type
+ "2": {
+
+ // Name
+ "Name": "Network Type",
+
+ // Type
+ "Type": ProtocolBuffers.ENUM_SCHEMA_DATA_TYPE
+ },
+
+ // Account
+ "3": {
+
+ // Name
+ "Name": "Account",
+
+ // Type
+ "Type": ProtocolBuffers.UINT_SCHEMA_DATA_TYPE,
+
+ // Size
+ "Size": Common.BYTES_IN_A_UINT32
+ },
+
+ // Timestamp
+ "4": {
+
+ // Name
+ "Name": "Timestamp",
+
+ // Type
+ "Type": ProtocolBuffers.UINT_SCHEMA_DATA_TYPE,
+
+ // Size
+ "Size": Common.BYTES_IN_A_UINT64
+ },
+
+ // Time zone offset
+ "5": {
+
+ // Name
+ "Name": "Time Zone Offset",
+
+ // Type
+ "Type": ProtocolBuffers.SINT_SCHEMA_DATA_TYPE,
+
+ // Size
+ "Size": Common.BYTES_IN_A_UINT16
+ },
+
+ // Identifier
+ "6": {
+
+ // Name
+ "Name": "Identifier",
+
+ // Type
+ "Type": ProtocolBuffers.BYTES_SCHEMA_DATA_TYPE
+ }
+ },
+
+ // MimbleWimble Coin login challenge signature
+ [HardwareWalletDefinitions.MIMBLEWIMBLE_COIN_LOGIN_CHALLENGE_SIGNATURE_MESSAGE_TYPE.toFixed()]: {
+
+ // Login public key
+ "1": {
+
+ // Name
+ "Name": "Login Public Key",
+
+ // Type
+ "Type": ProtocolBuffers.BYTES_SCHEMA_DATA_TYPE
+ },
+
+ // Login challenge signature
+ "2": {
+
+ // Name
+ "Name": "Login Challenge Signature",
+
+ // Type
+ "Type": ProtocolBuffers.BYTES_SCHEMA_DATA_TYPE
+ }
+ }
+ };
+ }
+
+ // Ledger ignore field names
+ static get LEDGER_IGNORE_FIELD_NAMES() {
+
+ // Return Ledger ignore field names
+ return [
+
+ // Coin type
+ "Coin Type",
+
+ // Network type
+ "Network Type",
+
+ // Parameter one
+ "Parameter One",
+
+ // Parameter Two
+ "Parameter Two"
+ ];
+ }
+
+ // Private
+
+ // Message response mask
+ static get MESSAGE_RESPONSE_MASK() {
+
+ // Return message response mask
+ return 0x80;
+ }
+}
+
+
+// Main function
+
+// Set global object's hardware wallet definitions
+globalThis["HardwareWalletDefinitions"] = HardwareWalletDefinitions;
diff --git a/scripts/hardware_wallet_usb_transport.js b/scripts/hardware_wallet_usb_transport.js
new file mode 100755
index 0000000..3f5f30e
--- /dev/null
+++ b/scripts/hardware_wallet_usb_transport.js
@@ -0,0 +1,1163 @@
+// Use strict
+"use strict";
+
+
+// Classes
+
+// HardwareWallet USB transport class
+class HardwareWalletUsbTransport {
+
+ // Public
+
+ // Constructor
+ constructor(device, interfaceNumber) {
+
+ // Set device
+ this.device = device;
+
+ // Set interface number
+ this.interfaceNumber = interfaceNumber;
+
+ // Set allow disconnect event to true
+ this.allowDisconnectEvent = true;
+
+ // Set product name
+ var productName = device["manufacturerName"] + " " + device["productName"];
+
+ // Go through all devices
+ for(var i = 0; i < HardwareWalletUsbTransport.DEVICES["length"]; ++i) {
+
+ // Check if device's vendor ID matches the device's
+ if(device["vendorId"] === HardwareWalletUsbTransport.DEVICES[i]["Vendor ID"]) {
+
+ // Set type to device's type
+ this.type = HardwareWalletUsbTransport.DEVICES[i]["Type"];
+
+ // Check if device's product ID matches the device's
+ if("Product ID" in HardwareWalletUsbTransport.DEVICES[i] === false || device["productId"] === HardwareWalletUsbTransport.DEVICES[i]["Product ID"]) {
+
+ // Check if device has a product name
+ if("Product Name" in HardwareWalletUsbTransport.DEVICES[i] === true) {
+
+ // Set product name
+ productName = HardwareWalletUsbTransport.DEVICES[i]["Product Name"];
+ }
+
+ // Break
+ break;
+ }
+ }
+ }
+
+ // Set device model
+ this["deviceModel"] = {
+
+ // Product name
+ "productName": productName
+ };
+ }
+
+ // On
+ on(event, callback) {
+
+ // Check event
+ switch(event) {
+
+ // Disconnect
+ case "disconnect":
+
+ // Set self
+ var self = this;
+
+ // Create callback once
+ var callbackOnce = function(event) {
+
+ // Check if device was disconnected
+ if(event["device"] === self.device) {
+
+ // Remove USB disconnect event
+ navigator["usb"].removeEventListener("disconnect", callbackOnce);
+
+ // Check if disconnect event is allowed
+ if(self.allowDisconnectEvent === true) {
+
+ // Call callback
+ callback();
+ }
+ }
+ };
+
+ // USB disconnect event
+ navigator["usb"].addEventListener("disconnect", callbackOnce);
+
+ // Return callback once
+ return callbackOnce;
+ }
+ }
+
+ // Off
+ off(event, callback) {
+
+ // Check event
+ switch(event) {
+
+ // Disconnect
+ case "disconnect":
+
+ // Remove USB disconnect event
+ navigator["usb"].removeEventListener("disconnect", callback);
+
+ // Break
+ break;
+ }
+ }
+
+ // Close
+ close() {
+
+ // Clear allow disconnect event
+ this.allowDisconnectEvent = false;
+
+ // Set self
+ var self = this;
+
+ // Return promise
+ return new Promise(function(resolve, reject) {
+
+ // Return releasing device interface and catch errors
+ return self.device.releaseInterface(self.interfaceNumber).catch(function(error) {
+
+ // Finally
+ }).finally(function() {
+
+ // Return resetting device and catch errors
+ return self.device.reset().catch(function(error) {
+
+ // Finally
+ }).finally(function() {
+
+ // Return closing device and catch errors
+ return self.device.close().then(function() {
+
+ // Resolve
+ resolve();
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Reject error
+ reject(error);
+ });
+ });
+ });
+ });
+ }
+
+ // Send
+ send(messageType, parameterOne, parameterTwo, data) {
+
+ // Set self
+ var self = this;
+
+ // Return promise
+ return new Promise(function(resolve, reject) {
+
+ // Check type
+ switch(self.type) {
+
+ // Ledger type
+ case HardwareWalletDefinitions.LEDGER_TRANSPORT_TYPE:
+
+ // Create header
+ var header = new Uint8Array([messageType >>> HardwareWalletUsbTransport.BITS_IN_A_BYTE, messageType, parameterOne, parameterTwo, data["length"]]);
+
+ // Break
+ break;
+
+ // Trezor type
+ case HardwareWalletDefinitions.TREZOR_TRANSPORT_TYPE:
+
+ // Create header
+ var header = new Uint8Array([messageType >>> HardwareWalletUsbTransport.BITS_IN_A_BYTE, messageType, data["length"] >>> (HardwareWalletUsbTransport.BITS_IN_A_BYTE * 3), data["length"] >>> (HardwareWalletUsbTransport.BITS_IN_A_BYTE * 2), data["length"] >>> HardwareWalletUsbTransport.BITS_IN_A_BYTE, data["length"]]);
+
+ // Break
+ break;
+ }
+
+ // Create message
+ var message = new Uint8Array(header["length"] + data["length"]);
+ message.set(header);
+ message.set(data, header["length"]);
+
+ // Return sending message to the device
+ return HardwareWalletUsbTransport.sendRequest(self.device, self.type, message).then(function(response) {
+
+ // Check if response contains a message type
+ if(response["length"] >= HardwareWalletUsbTransport.MESSAGE_TYPE_LENGTH) {
+
+ // Get message type
+ var messageType = (response[response["length"] - HardwareWalletUsbTransport.MESSAGE_TYPE_LENGTH] << HardwareWalletUsbTransport.BITS_IN_A_BYTE) | response[response["length"] - (HardwareWalletUsbTransport.MESSAGE_TYPE_LENGTH - 1)];
+
+ // Resolve
+ resolve({
+
+ // Message type
+ "Message Type": messageType,
+
+ // Data
+ "Data": response.subarray(0, response["length"] - HardwareWalletUsbTransport.MESSAGE_TYPE_LENGTH)
+ });
+ }
+
+ // Otherwise
+ else {
+
+ // Securely clear response
+ response.fill(0);
+
+ // Reject
+ reject();
+ }
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Check if error is that the device was disconnected
+ if(typeof error === "object" && error !== null && (("code" in error === true && error["code"] === HardwareWalletUsbTransport.NOT_FOUND_ERROR_CODE) || ("name" in error === true && error["name"] === "NotFoundError"))) {
+
+ // Reject error
+ reject(new DOMException("", "NetworkError"));
+ }
+
+ // Otherwise
+ else {
+
+ // Reject error
+ reject(error);
+ }
+ });
+ });
+ }
+
+ // Request
+ static request(device = HardwareWalletUsbTransport.NO_DEVICE) {
+
+ // Return promise
+ return new Promise(function(resolve, reject) {
+
+ // Get device
+ var getDevice = function() {
+
+ // Return promise
+ return new Promise(function(resolve, reject) {
+
+ // Check if no device was provided
+ if(device === HardwareWalletUsbTransport.NO_DEVICE) {
+
+ // Return requesting USB device
+ return navigator["usb"].requestDevice({
+
+ // Filters
+ "filters": HardwareWalletUsbTransport.DEVICES.map(function(device) {
+
+ // Check if device has a product ID
+ if("Product ID" in device === true) {
+
+ // Return device's vendor ID and product ID
+ return {
+
+ // Vendor ID
+ "vendorId": device["Vendor ID"],
+
+ // Product ID
+ "productId": device["Product ID"]
+ };
+ }
+
+ // Otherwise
+ else {
+
+ // Return device's vendor ID
+ return {
+
+ // Vendor ID
+ "vendorId": device["Vendor ID"]
+ };
+ }
+ })
+
+ }).then(function(device) {
+
+ // Check if device isn't opened
+ if(device["opened"] === false) {
+
+ // Resolve device
+ resolve(device);
+ }
+
+ // Otherwise
+ else {
+
+ // Reject error
+ reject(new DOMException("", "InvalidStateError"));
+ }
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Reject error
+ reject(error);
+ });
+ }
+
+ // Otherwise
+ else {
+
+ // Initialize device applicable
+ var deviceApplicable = false;
+
+ // Go through all devices
+ for(var i = 0; i < HardwareWalletUsbTransport.DEVICES["length"]; ++i) {
+
+ // Check if device's vendor ID and product ID match the device's
+ if(device["vendorId"] === HardwareWalletUsbTransport.DEVICES[i]["Vendor ID"] && ("Product ID" in HardwareWalletUsbTransport.DEVICES[i] === false || device["productId"] === HardwareWalletUsbTransport.DEVICES[i]["Product ID"])) {
+
+ // Set device applicable
+ deviceApplicable = true;
+
+ // Break
+ break;
+ }
+ }
+
+ // Check if device is applicable
+ if(deviceApplicable === true) {
+
+ // Check if device isn't opened
+ if(device["opened"] === false) {
+
+ // Resolve device
+ resolve(device);
+ }
+
+ // Otherwise
+ else {
+
+ // Reject error
+ reject(new DOMException("", "InvalidStateError"));
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Reject
+ reject();
+ }
+ }
+ });
+ };
+
+ // Return getting device
+ return getDevice().then(function(device) {
+
+ // Return opening device
+ return device.open().then(function() {
+
+ // Return selecting device's configuration
+ return device.selectConfiguration(HardwareWalletUsbTransport.CONFIGURATION).then(function() {
+
+ // Return resetting device and catch errors
+ return device.reset().catch(function(error) {
+
+ // Finally
+ }).finally(function() {
+
+ // Initialize interface found
+ var interfaceFound = false;
+
+ // Go through all the configuration's interfaces
+ for(var i = 0; i < device["configurations"][0]["interfaces"]["length"] && interfaceFound === false; ++i) {
+
+ // Go through all of the interface's alternates
+ for(var j = 0; j < device["configurations"][0]["interfaces"][i]["alternates"]["length"]; ++j) {
+
+ // Check if alternates is for WebUSB
+ if(device["configurations"][0]["interfaces"][i]["alternates"][j]["interfaceClass"] === HardwareWalletUsbTransport.WEBUSB_INTERFACE_CLASS) {
+
+ // Set interface found
+ interfaceFound = true;
+
+ // Set interface number
+ var interfaceNumber = device["configurations"][0]["interfaces"][i]["interfaceNumber"];
+
+ // Break
+ break;
+ }
+ }
+ }
+
+ // Check if interface was found
+ if(interfaceFound === true) {
+
+ // Return claiming interface
+ return device.claimInterface(interfaceNumber).then(function() {
+
+ // Create transport for the device
+ var transport = new HardwareWalletUsbTransport(device, interfaceNumber);
+
+ // Resolve transport
+ resolve(transport);
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Return closing device and catch errors
+ return device.close().catch(function(error) {
+
+ // Finally
+ }).finally(function() {
+
+ // Reject error
+ reject(error);
+ });
+ });
+ }
+
+ // Otherwise
+ else {
+
+ // Return closing device and catch errors
+ return device.close().catch(function(error) {
+
+ // Finally
+ }).finally(function() {
+
+ // Reject
+ reject();
+ });
+ }
+ });
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Return closing device and catch errors
+ return device.close().catch(function(error) {
+
+ // Finally
+ }).finally(function() {
+
+ // Reject error
+ reject(error);
+ });
+ });
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Reject error
+ reject(error);
+ });
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Reject error
+ reject(error);
+ });
+ });
+ }
+
+ // List
+ static list() {
+
+ // Return promise
+ return new Promise(function(resolve, reject) {
+
+ // Return getting attached USB devices
+ return navigator["usb"].getDevices().then(function(devices) {
+
+ // Initialize applicable devices
+ var applicableDevices = [];
+
+ // Go through all devices
+ for(var i = 0; i < devices["length"]; ++i) {
+
+ // Check if device isn't opened
+ if(devices[i]["opened"] === false) {
+
+ // Initialize device applicable
+ var deviceApplicable = false;
+
+ // Go through all devices
+ for(var j = 0; j < HardwareWalletUsbTransport.DEVICES["length"]; ++j) {
+
+ // Check if device's vendor ID and product ID match the device's
+ if(devices[i]["vendorId"] === HardwareWalletUsbTransport.DEVICES[j]["Vendor ID"] && ("Product ID" in HardwareWalletUsbTransport.DEVICES[j] === false || devices[i]["productId"] === HardwareWalletUsbTransport.DEVICES[j]["Product ID"])) {
+
+ // Set device aapplicable
+ deviceApplicable = true;
+
+ // Break
+ break;
+ }
+ }
+
+ // Check if device is applicable
+ if(deviceApplicable === true) {
+
+ // Append device to list
+ applicableDevices.push(devices[i]);
+ }
+ }
+ }
+
+ // Resolve applicable devices
+ resolve(applicableDevices);
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Reject error
+ reject(error);
+ });
+ });
+ }
+
+ // Private
+
+ // Create packets
+ static createPackets(channel, type, payload = HardwareWalletUsbTransport.NO_PAYLOAD) {
+
+ // Initialize packets
+ var packets = [];
+
+ // Check if payload doesn't exist
+ if(payload === HardwareWalletUsbTransport.NO_PAYLOAD) {
+
+ // Set payload to an empty array
+ payload = new Uint8Array([]);
+ }
+
+ // Check type
+ switch(type) {
+
+ // Ledger type
+ case HardwareWalletDefinitions.LEDGER_TRANSPORT_TYPE:
+
+ // Create padded payload
+ var numberOfPackets = Math.ceil((HardwareWalletUsbTransport.MESSAGE_TYPE_LENGTH + payload["length"]) / (HardwareWalletUsbTransport.PACKET_SIZE - HardwareWalletUsbTransport.LEDGER_PACKET_HEADER_LENGTH));
+ var paddedPayload = (new Uint8Array(numberOfPackets * (HardwareWalletUsbTransport.PACKET_SIZE - HardwareWalletUsbTransport.LEDGER_PACKET_HEADER_LENGTH))).fill(0);
+ paddedPayload.set(new Uint8Array([payload["length"] >>> HardwareWalletUsbTransport.BITS_IN_A_BYTE, payload["length"]]));
+ paddedPayload.set(payload, Uint16Array["BYTES_PER_ELEMENT"]);
+
+ // Break
+ break;
+
+ // Trezor type
+ case HardwareWalletDefinitions.TREZOR_TRANSPORT_TYPE:
+
+ // Check if more than one packet will be used
+ if(payload["length"] > HardwareWalletUsbTransport.PACKET_SIZE - HardwareWalletUsbTransport.TREZOR_FIRST_PACKET_HEADER["length"]) {
+
+ // Create padded payload
+ var numberOfPackets = Math.ceil((payload["length"] - (HardwareWalletUsbTransport.PACKET_SIZE - HardwareWalletUsbTransport.TREZOR_FIRST_PACKET_HEADER["length"])) / (HardwareWalletUsbTransport.PACKET_SIZE - HardwareWalletUsbTransport.TREZOR_NEXT_PACKETS_HEADER["length"]));
+ var paddedPayload = (new Uint8Array(HardwareWalletUsbTransport.PACKET_SIZE - HardwareWalletUsbTransport.TREZOR_FIRST_PACKET_HEADER["length"] + numberOfPackets * (HardwareWalletUsbTransport.PACKET_SIZE - HardwareWalletUsbTransport.TREZOR_NEXT_PACKETS_HEADER["length"]))).fill(0);
+ paddedPayload.set(payload);
+ }
+
+ // Otherwise
+ else {
+
+ // Create padded payload
+ var paddedPayload = (new Uint8Array(HardwareWalletUsbTransport.PACKET_SIZE - HardwareWalletUsbTransport.TREZOR_FIRST_PACKET_HEADER["length"])).fill(0);
+ paddedPayload.set(payload);
+ }
+
+ // Break
+ break;
+ }
+
+ // Set payload to padded payload
+ payload = paddedPayload;
+
+ // Initialize payload offset
+ var payloadOffset = 0;
+
+ // Go through all packets required to send the payload
+ for(var i = 0; payloadOffset !== payload["length"]; ++i) {
+
+ // Check type
+ switch(type) {
+
+ // Ledger type
+ case HardwareWalletDefinitions.LEDGER_TRANSPORT_TYPE:
+
+ // Create header
+ var header = new Uint8Array([channel >>> HardwareWalletUsbTransport.BITS_IN_A_BYTE, channel, HardwareWalletUsbTransport.LEDGER_PACKET_HEADER_TAG, i >>> HardwareWalletUsbTransport.BITS_IN_A_BYTE, i]);
+
+ // Break
+ break;
+
+ // Trezor type
+ case HardwareWalletDefinitions.TREZOR_TRANSPORT_TYPE:
+
+ // Check if at the first packet
+ if(i === 0) {
+
+ // Create header
+ var header = HardwareWalletUsbTransport.TREZOR_FIRST_PACKET_HEADER;
+ }
+
+ // Otherwise
+ else {
+
+ // Create header
+ var header = HardwareWalletUsbTransport.TREZOR_NEXT_PACKETS_HEADER;
+ }
+
+ // Break
+ break;
+ }
+
+ // Get payload part length
+ var payloadPartLength = HardwareWalletUsbTransport.PACKET_SIZE - header["length"];
+
+ // Create packet
+ var packet = new Uint8Array(header["length"] + payloadPartLength);
+ packet.set(header);
+ packet.set(payload.subarray(payloadOffset, payloadOffset + payloadPartLength), header["length"]);
+
+ // Append packet to list
+ packets.push(packet);
+
+ // Update payload offset
+ payloadOffset += payloadPartLength;
+ }
+
+ // Return packets
+ return packets;
+ }
+
+ // Send request
+ static sendRequest(device, type, payload = HardwareWalletUsbTransport.NO_PAYLOAD) {
+
+ // Return promise
+ return new Promise(function(resolve, reject) {
+
+ // Create random channel
+ var channel = Math.floor(Math.random() * HardwareWalletUsbTransport.MAX_CHANNEL);
+
+ // Get packets
+ var packets = HardwareWalletUsbTransport.createPackets(channel, type, payload);
+
+ // Check type
+ switch(type) {
+
+ // Ledger type
+ case HardwareWalletDefinitions.LEDGER_TRANSPORT_TYPE:
+
+ // Set endpoint
+ var endpoint = HardwareWalletUsbTransport.LEDGER_ENDPOINT;
+
+ // Break
+ break;
+
+ // Trezor type
+ case HardwareWalletDefinitions.TREZOR_TRANSPORT_TYPE:
+
+ // Set endpoint
+ var endpoint = HardwareWalletUsbTransport.TREZOR_ENDPOINT;
+
+ // Break
+ break;
+ }
+
+ // Send packet
+ var sendPacket = new Promise(function(resolve, reject) {
+
+ // Resolve
+ resolve();
+ });
+
+ // Initialize sending packets
+ var sendingPackets = [sendPacket];
+
+ // Go through all packets
+ for(var i = 0; i < packets["length"]; ++i) {
+
+ // Get packet
+ let packet = packets[i];
+
+ // Send next pack after previous packet is send
+ sendPacket = sendPacket.then(function() {
+
+ // Return promise
+ return new Promise(function(resolve, reject) {
+
+ // Return transfering out packet
+ return device.transferOut(endpoint, packet).then(function() {
+
+ // Resolve
+ resolve();
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Reject error
+ reject(error);
+ });
+ });
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Return promise
+ return new Promise(function(resolve, reject) {
+
+ // Reject error
+ reject(error);
+ });
+ });
+
+ // Append sending packet to list
+ sendingPackets.push(sendPacket);
+ }
+
+ // Return sending all packets
+ return Promise.all(sendingPackets).then(function() {
+
+ // Receive packet
+ var receivePacket = function(expectedSequenceIndex) {
+
+ // Return promise
+ return new Promise(function(resolve, reject) {
+
+ // Return transfering in packet
+ return device.transferIn(endpoint, HardwareWalletUsbTransport.PACKET_SIZE).then(function(response) {
+
+ // Get packet from response
+ var packet = new Uint8Array(response["data"]["buffer"]);
+
+ // Check if packet's size is correct
+ if(packet["length"] === HardwareWalletUsbTransport.PACKET_SIZE) {
+
+ // Check type
+ switch(type) {
+
+ // Ledger type
+ case HardwareWalletDefinitions.LEDGER_TRANSPORT_TYPE:
+
+ // Get response channel
+ var responseChannel = (packet[0] << HardwareWalletUsbTransport.BITS_IN_A_BYTE) | packet[1];
+
+ // Check if response channel is correct
+ if(responseChannel === channel) {
+
+ // Check if tag is correct
+ if(packet[Uint16Array["BYTES_PER_ELEMENT"]] === HardwareWalletUsbTransport.LEDGER_PACKET_HEADER_TAG) {
+
+ // Get sequence index
+ var sequenceIndex = (packet[Uint16Array["BYTES_PER_ELEMENT"] + Uint8Array["BYTES_PER_ELEMENT"]] << HardwareWalletUsbTransport.BITS_IN_A_BYTE) | packet[Uint16Array["BYTES_PER_ELEMENT"] + Uint8Array["BYTES_PER_ELEMENT"] + 1];
+
+ // Check if sequence index is correct
+ if(sequenceIndex === expectedSequenceIndex) {
+
+ // Resolve packet's payload
+ resolve(packet.subarray(HardwareWalletUsbTransport.LEDGER_PACKET_HEADER_LENGTH));
+ }
+
+ // Otherwise
+ else {
+
+ // Securely clear packet
+ packet.fill(0);
+
+ // Reject
+ reject();
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Securely clear packet
+ packet.fill(0);
+
+ // Reject
+ reject();
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Securely clear packet
+ packet.fill(0);
+
+ // Reject
+ reject();
+ }
+
+ // Break
+ break;
+
+ // Trezor type
+ case HardwareWalletDefinitions.TREZOR_TRANSPORT_TYPE:
+
+ // Check if at the first packet
+ if(expectedSequenceIndex === 0) {
+
+ // Get magic numbers
+ var magicNumbers = packet.subarray(0, HardwareWalletUsbTransport.TREZOR_FIRST_PACKET_HEADER["length"]);
+
+ // Go through all magic numbers
+ for(var i = 0; i < magicNumbers["length"]; ++i) {
+
+ // Check if magic number isn't correct
+ if(magicNumbers[i] !== HardwareWalletUsbTransport.TREZOR_FIRST_PACKET_HEADER[i]) {
+
+ // Securely clear packet
+ packet.fill(0);
+
+ // Reject
+ reject();
+
+ // Return
+ return;
+ }
+ }
+
+ // Resolve packet's payload
+ resolve(packet.subarray(HardwareWalletUsbTransport.TREZOR_FIRST_PACKET_HEADER["length"]));
+ }
+
+ // Otherwise
+ else {
+
+ // Get magic numbers
+ var magicNumbers = packet.subarray(0, HardwareWalletUsbTransport.TREZOR_NEXT_PACKETS_HEADER["length"]);
+
+ // Go through all magic numbers
+ for(var i = 0; i < magicNumbers["length"]; ++i) {
+
+ // Check if magic number isn't correct
+ if(magicNumbers[i] !== HardwareWalletUsbTransport.TREZOR_NEXT_PACKETS_HEADER[i]) {
+
+ // Securely clear packet
+ packet.fill(0);
+
+ // Reject
+ reject();
+
+ // Return
+ return;
+ }
+ }
+
+ // Resolve packet's payload
+ resolve(packet.subarray(HardwareWalletUsbTransport.TREZOR_NEXT_PACKETS_HEADER["length"]));
+ }
+
+ // Break
+ break;
+ }
+
+ }
+
+ // Otherwise
+ else {
+
+ // Securely clear packet
+ packet.fill(0);
+
+ // Reject
+ reject();
+ }
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Reject error
+ reject(error);
+ });
+ });
+ };
+
+ // Return receiving first packet
+ return receivePacket(0).then(function(responsePart) {
+
+ // Check type
+ switch(type) {
+
+ // Ledger type
+ case HardwareWalletDefinitions.LEDGER_TRANSPORT_TYPE:
+
+ // Get message type
+ var messageType = new Uint8Array([]);
+
+ // Get response size
+ var responseSize = (responsePart[0] << HardwareWalletUsbTransport.BITS_IN_A_BYTE) | responsePart[1];
+
+ // Set response
+ var response = responsePart.subarray(Uint16Array["BYTES_PER_ELEMENT"]);
+
+ // Break
+ break;
+
+ // Trezor type
+ case HardwareWalletDefinitions.TREZOR_TRANSPORT_TYPE:
+
+ // Get message type
+ var messageType = responsePart.subarray(0, HardwareWalletUsbTransport.MESSAGE_TYPE_LENGTH);
+
+ // Get response size
+ var responseSize = (responsePart[HardwareWalletUsbTransport.MESSAGE_TYPE_LENGTH] << (HardwareWalletUsbTransport.BITS_IN_A_BYTE * 3)) | (responsePart[HardwareWalletUsbTransport.MESSAGE_TYPE_LENGTH + 1] << (HardwareWalletUsbTransport.BITS_IN_A_BYTE * 2)) | (responsePart[HardwareWalletUsbTransport.MESSAGE_TYPE_LENGTH + 2] << HardwareWalletUsbTransport.BITS_IN_A_BYTE) | responsePart[HardwareWalletUsbTransport.MESSAGE_TYPE_LENGTH + 3];
+
+ // Set response
+ var response = responsePart.subarray(HardwareWalletUsbTransport.MESSAGE_TYPE_LENGTH + Uint32Array["BYTES_PER_ELEMENT"]);
+
+ // Break
+ break;
+ }
+
+ // Set next sequence index
+ var nextSequenceIndex = 1;
+
+ // Get next response part
+ var getNextResponsePart = function() {
+
+ // Return promise
+ return new Promise(function(resolve, reject) {
+
+ // Check if the entire response hasn't been received
+ if(response["length"] < responseSize) {
+
+ // Return receiving next packet
+ return receivePacket(nextSequenceIndex).then(function(responsePart) {
+
+ // Append response part to response
+ var currentResponse = new Uint8Array(response["length"] + responsePart["length"]);
+ currentResponse.set(response);
+ currentResponse.set(responsePart, response["length"]);
+ response.fill(0);
+ responsePart.fill(0);
+ response = currentResponse;
+
+ // Increment next sequence index
+ ++nextSequenceIndex;
+
+ // Return getting next response part
+ return getNextResponsePart().then(function() {
+
+ // Resolve
+ resolve();
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Reject error
+ reject(error);
+ });
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Reject error
+ reject(error);
+ });
+ }
+
+ // Otherwise
+ else {
+
+ // Resolve
+ resolve();
+ }
+ });
+ };
+
+ // Return getting next response part
+ return getNextResponsePart().then(function() {
+
+ // Append message type to response
+ var finalResponse = new Uint8Array(responseSize + messageType["length"]);
+ finalResponse.set(response.subarray(0, responseSize));
+ finalResponse.set(messageType, responseSize);
+ response.fill(0);
+
+ // Resolve final response
+ resolve(finalResponse);
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Securely clear response
+ response.fill(0);
+
+ // Reject error
+ reject(error);
+ });
+
+ // Catch error
+ }).catch(function(error) {
+
+ // Reject error
+ reject(error);
+ });
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Reject error
+ reject(error);
+ });
+ });
+ }
+
+ // No payload
+ static get NO_PAYLOAD() {
+
+ // Return no payload
+ return null;
+ }
+
+ // No device
+ static get NO_DEVICE() {
+
+ // Return no device
+ return null;
+ }
+
+ // Devices
+ static get DEVICES() {
+
+ // Return devices
+ return [
+
+ // Ledger
+ {
+
+ // Type
+ "Type": HardwareWalletDefinitions.LEDGER_TRANSPORT_TYPE,
+
+ // Vendor ID
+ "Vendor ID": 0x2C97
+ },
+
+ // Trezor
+ {
+
+ // Type
+ "Type": HardwareWalletDefinitions.TREZOR_TRANSPORT_TYPE,
+
+ // Product name
+ "Product Name": "Trezor",
+
+ // Vendor ID
+ "Vendor ID": 0x1209,
+
+ // Product ID
+ "Product ID": 0x53C1
+ }
+ ];
+ }
+
+ // WebUSB interface class
+ static get WEBUSB_INTERFACE_CLASS() {
+
+ // Return WebUSB interface class
+ return 0xFF;
+ }
+
+ // Configuration
+ static get CONFIGURATION() {
+
+ // Return configuration
+ return 0x01;
+ }
+
+ // Packet size
+ static get PACKET_SIZE() {
+
+ // Return packet size
+ return 64;
+ }
+
+ // Message type length
+ static get MESSAGE_TYPE_LENGTH() {
+
+ // Return message type length
+ return Uint16Array["BYTES_PER_ELEMENT"];
+ }
+
+ // Bits in a byte
+ static get BITS_IN_A_BYTE() {
+
+ // Rerurn bits in a byte
+ return 8;
+ }
+
+ // Ledger endpoint
+ static get LEDGER_ENDPOINT() {
+
+ // Return Ledger endpoint
+ return 0x03;
+ }
+
+ // Trezor endpoint
+ static get TREZOR_ENDPOINT() {
+
+ // Return Trezor endpoint
+ return 0x01;
+ }
+
+ // Not found error code
+ static get NOT_FOUND_ERROR_CODE() {
+
+ // Return not found error code
+ return 8;
+ }
+
+ // Ledger packet header length
+ static get LEDGER_PACKET_HEADER_LENGTH() {
+
+ // Return Ledger packet header length
+ return 5;
+ }
+
+ // Ledger packet header tag
+ static get LEDGER_PACKET_HEADER_TAG() {
+
+ // Return Ledger packet header tag
+ return 0x05;
+ }
+
+ // Trezor first packet header
+ static get TREZOR_FIRST_PACKET_HEADER() {
+
+ // Return Trezor packet header
+ return new Uint8Array([0x3F, 0x23, 0x23]);
+ }
+
+ // Trezor next packets header
+ static get TREZOR_NEXT_PACKETS_HEADER() {
+
+ // Return Trezor next packets header
+ return new Uint8Array([0x3F]);
+ }
+
+ // Max channel
+ static get MAX_CHANNEL() {
+
+ // Return max channel
+ return 0xFFFF;
+ }
+}
+
+
+// Main function
+
+// Set global object's hardware wallet USB transport
+globalThis["HardwareWalletUsbTransport"] = HardwareWalletUsbTransport;
diff --git a/scripts/hash.js b/scripts/hash.js
new file mode 100755
index 0000000..16487ed
--- /dev/null
+++ b/scripts/hash.js
@@ -0,0 +1,80 @@
+// Use strict
+"use strict";
+
+
+// Classes
+
+// Hash class
+class Hash {
+
+ // Public
+
+ // Constructor
+ constructor(data) {
+
+ // Check if creating hash from data was successful
+ this.bytes = Blake2b.compute(Hash.HASH_LENGTH, Common.mergeArrays(data), new Uint8Array([]));
+
+ if(this.bytes === Blake2b.OPERATION_FAILED) {
+
+ // Throw error
+ throw "Creating hash failed.";
+ }
+ }
+
+ // Compare
+ compare(hash) {
+
+ // Go through all bytes in the hash
+ for(var i = 0; i < Hash.HASH_LENGTH; ++i) {
+
+ // Get bytes
+ var byte = this.getBytes()[i];
+ var otherByte = hash.getBytes()[i];
+
+ // Check if byte is greater than the other
+ if(byte > otherByte)
+
+ // Return sort greater than
+ return Common.SORT_GREATER_THAN;
+
+ // Otherwise check if byte is less than the other
+ else if(byte < otherByte)
+
+ // Return sort less than
+ return Common.SORT_LESS_THAN;
+ }
+
+ // Return sort equal
+ return Common.SORT_EQUAL;
+ }
+
+ // Serialize
+ serialize() {
+
+ // Return serialized hash
+ return Common.toHexString(this.getBytes());
+ }
+
+ // Private
+
+ // Get bytes
+ getBytes() {
+
+ // Return bytes
+ return this.bytes;
+ }
+
+ // Hash length
+ static get HASH_LENGTH() {
+
+ // Return hash length
+ return 32;
+ }
+}
+
+
+// Main function
+
+// Set global object's hash
+globalThis["Hash"] = Hash;
diff --git a/scripts/height.js b/scripts/height.js
new file mode 100755
index 0000000..005fa18
--- /dev/null
+++ b/scripts/height.js
@@ -0,0 +1,64 @@
+// Use strict
+"use strict";
+
+
+// Classes
+
+// Height class
+class Height {
+
+ // Public
+
+ // Constructor
+ constructor(height, hash = Height.NO_HASH) {
+
+ // Set height
+ this.setHeight(height);
+
+ // Set hash
+ this.setHash(hash);
+ }
+
+ // Set height
+ setHeight(height) {
+
+ // Set height
+ this.height = height;
+ }
+
+ // Get height
+ getHeight() {
+
+ // Return height
+ return this.height;
+ }
+
+ // Set hash
+ setHash(hash) {
+
+ // Set hash
+ this.hash = hash;
+ }
+
+ // Get hash
+ getHash() {
+
+ // Return hash
+ return this.hash;
+ }
+
+ // Private
+
+ // No hash
+ static get NO_HASH() {
+
+ // Return no hash
+ return "";
+ }
+}
+
+
+// Main function
+
+// Set global object's height
+globalThis["Height"] = Height;
diff --git a/scripts/hi-base32 license.txt b/scripts/hi-base32 license.txt
new file mode 100755
index 0000000..da790a8
--- /dev/null
+++ b/scripts/hi-base32 license.txt
@@ -0,0 +1,22 @@
+Copyright (c) 2015-2021 Chen, Yi-Cyuan
+
+MIT License
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/scripts/hi-base32-0.5.1.js b/scripts/hi-base32-0.5.1.js
new file mode 100755
index 0000000..3814239
--- /dev/null
+++ b/scripts/hi-base32-0.5.1.js
@@ -0,0 +1,458 @@
+/*
+ * [hi-base32]{@link https://github.com/emn178/hi-base32}
+ *
+ * @version 0.5.0
+ * @author Chen, Yi-Cyuan [emn178@gmail.com]
+ * @copyright Chen, Yi-Cyuan 2015-2018
+ * @license MIT
+ */
+/*jslint bitwise: true */
+(function () {
+ 'use strict';
+
+ var root = typeof window === 'object' ? window : self;
+ var NODE_JS = !root.HI_BASE32_NO_NODE_JS && typeof process === 'object' && process.versions && process.versions.node;
+ if (NODE_JS) {
+ root = global;
+ }
+ var COMMON_JS = !root.HI_BASE32_NO_COMMON_JS && typeof module === 'object' && module.exports;
+ var AMD = typeof define === 'function' && define.amd;
+ var BASE32_ENCODE_CHAR = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'.split('');
+ var BASE32_DECODE_CHAR = {
+ 'A': 0, 'B': 1, 'C': 2, 'D': 3, 'E': 4, 'F': 5, 'G': 6, 'H': 7, 'I': 8,
+ 'J': 9, 'K': 10, 'L': 11, 'M': 12, 'N': 13, 'O': 14, 'P': 15, 'Q': 16,
+ 'R': 17, 'S': 18, 'T': 19, 'U': 20, 'V': 21, 'W': 22, 'X': 23, 'Y': 24,
+ 'Z': 25, '2': 26, '3': 27, '4': 28, '5': 29, '6': 30, '7': 31
+ };
+
+ var blocks = [0, 0, 0, 0, 0, 0, 0, 0];
+
+ var throwInvalidUtf8 = function (position, partial) {
+ if (partial.length > 10) {
+ partial = '...' + partial.substr(-10);
+ }
+ var err = new Error('Decoded data is not valid UTF-8.'
+ + ' Maybe try base32.decode.asBytes()?'
+ + ' Partial data after reading ' + position + ' bytes: ' + partial + ' <-');
+ err.position = position;
+ throw err;
+ };
+
+ var toUtf8String = function (bytes) {
+ var str = '', length = bytes.length, i = 0, followingChars = 0, b, c;
+ while (i < length) {
+ b = bytes[i++];
+ if (b <= 0x7F) {
+ str += String.fromCharCode(b);
+ continue;
+ } else if (b > 0xBF && b <= 0xDF) {
+ c = b & 0x1F;
+ followingChars = 1;
+ } else if (b <= 0xEF) {
+ c = b & 0x0F;
+ followingChars = 2;
+ } else if (b <= 0xF7) {
+ c = b & 0x07;
+ followingChars = 3;
+ } else {
+ throwInvalidUtf8(i, str);
+ }
+
+ for (var j = 0; j < followingChars; ++j) {
+ b = bytes[i++];
+ if (b < 0x80 || b > 0xBF) {
+ throwInvalidUtf8(i, str);
+ }
+ c <<= 6;
+ c += b & 0x3F;
+ }
+ if (c >= 0xD800 && c <= 0xDFFF) {
+ throwInvalidUtf8(i, str);
+ }
+ if (c > 0x10FFFF) {
+ throwInvalidUtf8(i, str);
+ }
+
+ if (c <= 0xFFFF) {
+ str += String.fromCharCode(c);
+ } else {
+ c -= 0x10000;
+ str += String.fromCharCode((c >> 10) + 0xD800);
+ str += String.fromCharCode((c & 0x3FF) + 0xDC00);
+ }
+ }
+ return str;
+ };
+
+ var decodeAsBytes = function (base32Str) {
+ if (base32Str === '') {
+ return [];
+ } else if (!/^[A-Z2-7=]+$/.test(base32Str)) {
+ throw new Error('Invalid base32 characters');
+ }
+ base32Str = base32Str.replace(/=/g, '');
+ var v1, v2, v3, v4, v5, v6, v7, v8, bytes = [], index = 0, length = base32Str.length;
+
+ // 4 char to 3 bytes
+ for (var i = 0, count = length >> 3 << 3; i < count;) {
+ v1 = BASE32_DECODE_CHAR[base32Str.charAt(i++)];
+ v2 = BASE32_DECODE_CHAR[base32Str.charAt(i++)];
+ v3 = BASE32_DECODE_CHAR[base32Str.charAt(i++)];
+ v4 = BASE32_DECODE_CHAR[base32Str.charAt(i++)];
+ v5 = BASE32_DECODE_CHAR[base32Str.charAt(i++)];
+ v6 = BASE32_DECODE_CHAR[base32Str.charAt(i++)];
+ v7 = BASE32_DECODE_CHAR[base32Str.charAt(i++)];
+ v8 = BASE32_DECODE_CHAR[base32Str.charAt(i++)];
+ bytes[index++] = (v1 << 3 | v2 >>> 2) & 255;
+ bytes[index++] = (v2 << 6 | v3 << 1 | v4 >>> 4) & 255;
+ bytes[index++] = (v4 << 4 | v5 >>> 1) & 255;
+ bytes[index++] = (v5 << 7 | v6 << 2 | v7 >>> 3) & 255;
+ bytes[index++] = (v7 << 5 | v8) & 255;
+ }
+
+ // remain bytes
+ var remain = length - count;
+ if (remain === 2) {
+ v1 = BASE32_DECODE_CHAR[base32Str.charAt(i++)];
+ v2 = BASE32_DECODE_CHAR[base32Str.charAt(i++)];
+ bytes[index++] = (v1 << 3 | v2 >>> 2) & 255;
+ } else if (remain === 4) {
+ v1 = BASE32_DECODE_CHAR[base32Str.charAt(i++)];
+ v2 = BASE32_DECODE_CHAR[base32Str.charAt(i++)];
+ v3 = BASE32_DECODE_CHAR[base32Str.charAt(i++)];
+ v4 = BASE32_DECODE_CHAR[base32Str.charAt(i++)];
+ bytes[index++] = (v1 << 3 | v2 >>> 2) & 255;
+ bytes[index++] = (v2 << 6 | v3 << 1 | v4 >>> 4) & 255;
+ } else if (remain === 5) {
+ v1 = BASE32_DECODE_CHAR[base32Str.charAt(i++)];
+ v2 = BASE32_DECODE_CHAR[base32Str.charAt(i++)];
+ v3 = BASE32_DECODE_CHAR[base32Str.charAt(i++)];
+ v4 = BASE32_DECODE_CHAR[base32Str.charAt(i++)];
+ v5 = BASE32_DECODE_CHAR[base32Str.charAt(i++)];
+ bytes[index++] = (v1 << 3 | v2 >>> 2) & 255;
+ bytes[index++] = (v2 << 6 | v3 << 1 | v4 >>> 4) & 255;
+ bytes[index++] = (v4 << 4 | v5 >>> 1) & 255;
+ } else if (remain === 7) {
+ v1 = BASE32_DECODE_CHAR[base32Str.charAt(i++)];
+ v2 = BASE32_DECODE_CHAR[base32Str.charAt(i++)];
+ v3 = BASE32_DECODE_CHAR[base32Str.charAt(i++)];
+ v4 = BASE32_DECODE_CHAR[base32Str.charAt(i++)];
+ v5 = BASE32_DECODE_CHAR[base32Str.charAt(i++)];
+ v6 = BASE32_DECODE_CHAR[base32Str.charAt(i++)];
+ v7 = BASE32_DECODE_CHAR[base32Str.charAt(i++)];
+ bytes[index++] = (v1 << 3 | v2 >>> 2) & 255;
+ bytes[index++] = (v2 << 6 | v3 << 1 | v4 >>> 4) & 255;
+ bytes[index++] = (v4 << 4 | v5 >>> 1) & 255;
+ bytes[index++] = (v5 << 7 | v6 << 2 | v7 >>> 3) & 255;
+ }
+ return bytes;
+ };
+
+ var encodeAscii = function (str) {
+ var v1, v2, v3, v4, v5, base32Str = '', length = str.length;
+ for (var i = 0, count = parseInt(length / 5) * 5; i < count;) {
+ v1 = str.charCodeAt(i++);
+ v2 = str.charCodeAt(i++);
+ v3 = str.charCodeAt(i++);
+ v4 = str.charCodeAt(i++);
+ v5 = str.charCodeAt(i++);
+ base32Str += BASE32_ENCODE_CHAR[v1 >>> 3] +
+ BASE32_ENCODE_CHAR[(v1 << 2 | v2 >>> 6) & 31] +
+ BASE32_ENCODE_CHAR[(v2 >>> 1) & 31] +
+ BASE32_ENCODE_CHAR[(v2 << 4 | v3 >>> 4) & 31] +
+ BASE32_ENCODE_CHAR[(v3 << 1 | v4 >>> 7) & 31] +
+ BASE32_ENCODE_CHAR[(v4 >>> 2) & 31] +
+ BASE32_ENCODE_CHAR[(v4 << 3 | v5 >>> 5) & 31] +
+ BASE32_ENCODE_CHAR[v5 & 31];
+ }
+
+ // remain char
+ var remain = length - count;
+ if (remain === 1) {
+ v1 = str.charCodeAt(i);
+ base32Str += BASE32_ENCODE_CHAR[v1 >>> 3] +
+ BASE32_ENCODE_CHAR[(v1 << 2) & 31] +
+ '======';
+ } else if (remain === 2) {
+ v1 = str.charCodeAt(i++);
+ v2 = str.charCodeAt(i);
+ base32Str += BASE32_ENCODE_CHAR[v1 >>> 3] +
+ BASE32_ENCODE_CHAR[(v1 << 2 | v2 >>> 6) & 31] +
+ BASE32_ENCODE_CHAR[(v2 >>> 1) & 31] +
+ BASE32_ENCODE_CHAR[(v2 << 4) & 31] +
+ '====';
+ } else if (remain === 3) {
+ v1 = str.charCodeAt(i++);
+ v2 = str.charCodeAt(i++);
+ v3 = str.charCodeAt(i);
+ base32Str += BASE32_ENCODE_CHAR[v1 >>> 3] +
+ BASE32_ENCODE_CHAR[(v1 << 2 | v2 >>> 6) & 31] +
+ BASE32_ENCODE_CHAR[(v2 >>> 1) & 31] +
+ BASE32_ENCODE_CHAR[(v2 << 4 | v3 >>> 4) & 31] +
+ BASE32_ENCODE_CHAR[(v3 << 1) & 31] +
+ '===';
+ } else if (remain === 4) {
+ v1 = str.charCodeAt(i++);
+ v2 = str.charCodeAt(i++);
+ v3 = str.charCodeAt(i++);
+ v4 = str.charCodeAt(i);
+ base32Str += BASE32_ENCODE_CHAR[v1 >>> 3] +
+ BASE32_ENCODE_CHAR[(v1 << 2 | v2 >>> 6) & 31] +
+ BASE32_ENCODE_CHAR[(v2 >>> 1) & 31] +
+ BASE32_ENCODE_CHAR[(v2 << 4 | v3 >>> 4) & 31] +
+ BASE32_ENCODE_CHAR[(v3 << 1 | v4 >>> 7) & 31] +
+ BASE32_ENCODE_CHAR[(v4 >>> 2) & 31] +
+ BASE32_ENCODE_CHAR[(v4 << 3) & 31] +
+ '=';
+ }
+ return base32Str;
+ };
+
+ var encodeUtf8 = function (str) {
+ var v1, v2, v3, v4, v5, code, end = false, base32Str = '',
+ index = 0, i, start = 0, bytes = 0, length = str.length;
+ if (str === '') {
+ return base32Str;
+ }
+ do {
+ blocks[0] = blocks[5];
+ blocks[1] = blocks[6];
+ blocks[2] = blocks[7];
+ for (i = start; index < length && i < 5; ++index) {
+ code = str.charCodeAt(index);
+ if (code < 0x80) {
+ blocks[i++] = code;
+ } else if (code < 0x800) {
+ blocks[i++] = 0xc0 | (code >> 6);
+ blocks[i++] = 0x80 | (code & 0x3f);
+ } else if (code < 0xd800 || code >= 0xe000) {
+ blocks[i++] = 0xe0 | (code >> 12);
+ blocks[i++] = 0x80 | ((code >> 6) & 0x3f);
+ blocks[i++] = 0x80 | (code & 0x3f);
+ } else {
+ code = 0x10000 + (((code & 0x3ff) << 10) | (str.charCodeAt(++index) & 0x3ff));
+ blocks[i++] = 0xf0 | (code >> 18);
+ blocks[i++] = 0x80 | ((code >> 12) & 0x3f);
+ blocks[i++] = 0x80 | ((code >> 6) & 0x3f);
+ blocks[i++] = 0x80 | (code & 0x3f);
+ }
+ }
+ bytes += i - start;
+ start = i - 5;
+ if (index === length) {
+ ++index;
+ }
+ if (index > length && i < 6) {
+ end = true;
+ }
+ v1 = blocks[0];
+ if (i > 4) {
+ v2 = blocks[1];
+ v3 = blocks[2];
+ v4 = blocks[3];
+ v5 = blocks[4];
+ base32Str += BASE32_ENCODE_CHAR[v1 >>> 3] +
+ BASE32_ENCODE_CHAR[(v1 << 2 | v2 >>> 6) & 31] +
+ BASE32_ENCODE_CHAR[(v2 >>> 1) & 31] +
+ BASE32_ENCODE_CHAR[(v2 << 4 | v3 >>> 4) & 31] +
+ BASE32_ENCODE_CHAR[(v3 << 1 | v4 >>> 7) & 31] +
+ BASE32_ENCODE_CHAR[(v4 >>> 2) & 31] +
+ BASE32_ENCODE_CHAR[(v4 << 3 | v5 >>> 5) & 31] +
+ BASE32_ENCODE_CHAR[v5 & 31];
+ } else if (i === 1) {
+ base32Str += BASE32_ENCODE_CHAR[v1 >>> 3] +
+ BASE32_ENCODE_CHAR[(v1 << 2) & 31] +
+ '======';
+ } else if (i === 2) {
+ v2 = blocks[1];
+ base32Str += BASE32_ENCODE_CHAR[v1 >>> 3] +
+ BASE32_ENCODE_CHAR[(v1 << 2 | v2 >>> 6) & 31] +
+ BASE32_ENCODE_CHAR[(v2 >>> 1) & 31] +
+ BASE32_ENCODE_CHAR[(v2 << 4) & 31] +
+ '====';
+ } else if (i === 3) {
+ v2 = blocks[1];
+ v3 = blocks[2];
+ base32Str += BASE32_ENCODE_CHAR[v1 >>> 3] +
+ BASE32_ENCODE_CHAR[(v1 << 2 | v2 >>> 6) & 31] +
+ BASE32_ENCODE_CHAR[(v2 >>> 1) & 31] +
+ BASE32_ENCODE_CHAR[(v2 << 4 | v3 >>> 4) & 31] +
+ BASE32_ENCODE_CHAR[(v3 << 1) & 31] +
+ '===';
+ } else {
+ v2 = blocks[1];
+ v3 = blocks[2];
+ v4 = blocks[3];
+ base32Str += BASE32_ENCODE_CHAR[v1 >>> 3] +
+ BASE32_ENCODE_CHAR[(v1 << 2 | v2 >>> 6) & 31] +
+ BASE32_ENCODE_CHAR[(v2 >>> 1) & 31] +
+ BASE32_ENCODE_CHAR[(v2 << 4 | v3 >>> 4) & 31] +
+ BASE32_ENCODE_CHAR[(v3 << 1 | v4 >>> 7) & 31] +
+ BASE32_ENCODE_CHAR[(v4 >>> 2) & 31] +
+ BASE32_ENCODE_CHAR[(v4 << 3) & 31] +
+ '=';
+ }
+ } while (!end);
+ return base32Str;
+ };
+
+ var encodeBytes = function (bytes) {
+ var v1, v2, v3, v4, v5, base32Str = '', length = bytes.length;
+ for (var i = 0, count = parseInt(length / 5) * 5; i < count;) {
+ v1 = bytes[i++];
+ v2 = bytes[i++];
+ v3 = bytes[i++];
+ v4 = bytes[i++];
+ v5 = bytes[i++];
+ base32Str += BASE32_ENCODE_CHAR[v1 >>> 3] +
+ BASE32_ENCODE_CHAR[(v1 << 2 | v2 >>> 6) & 31] +
+ BASE32_ENCODE_CHAR[(v2 >>> 1) & 31] +
+ BASE32_ENCODE_CHAR[(v2 << 4 | v3 >>> 4) & 31] +
+ BASE32_ENCODE_CHAR[(v3 << 1 | v4 >>> 7) & 31] +
+ BASE32_ENCODE_CHAR[(v4 >>> 2) & 31] +
+ BASE32_ENCODE_CHAR[(v4 << 3 | v5 >>> 5) & 31] +
+ BASE32_ENCODE_CHAR[v5 & 31];
+ }
+
+ // remain char
+ var remain = length - count;
+ if (remain === 1) {
+ v1 = bytes[i];
+ base32Str += BASE32_ENCODE_CHAR[v1 >>> 3] +
+ BASE32_ENCODE_CHAR[(v1 << 2) & 31] +
+ '======';
+ } else if (remain === 2) {
+ v1 = bytes[i++];
+ v2 = bytes[i];
+ base32Str += BASE32_ENCODE_CHAR[v1 >>> 3] +
+ BASE32_ENCODE_CHAR[(v1 << 2 | v2 >>> 6) & 31] +
+ BASE32_ENCODE_CHAR[(v2 >>> 1) & 31] +
+ BASE32_ENCODE_CHAR[(v2 << 4) & 31] +
+ '====';
+ } else if (remain === 3) {
+ v1 = bytes[i++];
+ v2 = bytes[i++];
+ v3 = bytes[i];
+ base32Str += BASE32_ENCODE_CHAR[v1 >>> 3] +
+ BASE32_ENCODE_CHAR[(v1 << 2 | v2 >>> 6) & 31] +
+ BASE32_ENCODE_CHAR[(v2 >>> 1) & 31] +
+ BASE32_ENCODE_CHAR[(v2 << 4 | v3 >>> 4) & 31] +
+ BASE32_ENCODE_CHAR[(v3 << 1) & 31] +
+ '===';
+ } else if (remain === 4) {
+ v1 = bytes[i++];
+ v2 = bytes[i++];
+ v3 = bytes[i++];
+ v4 = bytes[i];
+ base32Str += BASE32_ENCODE_CHAR[v1 >>> 3] +
+ BASE32_ENCODE_CHAR[(v1 << 2 | v2 >>> 6) & 31] +
+ BASE32_ENCODE_CHAR[(v2 >>> 1) & 31] +
+ BASE32_ENCODE_CHAR[(v2 << 4 | v3 >>> 4) & 31] +
+ BASE32_ENCODE_CHAR[(v3 << 1 | v4 >>> 7) & 31] +
+ BASE32_ENCODE_CHAR[(v4 >>> 2) & 31] +
+ BASE32_ENCODE_CHAR[(v4 << 3) & 31] +
+ '=';
+ }
+ return base32Str;
+ };
+
+ var encode = function (input, asciiOnly) {
+ var notString = typeof(input) !== 'string';
+ if (notString && input.constructor === ArrayBuffer) {
+ input = new Uint8Array(input);
+ }
+ if (notString) {
+ return encodeBytes(input);
+ } else if (asciiOnly) {
+ return encodeAscii(input);
+ } else {
+ return encodeUtf8(input);
+ }
+ };
+
+ var decode = function (base32Str, asciiOnly) {
+ if (!asciiOnly) {
+ return toUtf8String(decodeAsBytes(base32Str));
+ }
+ if (base32Str === '') {
+ return '';
+ } else if (!/^[A-Z2-7=]+$/.test(base32Str)) {
+ throw new Error('Invalid base32 characters');
+ }
+ var v1, v2, v3, v4, v5, v6, v7, v8, str = '', length = base32Str.indexOf('=');
+ if (length === -1) {
+ length = base32Str.length;
+ }
+
+ // 8 char to 5 bytes
+ for (var i = 0, count = length >> 3 << 3; i < count;) {
+ v1 = BASE32_DECODE_CHAR[base32Str.charAt(i++)];
+ v2 = BASE32_DECODE_CHAR[base32Str.charAt(i++)];
+ v3 = BASE32_DECODE_CHAR[base32Str.charAt(i++)];
+ v4 = BASE32_DECODE_CHAR[base32Str.charAt(i++)];
+ v5 = BASE32_DECODE_CHAR[base32Str.charAt(i++)];
+ v6 = BASE32_DECODE_CHAR[base32Str.charAt(i++)];
+ v7 = BASE32_DECODE_CHAR[base32Str.charAt(i++)];
+ v8 = BASE32_DECODE_CHAR[base32Str.charAt(i++)];
+ str += String.fromCharCode((v1 << 3 | v2 >>> 2) & 255) +
+ String.fromCharCode((v2 << 6 | v3 << 1 | v4 >>> 4) & 255) +
+ String.fromCharCode((v4 << 4 | v5 >>> 1) & 255) +
+ String.fromCharCode((v5 << 7 | v6 << 2 | v7 >>> 3) & 255) +
+ String.fromCharCode((v7 << 5 | v8) & 255);
+ }
+
+ // remain bytes
+ var remain = length - count;
+ if (remain === 2) {
+ v1 = BASE32_DECODE_CHAR[base32Str.charAt(i++)];
+ v2 = BASE32_DECODE_CHAR[base32Str.charAt(i++)];
+ str += String.fromCharCode((v1 << 3 | v2 >>> 2) & 255);
+ } else if (remain === 4) {
+ v1 = BASE32_DECODE_CHAR[base32Str.charAt(i++)];
+ v2 = BASE32_DECODE_CHAR[base32Str.charAt(i++)];
+ v3 = BASE32_DECODE_CHAR[base32Str.charAt(i++)];
+ v4 = BASE32_DECODE_CHAR[base32Str.charAt(i++)];
+ str += String.fromCharCode((v1 << 3 | v2 >>> 2) & 255) +
+ String.fromCharCode((v2 << 6 | v3 << 1 | v4 >>> 4) & 255);
+ } else if (remain === 5) {
+ v1 = BASE32_DECODE_CHAR[base32Str.charAt(i++)];
+ v2 = BASE32_DECODE_CHAR[base32Str.charAt(i++)];
+ v3 = BASE32_DECODE_CHAR[base32Str.charAt(i++)];
+ v4 = BASE32_DECODE_CHAR[base32Str.charAt(i++)];
+ v5 = BASE32_DECODE_CHAR[base32Str.charAt(i++)];
+ str += String.fromCharCode((v1 << 3 | v2 >>> 2) & 255) +
+ String.fromCharCode((v2 << 6 | v3 << 1 | v4 >>> 4) & 255) +
+ String.fromCharCode((v4 << 4 | v5 >>> 1) & 255);
+ } else if (remain === 7) {
+ v1 = BASE32_DECODE_CHAR[base32Str.charAt(i++)];
+ v2 = BASE32_DECODE_CHAR[base32Str.charAt(i++)];
+ v3 = BASE32_DECODE_CHAR[base32Str.charAt(i++)];
+ v4 = BASE32_DECODE_CHAR[base32Str.charAt(i++)];
+ v5 = BASE32_DECODE_CHAR[base32Str.charAt(i++)];
+ v6 = BASE32_DECODE_CHAR[base32Str.charAt(i++)];
+ v7 = BASE32_DECODE_CHAR[base32Str.charAt(i++)];
+ str += String.fromCharCode((v1 << 3 | v2 >>> 2) & 255) +
+ String.fromCharCode((v2 << 6 | v3 << 1 | v4 >>> 4) & 255) +
+ String.fromCharCode((v4 << 4 | v5 >>> 1) & 255) +
+ String.fromCharCode((v5 << 7 | v6 << 2 | v7 >>> 3) & 255);
+ }
+ return str;
+ };
+
+ var exports = {
+ encode: encode,
+ decode: decode
+ };
+ decode.asBytes = decodeAsBytes;
+
+ if (COMMON_JS) {
+ module.exports = exports;
+ } else {
+ root.base32 = exports;
+ if (AMD) {
+ define(function() {
+ return exports;
+ });
+ }
+ }
+})();
diff --git a/scripts/identifier.js b/scripts/identifier.js
new file mode 100755
index 0000000..8859be6
--- /dev/null
+++ b/scripts/identifier.js
@@ -0,0 +1,437 @@
+// Use strict
+"use strict";
+
+
+// Classes
+
+// Identifier class
+class Identifier {
+
+ // Public
+
+ // Constructor
+ constructor(serializedIdentifier = Identifier.DEFAULT_SERIALIZED_IDENTIFIER) {
+
+ // Set depth
+ this.depth = 0;
+
+ // Set paths
+ this.paths = new Uint32Array(Identifier.MAX_DEPTH);
+
+ // Set value to serialized identifier
+ this.setValue(serializedIdentifier);
+ }
+
+ // Get depth
+ getDepth() {
+
+ // Return depth
+ return this.depth;
+ }
+
+ // Get paths
+ getPaths() {
+
+ // Return paths
+ return this.paths;
+ }
+
+ // Set value
+ setValue(serializedIdentifierOrDepth, firstValue, secondValue, thirdValue, fourthValue) {
+
+ // Check if value is provided in a serialized format
+ if(typeof serializedIdentifierOrDepth === "string") {
+
+ // Get serialized identifier
+ var serializedIdentifier = serializedIdentifierOrDepth;
+
+ // Check if serialized identifier ia a hex string
+ if(Common.isHexString(serializedIdentifier) === true) {
+
+ // Get serialized value
+ var serializedValue = Common.fromHexString(serializedIdentifier);
+
+ // Check if serialized value is valid
+ if(serializedValue["length"] === Identifier.LENGTH) {
+
+ // Set depth
+ this.depth = Math.min(serializedValue[Identifier.DEPTH_INDEX], Identifier.MAX_DEPTH);
+
+ // Go through all paths
+ var serializedValueDataView = new DataView(serializedValue["buffer"]);
+
+ for(var i = 0; i < this.getPaths()["length"]; ++i)
+
+ // Set path
+ this.paths[i] = serializedValueDataView.getUint32(Identifier.PATHS_INDEX + i * Uint32Array["BYTES_PER_ELEMENT"], false);
+ }
+
+ // Otherwise
+ else {
+
+ // Throw error
+ throw "Invalid identifier.";
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Throw error
+ throw "Invalid identifier.";
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Get depth
+ var depth = serializedIdentifierOrDepth;
+
+ // Check if depth is valid
+ if(depth <= Identifier.MAX_DEPTH) {
+
+ // Set depth
+ this.depth = depth;
+
+ // Check if paths are provided as an array
+ if(firstValue instanceof Uint32Array === true) {
+
+ // Get paths
+ var paths = firstValue;
+
+ // Check if paths are valid
+ if(paths["length"] === this.getPaths()["length"]) {
+
+ // Go through all paths
+ for(var i = 0; i < this.getPaths()["length"]; ++i) {
+
+ // Set path to path
+ this.paths[i] = paths[i];
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Throw error
+ throw "Invalid identifier.";
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Set paths
+ this.paths[0] = firstValue;
+ this.paths[1] = secondValue;
+ this.paths[2] = thirdValue;
+ this.paths[3] = fourthValue;
+ }
+ }
+
+ // Otherwise
+ else {
+
+ // Throw error
+ throw "Invalid identifier.";
+ }
+ }
+ }
+
+ // Get value
+ getValue() {
+
+ // Initialize buffer
+ var buffer = new Uint8Array(Identifier.LENGTH);
+
+ // Set buffer's depth
+ buffer[Identifier.DEPTH_INDEX] = this.getDepth();
+
+ // Go through all paths
+ var bufferView = new DataView(buffer["buffer"]);
+
+ for(var i = 0; i < this.getPaths()["length"]; ++i)
+
+ // Set buffer's path
+ bufferView.setUint32(Identifier.PATHS_INDEX + i * Uint32Array["BYTES_PER_ELEMENT"], this.getPaths()[i], false);
+
+ // Return buffer
+ return buffer;
+ }
+
+ // Equals value
+ equalsValue(identifier) {
+
+ // Check if depths differ
+ if(this.getDepth() !== identifier.getDepth())
+
+ // Return false
+ return false;
+
+ // Return if paths are equal
+ return Common.arraysAreEqual(this.getPaths(), identifier.getPaths() === true);
+ }
+
+ // Includes value
+ includesValue(identifier) {
+
+ // Check if depths differ
+ if(this.getDepth() !== identifier.getDepth())
+
+ // Return false
+ return false;
+
+ // Return if last path is greater than or equal to the value's last path
+ return this.getLastPath() >= identifier.getLastPath();
+ }
+
+ // Get last path index
+ getLastPath() {
+
+ // Return last path
+ return (this.getDepth() === 0) ? 0 : this.getPaths()[this.getDepth() - 1];
+ }
+
+ // Get next
+ getNext(height = Identifier.NO_HEIGHT) {
+
+ // Create next as a clone of self
+ var next = this.clone();
+
+ // Increment next's last path to value
+ ++next.paths[next.getDepth() - 1];
+
+ // Check if height exists
+ if(height !== Identifier.NO_HEIGHT) {
+
+ // Check wallet type
+ switch(Consensus.getWalletType()) {
+
+ // MWC wallet
+ case Consensus.MWC_WALLET_TYPE:
+
+ // Set next's height path as the height limited by its maximum value
+ next.paths[Identifier.HEIGHT_PATH_INDEX] = Math.max(height.modulo(Identifier.MAXIMUM_HEIGHT + 1).toNumber(), 1);
+
+ // Break
+ break;
+ }
+ }
+
+ // Return next
+ return next;
+ }
+
+ // Get parent
+ getParent() {
+
+ // Create parent as a clone of self
+ var parent = this.clone();
+
+ // Check if previous depth exists
+ if(this.getDepth() > 0) {
+
+ // Clear parent's last path
+ parent.paths[parent.getDepth() - 1] = 0;
+
+ // Decrement parent's depth
+ --parent.depth;
+ }
+
+ // Return parent
+ return parent;
+ }
+
+ // Get child
+ getChild(height = Identifier.NO_HEIGHT) {
+
+ // Create child as a clone of self
+ var child = this.clone();
+
+ // Check if next depth exists
+ if(this.getDepth() < Identifier.MAX_DEPTH) {
+
+ // Increment child's depth
+ ++child.depth;
+ }
+
+ // Check if height exists
+ if(height !== Identifier.NO_HEIGHT) {
+
+ // Check wallet type
+ switch(Consensus.getWalletType()) {
+
+ // MWC wallet
+ case Consensus.MWC_WALLET_TYPE:
+
+ // Set child's height path as the height limited by its maximum value
+ child.paths[Identifier.HEIGHT_PATH_INDEX] = Math.max(height.modulo(Identifier.MAXIMUM_HEIGHT + 1).toNumber(), 1);
+
+ // Break
+ break;
+ }
+ }
+
+ // Return child
+ return child;
+ }
+
+ // Get height
+ getHeight() {
+
+ // Check wallet type
+ switch(Consensus.getWalletType()) {
+
+ // MWC wallet
+ case Consensus.MWC_WALLET_TYPE:
+
+ // Get height from height path
+ var height = this.getPaths()[Identifier.HEIGHT_PATH_INDEX];
+
+ // Check if height exists
+ if(height !== Consensus.FIRST_BLOCK_HEIGHT) {
+
+ // Return height
+ return new BigNumber(height);
+ }
+
+ // Otherwise
+ else {
+
+ // Return no height
+ return Identifier.NO_HEIGHT;
+ }
+ }
+
+ // Return no height
+ return Identifier.NO_HEIGHT;
+ }
+
+ // Remove extras
+ removeExtras() {
+
+ // Create copy as a clone of self
+ var copy = this.clone();
+
+ // Check wallet type
+ switch(Consensus.getWalletType()) {
+
+ // MWC wallet
+ case Consensus.MWC_WALLET_TYPE:
+
+ // Set copy's height path to not exist
+ copy.paths[Identifier.HEIGHT_PATH_INDEX] = Consensus.FIRST_BLOCK_HEIGHT;
+
+ // Break
+ break;
+ }
+
+ // Return copy
+ return copy;
+ }
+
+ // Is path hardened
+ static isPathHardened(path) {
+
+ // Return if path is hardened
+ return (path & Identifier.PATH_HARDENED_MASK) !== 0;
+ }
+
+ // Length
+ static get LENGTH() {
+
+ // Return length
+ return 1 * Uint8Array["BYTES_PER_ELEMENT"] + Identifier.MAX_DEPTH * Uint32Array["BYTES_PER_ELEMENT"];
+ }
+
+ // Max depth
+ static get MAX_DEPTH() {
+
+ // Return max depth
+ return 4;
+ }
+
+ // Root serialized identifier
+ static get ROOT_SERIALIZED_IDENTIFIER() {
+
+ // Return root serialized identifier
+ return "0000000000000000000000000000000000";
+ }
+
+ // Paths index
+ static get PATHS_INDEX() {
+
+ // Return paths index
+ return Identifier.DEPTH_INDEX + 1;
+ }
+
+ // No identifier
+ static get NO_IDENTIFIER() {
+
+ // Return no identifier
+ return null;
+ }
+
+ // No heights
+ static get NO_HEIGHT() {
+
+ // Return no height
+ return null;
+ }
+
+ // Maximum height
+ static get MAXIMUM_HEIGHT() {
+
+ // Return maximum height
+ return Common.UINT32_MAX_VALUE;
+ }
+
+ // Private
+
+ // Clone
+ clone() {
+
+ // Create a copy of self
+ var copy = new Identifier();
+
+ copy.setValue(this.getDepth(), this.getPaths());
+
+ // Return copy
+ return copy;
+ }
+
+ // Default serialized identifier
+ static get DEFAULT_SERIALIZED_IDENTIFIER() {
+
+ // Return default serialized identifier
+ return "0200000000000000000000000000000000";
+ }
+
+ // Depth index
+ static get DEPTH_INDEX() {
+
+ // Return depth index
+ return 0;
+ }
+
+ // Path hardened mask
+ static get PATH_HARDENED_MASK() {
+
+ // Return path hardened mask
+ return 0x80000000;
+ }
+
+ // Height path index
+ static get HEIGHT_PATH_INDEX() {
+
+ // Return height path index
+ return Identifier.MAX_DEPTH - 1;
+ }
+}
+
+
+// Main function
+
+// Set global object's identifier
+globalThis["Identifier"] = Identifier;
diff --git a/scripts/initial_heights_obtained.js b/scripts/initial_heights_obtained.js
new file mode 100755
index 0000000..e6cacd1
--- /dev/null
+++ b/scripts/initial_heights_obtained.js
@@ -0,0 +1,132 @@
+// Use strict
+"use strict";
+
+
+// Classes
+
+// Initial heights obtained class
+class InitialHeightsObtained {
+
+ // Public
+
+ // Constructor
+ constructor(node) {
+
+ // Create database
+ Database.createDatabase(function(database, currentVersion, databaseTransaction) {
+
+ // Create or get initial heights obtained object store
+ var initialHeightsObtainedObjectStore = (currentVersion === Database.NO_CURRENT_VERSION) ? database.createObjectStore(InitialHeightsObtained.OBJECT_STORE_NAME, {
+
+ // Key path
+ "keyPath": [
+
+ // Wallet type
+ Database.toKeyPath(InitialHeightsObtained.DATABASE_WALLET_TYPE_NAME),
+
+ // Network type
+ Database.toKeyPath(InitialHeightsObtained.DATABASE_NETWORK_TYPE_NAME)
+ ]
+
+ }) : databaseTransaction.objectStore(InitialHeightsObtained.OBJECT_STORE_NAME);
+ });
+ }
+
+ // Get obtained
+ getObtained() {
+
+ // Return promise
+ return new Promise(function(resolve, reject) {
+
+ // Return getting the initial heights obtained with the wallet type and network type in the database
+ return Database.getResults(InitialHeightsObtained.OBJECT_STORE_NAME, 0, 1, Database.NO_INDEX, IDBKeyRange.only([
+
+ // Wallet type
+ Consensus.getWalletType(),
+
+ // Network type
+ Consensus.getNetworkType(),
+
+ ])).then(function(results) {
+
+ // Check if initial heights were obtained
+ if(results["length"] !== 0) {
+
+ // Resolve true
+ resolve(true);
+ }
+
+ // Otherwise
+ else {
+
+ // Resolve false
+ resolve(false);
+ }
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Reject error
+ reject("The database failed.");
+ });
+ });
+ }
+
+ // Set obtained
+ setObtained() {
+
+ // Return promise
+ return new Promise(function(resolve, reject) {
+
+ // Save initial heights obtained for the wallet type and network type in the database
+ Database.saveResults(InitialHeightsObtained.OBJECT_STORE_NAME, [{
+
+ // Wallet type
+ [Database.toKeyPath(InitialHeightsObtained.DATABASE_WALLET_TYPE_NAME)]: Consensus.getWalletType(),
+
+ // Network type
+ [Database.toKeyPath(InitialHeightsObtained.DATABASE_NETWORK_TYPE_NAME)]: Consensus.getNetworkType()
+
+ }], [], Database.CREATE_NEW_TRANSACTION, Database.STRICT_DURABILITY).then(function() {
+
+ // Resolve
+ resolve();
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Reject error
+ reject("The database failed.");
+ });
+ });
+ }
+
+ // Private
+
+ // Object store name
+ static get OBJECT_STORE_NAME() {
+
+ // Return object store name
+ return "Initial Heights Obtained";
+ }
+
+ // Database wallet type name
+ static get DATABASE_WALLET_TYPE_NAME() {
+
+ // Return database wallet type name
+ return "Wallet Type";
+ }
+
+ // Database network type name
+ static get DATABASE_NETWORK_TYPE_NAME() {
+
+ // Return database network type name
+ return "Network Type";
+ }
+}
+
+
+// Main function
+
+// Set global object's initial heights obtained
+globalThis["InitialHeightsObtained"] = InitialHeightsObtained;
diff --git a/scripts/install_app.js b/scripts/install_app.js
new file mode 100755
index 0000000..39f746d
--- /dev/null
+++ b/scripts/install_app.js
@@ -0,0 +1,353 @@
+// Use strict
+"use strict";
+
+
+// Classes
+
+// Install app class
+class InstallApp {
+
+ // Public
+
+ // Constructor
+ constructor(cookieAcceptance, automaticLock) {
+
+ // Set automatic lock
+ this.automaticLock = automaticLock;
+
+ // Get install app display
+ this.installAppDisplay = $("section.installApp");
+
+ // Get install now button
+ this.installNowButton = this.installAppDisplay.find("button.installNow");
+
+ // Get remind me later button
+ this.remindMeLaterButton = this.installAppDisplay.find("button.remindMeLater");
+
+ // Set can show
+ this.canShow = false;
+
+ // Set was installed
+ this.wasInstalled = false;
+
+ // Set cookie acceptance is hidden
+ this.cookieAcceptanceIsHidden = false;
+
+ // Set self
+ var self = this;
+
+ // Cookie acceptance is hidden event
+ $(cookieAcceptance).one(CookieAcceptance.IS_HIDDEN_EVENT, function() {
+
+ // Set timeout
+ setTimeout(function() {
+
+ // Set cookie acceptance is hidden
+ self.cookieAcceptanceIsHidden = true;
+
+ // Can if can show
+ if(self.canShow === true)
+
+ // Show
+ self.show();
+
+ }, InstallApp.COOKIE_ACCEPTANCE_HIDE_BEFORE_SHOW_DELAY_MILLISECONDS);
+ });
+
+ // Window before install prompt event
+ $(window).on("beforeinstallprompt", function(event) {
+
+ // Prevent default
+ event.preventDefault();
+
+ // Store install app prompt
+ installAppPrompt = event["originalEvent"];
+
+ // Can if can show
+ if(self.canShow === true)
+
+ // Show
+ self.show();
+ });
+
+ // Install app display transaition end event
+ this.installAppDisplay.on("transitionend", function() {
+
+ // Check if install app display is hiding
+ if(self.installAppDisplay.hasClass("hide") === true)
+
+ // Prevent focus on install app display's elements
+ self.installAppDisplay.addClass("noFocus");
+ });
+
+ // Window app installed event
+ $(window).on("appinstalled", function(event) {
+
+ // Hide
+ self.hide();
+ });
+
+ // Window storage event
+ $(window).on("storage", function(event) {
+
+ // Check if remind me later timestamp was changed
+ if(event["originalEvent"]["key"] === InstallApp.INSTALL_APP_REMIND_ME_LATER_TIMESTAMP_LOCAL_STORAGE_NAME)
+
+ // Hide
+ self.hide();
+ });
+
+ // Install now button click event
+ this.installNowButton.on("click", function() {
+
+ // Check if the install app prompt exists
+ if(installAppPrompt !== InstallApp.NO_INSTALL_APP_PROMPT) {
+
+ // Get if automatic lock is enabled
+ var automaticLockEnabled = self.automaticLock.getAllowed() !== 0;
+
+ // Check if automatic lock is enabled
+ if(automaticLockEnabled === true) {
+
+ // Prevent automatic lock
+ self.automaticLock.prevent();
+ }
+
+ // Prompt to install app
+ installAppPrompt.prompt();
+
+ // Get if app was installed
+ installAppPrompt["userChoice"].then(function(result) {
+
+ // Check if automatic lock is enabled
+ if(automaticLockEnabled === true) {
+
+ // Allow automatic lock
+ self.automaticLock.allow();
+ }
+
+ // Check if app was installed
+ if(result["outcome"] === InstallApp.APP_INSTALLED_OUTCOME) {
+
+ // Set was installed
+ self.wasInstalled = true;
+
+ // Hide
+ self.hide();
+ }
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Check if automatic lock is enabled
+ if(automaticLockEnabled === true) {
+
+ // Allow automatic lock
+ self.automaticLock.allow();
+ }
+ });
+ }
+ });
+
+ // Remind me later button click event
+ this.remindMeLaterButton.on("click", function() {
+
+ // Hide
+ self.hide();
+
+ // Try
+ try {
+
+ // Save current timestamp as the remind me later timestamp
+ localStorage.setItem(InstallApp.INSTALL_APP_REMIND_ME_LATER_TIMESTAMP_LOCAL_STORAGE_NAME, Common.getCurrentTimestamp().toFixed());
+ }
+
+ // Catch errors
+ catch(error) {
+
+ // Trigger a fatal error
+ new FatalError(FatalError.LOCAL_STORAGE_ERROR);
+ }
+
+ // Remind me later hover in event
+ }).hover(function() {
+
+ // Check if can hover
+ if(typeof matchMedia === "function" && matchMedia("(any-hover: hover)")["matches"] === true) {
+
+ // Get element
+ var element = $(this);
+
+ // Check if element's text is shown
+ if(element.children().is(":visible") === true) {
+
+ // Save element's title
+ element.attr(Common.DATA_ATTRIBUTE_PREFIX + "title", element.attr("title"));
+
+ // Remove element's title
+ element.removeAttr("title");
+ }
+ }
+
+ // Remind me later hover out event
+ }, function() {
+
+ // Check if can hover
+ if(typeof matchMedia === "function" && matchMedia("(any-hover: hover)")["matches"] === true) {
+
+ // Get element
+ var element = $(this);
+
+ // Check if element isn't focused
+ if(element.is(":focus") === false) {
+
+ // Check if element's title is saved
+ if(element.attr(Common.DATA_ATTRIBUTE_PREFIX + "title") !== Common.NO_ATTRIBUTE) {
+
+ // Restore element's title
+ element.attr("title", element.attr(Common.DATA_ATTRIBUTE_PREFIX + "title"));
+
+ // Remove element's saved title
+ element.removeAttr(Common.DATA_ATTRIBUTE_PREFIX + "title");
+ }
+ }
+ }
+
+ // Remind me later focus event
+ }).on("focus", function() {
+
+ // Get element
+ var element = $(this);
+
+ // Check if element's text is shown
+ if(element.children().is(":visible") === true) {
+
+ // Save element's title
+ element.attr(Common.DATA_ATTRIBUTE_PREFIX + "title", element.attr("title"));
+
+ // Remove element's title
+ element.removeAttr("title");
+ }
+
+ // Remind me later blur event
+ }).on("blur", function() {
+
+ // Get element
+ var element = $(this);
+
+ // Check if can't hover or element isn't hovered
+ if((typeof matchMedia !== "function" || matchMedia("(any-hover: hover)")["matches"] === false) || element.is(":hover") === false) {
+
+ // Check if element's title is saved
+ if(element.attr(Common.DATA_ATTRIBUTE_PREFIX + "title") !== Common.NO_ATTRIBUTE) {
+
+ // Restore element's title
+ element.attr("title", element.attr(Common.DATA_ATTRIBUTE_PREFIX + "title"));
+
+ // Remove element's saved title
+ element.removeAttr(Common.DATA_ATTRIBUTE_PREFIX + "title");
+ }
+ }
+ });
+ }
+
+ // Show
+ show() {
+
+ // Set can show
+ this.canShow = true;
+
+ // Check if not an app or extension and not loading from file
+ if(Common.isApp() === false && Common.isExtension() === false && location["protocol"] !== Common.FILE_PROTOCOL) {
+
+ // Check if the install app prompt exists, cookie acceptance is hidden, and wasn't installed
+ if(installAppPrompt !== InstallApp.NO_INSTALL_APP_PROMPT && this.cookieAcceptanceIsHidden === true && this.wasInstalled === false) {
+
+ // Get remind me later timestamp
+ var remindMeLaterTimestamp = localStorage.getItem(InstallApp.INSTALL_APP_REMIND_ME_LATER_TIMESTAMP_LOCAL_STORAGE_NAME);
+
+ // Check if it's time to remind about installing the app
+ if(remindMeLaterTimestamp === Common.INVALID_LOCAL_STORAGE_ITEM || parseInt(remindMeLaterTimestamp, Common.DECIMAL_NUMBER_BASE) <= Common.getCurrentTimestamp() - InstallApp.REMIND_ME_LATER_DURATION_SECONDS) {
+
+ // Show install app display and make it so that its elements can be focused
+ this.installAppDisplay.removeClass("hide noFocus");
+
+ // Return true
+ return true;
+ }
+ }
+ }
+
+ // Return false
+ return false;
+ }
+
+ // No install app prompt
+ static get NO_INSTALL_APP_PROMPT() {
+
+ // Return no install app prompt
+ return null;
+ }
+
+ // Private
+
+ // Hide
+ hide() {
+
+ // Set install app prompt to no install app prompt
+ this.installAppPrompt = InstallApp.NO_INSTALL_APP_PROMPT;
+
+ // Hide install app display
+ this.installAppDisplay.addClass("hide");
+ }
+
+ // Remind me later duration seconds
+ static get REMIND_ME_LATER_DURATION_SECONDS() {
+
+ // Return remind me later duration seconds
+ return 90 * Common.HOURS_IN_A_DAY * Common.MINUTES_IN_AN_HOUR * Common.SECONDS_IN_A_MINUTE;
+ }
+
+ // Install app remind me later timestamp local storage name
+ static get INSTALL_APP_REMIND_ME_LATER_TIMESTAMP_LOCAL_STORAGE_NAME() {
+
+ // Return install app remind me later timestamp local storage name
+ return "Install App Remind Me Later Timestamp";
+ }
+
+ // App install outcome
+ static get APP_INSTALLED_OUTCOME() {
+
+ // Return app installed outcome
+ return "accepted";
+ }
+
+ // Return cookie acceptance hide before show delay milliseconds
+ static get COOKIE_ACCEPTANCE_HIDE_BEFORE_SHOW_DELAY_MILLISECONDS() {
+
+ // Return cookie acceptance hide before show delay milliseconds
+ return 100;
+ }
+}
+
+
+// Global variables
+
+// Install app prompt
+var installAppPrompt = InstallApp.NO_INSTALL_APP_PROMPT;
+
+
+// Main function
+
+// Set global object's install app
+globalThis["InstallApp"] = InstallApp;
+
+// Window before install prompt event
+$(window).on("beforeinstallprompt", function(event) {
+
+ // Prevent default
+ event.preventDefault();
+
+ // Store install app prompt
+ installAppPrompt = event["originalEvent"];
+});
diff --git a/scripts/instance.js b/scripts/instance.js
new file mode 100755
index 0000000..75e3c0e
--- /dev/null
+++ b/scripts/instance.js
@@ -0,0 +1,268 @@
+// Use strict
+"use strict";
+
+
+// Classes
+
+// Instance class
+class Instance {
+
+ // Public
+
+ // Constructor
+ constructor() {
+
+ // Set current instance ID to random value
+ this.currentInstanceId = Math.random().toFixed(Instance.MAX_FIXED_NUMBER_OF_DECIMAL_PLACES);
+
+ // Set primary instance ID to stored value
+ this.primaryInstanceId = localStorage.getItem(Instance.PRIMARY_INSTANCE_ID_LOCAL_STORAGE_NAME);
+
+ // Set check primary instance timeout
+ this.checkPrimaryInstanceTimeout = Instance.NO_TIMEOUT;
+
+ // Set self
+ var self = this;
+
+ // Try
+ try {
+
+ // Save current instance ID as the primary instance ID
+ localStorage.setItem(Instance.PRIMARY_INSTANCE_ID_LOCAL_STORAGE_NAME, this.currentInstanceId);
+ }
+
+ // Catch errors
+ catch(error) {
+
+ // Throw error
+ throw "Local storage failed.";
+ }
+
+ // Set check primary instance timeout
+ this.checkPrimaryInstanceTimeout = setTimeout(function() {
+
+ // Check if primary instance
+ self.checkIfPrimaryInstance();
+
+ }, Instance.CHECK_IF_PRIMARY_INSTANCE_DELAY_MILLISECONDS);
+
+ // Window storage instance event
+ $(window).on("storage.instance", function(event) {
+
+ // Check if primary instance ID was changed
+ if(event["originalEvent"]["key"] === Instance.PRIMARY_INSTANCE_ID_LOCAL_STORAGE_NAME) {
+
+ // Check if primary instance was closed
+ if(event["originalEvent"]["newValue"] === null || event["originalEvent"]["newValue"] === "") {
+
+ // Try
+ try {
+
+ // Save current instance ID as the primary instance ID
+ localStorage.setItem(Instance.PRIMARY_INSTANCE_ID_LOCAL_STORAGE_NAME, self.currentInstanceId);
+ }
+
+ // Catch errors
+ catch(error) {
+
+ // Return
+ return;
+ }
+
+ // Check if check primary instance timeout exists
+ if(self.checkPrimaryInstanceTimeout !== Instance.NO_TIMEOUT) {
+
+ // Clear check primary instance timeout
+ clearTimeout(self.checkPrimaryInstanceTimeout);
+
+ // Clear check primary instance timeout
+ self.checkPrimaryInstanceTimeout = Instance.NO_TIMEOUT;
+ }
+
+ // Set check primary instance timeout
+ self.checkPrimaryInstanceTimeout = setTimeout(function() {
+
+ // Check if primary instance
+ self.checkIfPrimaryInstance();
+
+ }, Instance.CHECK_IF_PRIMARY_INSTANCE_DELAY_MILLISECONDS);
+ }
+
+ // Otherwise check if current instance ID is the primary instance ID and a new instance is trying to become the primary instance
+ else if(self.currentInstanceId === self.primaryInstanceId && event["originalEvent"]["newValue"] !== self.currentInstanceId) {
+
+ // Try
+ try {
+
+ // Save current instance ID as the primary instance ID
+ localStorage.setItem(Instance.PRIMARY_INSTANCE_ID_LOCAL_STORAGE_NAME, self.currentInstanceId);
+ }
+
+ // Catch errors
+ catch(error) {
+
+ // Trigger a fatal error
+ new FatalError(FatalError.LOCAL_STORAGE_ERROR);
+ }
+ }
+
+ // Otherwise check if new instance isn't the current instance ID
+ else if(event["originalEvent"]["newValue"] !== self.currentInstanceId)
+
+ // Update primary instance ID
+ self.primaryInstanceId = event["originalEvent"]["newValue"];
+ }
+
+ // Window focus instance event
+ }).on("focus.instance", function() {
+
+ // Check if current instance ID isn't the primary instance ID
+ if(self.currentInstanceId !== self.primaryInstanceId) {
+
+ // Set timeout
+ setTimeout(function() {
+
+ // Try
+ try {
+
+ // Save current instance ID as the primary instance ID
+ localStorage.setItem(Instance.PRIMARY_INSTANCE_ID_LOCAL_STORAGE_NAME, self.currentInstanceId);
+ }
+
+ // Catch errors
+ catch(error) {
+
+ // Return
+ return;
+ }
+
+ // Check if check primary instance timeout exists
+ if(self.checkPrimaryInstanceTimeout !== Instance.NO_TIMEOUT) {
+
+ // Clear check primary instance timeout
+ clearTimeout(self.checkPrimaryInstanceTimeout);
+
+ // Clear check primary instance timeout
+ self.checkPrimaryInstanceTimeout = Instance.NO_TIMEOUT;
+ }
+
+ // Set check primary instance timeout
+ self.checkPrimaryInstanceTimeout = setTimeout(function() {
+
+ // Check if primary instance
+ self.checkIfPrimaryInstance();
+
+ }, Instance.CHECK_IF_PRIMARY_INSTANCE_DELAY_MILLISECONDS);
+
+ }, Instance.FOCUS_DELAY_MILLISECONDS);
+ }
+
+ // Window before unload instance and unload instance event
+ }).on("beforeunload.instance unload.instance", function() {
+
+ // Turn off window before unload instance and unload instance event
+ $(window).off("beforeunload.instance unload.instance");
+
+ // Turn off window storage instance event
+ $(window).off("storage.instance");
+
+ // Turn off window focus instance event
+ $(window).off("focus.instance");
+
+ // Check if current instance ID is the primary instance ID
+ if(self.currentInstanceId === self.primaryInstanceId)
+
+ // Remove saved primary instance ID
+ localStorage.removeItem(Instance.PRIMARY_INSTANCE_ID_LOCAL_STORAGE_NAME);
+ });
+ }
+
+ // Is primary instance
+ isPrimaryInstance() {
+
+ // Set self
+ var self = this;
+
+ // Return promise
+ return new Promise(function(resolve, reject) {
+
+ // Set timeout
+ setTimeout(function() {
+
+ // Resolve if current instance ID is the primary instance ID
+ resolve(self.currentInstanceId === self.primaryInstanceId);
+
+ }, Instance.CHECK_IF_PRIMARY_INSTANCE_DELAY_MILLISECONDS);
+ });
+ }
+
+ // Is primary instance event
+ static get IS_PRIMARY_INSTANCE_EVENT() {
+
+ // Return is primary instance event
+ return "InstanceIsPrimaryInstanceEvent";
+ }
+
+ // Private
+
+ // Check if primary instance
+ checkIfPrimaryInstance() {
+
+ // Clear check primary instance timeout
+ this.checkPrimaryInstanceTimeout = Instance.NO_TIMEOUT;
+
+ // Get the saved primary instance ID
+ var savedPrimaryInstanceId = localStorage.getItem(Instance.PRIMARY_INSTANCE_ID_LOCAL_STORAGE_NAME);
+
+ // Check if current instance ID is the saved primary instance ID
+ if(savedPrimaryInstanceId !== Common.INVALID_LOCAL_STORAGE_ITEM && this.currentInstanceId === savedPrimaryInstanceId) {
+
+ // Set primary instance ID to current instance ID
+ this.primaryInstanceId = this.currentInstanceId;
+
+ // Trigger is primary instance event
+ $(this).trigger(Instance.IS_PRIMARY_INSTANCE_EVENT);
+ }
+ }
+
+ // Primary instance ID local storage name
+ static get PRIMARY_INSTANCE_ID_LOCAL_STORAGE_NAME() {
+
+ // Return primary instance ID local storage name
+ return "Primary Instance ID";
+ }
+
+ // Check if primary instance delay milliseconds
+ static get CHECK_IF_PRIMARY_INSTANCE_DELAY_MILLISECONDS() {
+
+ // Return check if primary instance delay milliseconds
+ return 600;
+ }
+
+ // Focus delay milliseconds
+ static get FOCUS_DELAY_MILLISECONDS() {
+
+ // Return focus delay milliseconds
+ return 100;
+ }
+
+ // No timeout
+ static get NO_TIMEOUT() {
+
+ // Return no timeout
+ return null;
+ }
+
+ // Max fixed number of decimal places
+ static get MAX_FIXED_NUMBER_OF_DECIMAL_PLACES() {
+
+ // Return max fixed number of decimal places
+ return 20;
+ }
+}
+
+
+// Main function
+
+// Set global object's instance
+globalThis["Instance"] = Instance;
diff --git a/scripts/interaction.js b/scripts/interaction.js
new file mode 100755
index 0000000..6bf0752
--- /dev/null
+++ b/scripts/interaction.js
@@ -0,0 +1,226 @@
+// Use strict
+"use strict";
+
+
+// Classes
+
+// Interaction class
+class Interaction {
+
+ // Public
+
+ // Constructor
+ constructor(index, urlOrWalletKeyPath, api, type, data, listener = Interaction.NO_LISTENER) {
+
+ // Check if a URL is provided
+ if(typeof urlOrWalletKeyPath === "string") {
+
+ // Set URL
+ var url = urlOrWalletKeyPath;
+
+ // Set wallet key path
+ var walletKeyPath = Interaction.NO_WALLET_KEY_PATH;
+ }
+
+ // Otherwise
+ else {
+
+ // Set URL
+ var url = Interaction.NO_URL;
+
+ // Set wallet key path
+ var walletKeyPath = urlOrWalletKeyPath;
+ }
+
+ // Set index
+ this.index = index;
+
+ // Set URL
+ this.url = url;
+
+ // Set wallet key path
+ this.walletKeyPath = walletKeyPath;
+
+ // Set API
+ this.api = api;
+
+ // Set type
+ this.type = type;
+
+ // Set data
+ this.data = data;
+
+ // Set listener
+ this.listener = listener;
+
+ // Set canceled
+ this.canceled = false;
+ }
+
+ // Get URL
+ getUrl() {
+
+ // Return URL
+ return this.url;
+ }
+
+ // Get wallet key path
+ getWalletKeyPath() {
+
+ // Return wallet key path
+ return this.walletKeyPath;
+ }
+
+ // Get API
+ getApi() {
+
+ // Return API
+ return this.api;
+ }
+
+ // Get type
+ getType() {
+
+ // Return type
+ return this.type;
+ }
+
+ // Get data
+ getData() {
+
+ // Return data
+ return this.data;
+ }
+
+ // Set canceled
+ setCanceled() {
+
+ // Set canceled
+ this.canceled = true;
+ }
+
+ // Is canceled
+ isCanceled() {
+
+ // Return if canceled
+ return this.canceled === true;
+ }
+
+ // Respond
+ respond(response) {
+
+ // Set self
+ var self = this;
+
+ // Return promise
+ return new Promise(function(resolve, reject) {
+
+ // Check if canceled
+ if(self.isCanceled() === true) {
+
+ // Reject error
+ reject("Interaction canceled.");
+ }
+
+ // Otherwise check if a listener is used
+ else if(self.listener !== Interaction.NO_LISTENER) {
+
+ // Return responding with data to listener
+ return self.listener.respondWithData(self.index, response).then(function(status) {
+
+ // Resolve status
+ resolve(status);
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Reject error
+ reject(error);
+ });
+ }
+
+ // Otherwise
+ else {
+
+ // Resolve
+ resolve();
+ }
+ });
+ }
+
+ // Cancel
+ cancel(response) {
+
+ // Set self
+ var self = this;
+
+ // Return promise
+ return new Promise(function(resolve, reject) {
+
+ // Check if canceled
+ if(self.isCanceled() === true) {
+
+ // Reject error
+ reject("Interaction canceled.");
+ }
+
+ // Otherwise check if a listener is used
+ else if(self.listener !== Interaction.NO_LISTENER) {
+
+ // Return responding with error to listener
+ return self.listener.respondWithError(self.index, response).then(function(status) {
+
+ // Resolve status
+ resolve(status);
+
+ // Catch errors
+ }).catch(function(error) {
+
+ // Reject error
+ reject(error);
+ });
+ }
+
+ // Otherwise
+ else {
+
+ // Resolve
+ resolve();
+ }
+ });
+ }
+
+ // No index
+ static get NO_INDEX() {
+
+ // Return no index
+ return null;
+ }
+
+ // No listener
+ static get NO_LISTENER() {
+
+ // Return no listener
+ return null;
+ }
+
+ // No URL
+ static get NO_URL() {
+
+ // Return no URL
+ return null;
+ }
+
+ // No wallet key path
+ static get NO_WALLET_KEY_PATH() {
+
+ // Return no wallet key path
+ return null;
+ }
+}
+
+
+// Main function
+
+// Set global object's interaction
+globalThis["Interaction"] = Interaction;
diff --git a/scripts/jQuery license.txt b/scripts/jQuery license.txt
new file mode 100755
index 0000000..f642c3f
--- /dev/null
+++ b/scripts/jQuery license.txt
@@ -0,0 +1,20 @@
+Copyright OpenJS Foundation and other contributors, https://openjsf.org/
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/scripts/jQuery-3.6.4.js b/scripts/jQuery-3.6.4.js
new file mode 100755
index 0000000..7f35c11
--- /dev/null
+++ b/scripts/jQuery-3.6.4.js
@@ -0,0 +1,10965 @@
+/*!
+ * jQuery JavaScript Library v3.6.4
+ * https://jquery.com/
+ *
+ * Includes Sizzle.js
+ * https://sizzlejs.com/
+ *
+ * Copyright OpenJS Foundation and other contributors
+ * Released under the MIT license
+ * https://jquery.org/license
+ *
+ * Date: 2023-03-08T15:28Z
+ */
+( function( global, factory ) {
+
+ "use strict";
+
+ if ( typeof module === "object" && typeof module.exports === "object" ) {
+
+ // For CommonJS and CommonJS-like environments where a proper `window`
+ // is present, execute the factory and get jQuery.
+ // For environments that do not have a `window` with a `document`
+ // (such as Node.js), expose a factory as module.exports.
+ // This accentuates the need for the creation of a real `window`.
+ // e.g. var jQuery = require("jquery")(window);
+ // See ticket trac-14549 for more info.
+ module.exports = global.document ?
+ factory( global, true ) :
+ function( w ) {
+ if ( !w.document ) {
+ throw new Error( "jQuery requires a window with a document" );
+ }
+ return factory( w );
+ };
+ } else {
+ factory( global );
+ }
+
+// Pass this if window is not defined yet
+} )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) {
+
+// Edge <= 12 - 13+, Firefox <=18 - 45+, IE 10 - 11, Safari 5.1 - 9+, iOS 6 - 9.1
+// throw exceptions when non-strict code (e.g., ASP.NET 4.5) accesses strict mode
+// arguments.callee.caller (trac-13335). But as of jQuery 3.0 (2016), strict mode should be common
+// enough that all such attempts are guarded in a try block.
+"use strict";
+
+var arr = [];
+
+var getProto = Object.getPrototypeOf;
+
+var slice = arr.slice;
+
+var flat = arr.flat ? function( array ) {
+ return arr.flat.call( array );
+} : function( array ) {
+ return arr.concat.apply( [], array );
+};
+
+
+var push = arr.push;
+
+var indexOf = arr.indexOf;
+
+var class2type = {};
+
+var toString = class2type.toString;
+
+var hasOwn = class2type.hasOwnProperty;
+
+var fnToString = hasOwn.toString;
+
+var ObjectFunctionString = fnToString.call( Object );
+
+var support = {};
+
+var isFunction = function isFunction( obj ) {
+
+ // Support: Chrome <=57, Firefox <=52
+ // In some browsers, typeof returns "function" for HTML
elements
+ // (i.e., `typeof document.createElement( "object" ) === "function"`).
+ // We don't want to classify *any* DOM node as a function.
+ // Support: QtWeb <=3.8.5, WebKit <=534.34, wkhtmltopdf tool <=0.12.5
+ // Plus for old WebKit, typeof returns "function" for HTML collections
+ // (e.g., `typeof document.getElementsByTagName("div") === "function"`). (gh-4756)
+ return typeof obj === "function" && typeof obj.nodeType !== "number" &&
+ typeof obj.item !== "function";
+ };
+
+
+var isWindow = function isWindow( obj ) {
+ return obj != null && obj === obj.window;
+ };
+
+
+var document = window.document;
+
+
+
+ var preservedScriptAttributes = {
+ type: true,
+ src: true,
+ nonce: true,
+ noModule: true
+ };
+
+ function DOMEval( code, node, doc ) {
+ doc = doc || document;
+
+ var i, val,
+ script = doc.createElement( "script" );
+
+ script.text = code;
+ if ( node ) {
+ for ( i in preservedScriptAttributes ) {
+
+ // Support: Firefox 64+, Edge 18+
+ // Some browsers don't support the "nonce" property on scripts.
+ // On the other hand, just using `getAttribute` is not enough as
+ // the `nonce` attribute is reset to an empty string whenever it
+ // becomes browsing-context connected.
+ // See https://github.com/whatwg/html/issues/2369
+ // See https://html.spec.whatwg.org/#nonce-attributes
+ // The `node.getAttribute` check was added for the sake of
+ // `jQuery.globalEval` so that it can fake a nonce-containing node
+ // via an object.
+ val = node[ i ] || node.getAttribute && node.getAttribute( i );
+ if ( val ) {
+ script.setAttribute( i, val );
+ }
+ }
+ }
+ doc.head.appendChild( script ).parentNode.removeChild( script );
+ }
+
+
+function toType( obj ) {
+ if ( obj == null ) {
+ return obj + "";
+ }
+
+ // Support: Android <=2.3 only (functionish RegExp)
+ return typeof obj === "object" || typeof obj === "function" ?
+ class2type[ toString.call( obj ) ] || "object" :
+ typeof obj;
+}
+/* global Symbol */
+// Defining this global in .eslintrc.json would create a danger of using the global
+// unguarded in another place, it seems safer to define global only for this module
+
+
+
+var
+ version = "3.6.4",
+
+ // Define a local copy of jQuery
+ jQuery = function( selector, context ) {
+
+ // The jQuery object is actually just the init constructor 'enhanced'
+ // Need init if jQuery is called (just allow error to be thrown if not included)
+ return new jQuery.fn.init( selector, context );
+ };
+
+jQuery.fn = jQuery.prototype = {
+
+ // The current version of jQuery being used
+ jquery: version,
+
+ constructor: jQuery,
+
+ // The default length of a jQuery object is 0
+ length: 0,
+
+ toArray: function() {
+ return slice.call( this );
+ },
+
+ // Get the Nth element in the matched element set OR
+ // Get the whole matched element set as a clean array
+ get: function( num ) {
+
+ // Return all the elements in a clean array
+ if ( num == null ) {
+ return slice.call( this );
+ }
+
+ // Return just the one element from the set
+ return num < 0 ? this[ num + this.length ] : this[ num ];
+ },
+
+ // Take an array of elements and push it onto the stack
+ // (returning the new matched element set)
+ pushStack: function( elems ) {
+
+ // Build a new jQuery matched element set
+ var ret = jQuery.merge( this.constructor(), elems );
+
+ // Add the old object onto the stack (as a reference)
+ ret.prevObject = this;
+
+ // Return the newly-formed element set
+ return ret;
+ },
+
+ // Execute a callback for every element in the matched set.
+ each: function( callback ) {
+ return jQuery.each( this, callback );
+ },
+
+ map: function( callback ) {
+ return this.pushStack( jQuery.map( this, function( elem, i ) {
+ return callback.call( elem, i, elem );
+ } ) );
+ },
+
+ slice: function() {
+ return this.pushStack( slice.apply( this, arguments ) );
+ },
+
+ first: function() {
+ return this.eq( 0 );
+ },
+
+ last: function() {
+ return this.eq( -1 );
+ },
+
+ even: function() {
+ return this.pushStack( jQuery.grep( this, function( _elem, i ) {
+ return ( i + 1 ) % 2;
+ } ) );
+ },
+
+ odd: function() {
+ return this.pushStack( jQuery.grep( this, function( _elem, i ) {
+ return i % 2;
+ } ) );
+ },
+
+ eq: function( i ) {
+ var len = this.length,
+ j = +i + ( i < 0 ? len : 0 );
+ return this.pushStack( j >= 0 && j < len ? [ this[ j ] ] : [] );
+ },
+
+ end: function() {
+ return this.prevObject || this.constructor();
+ },
+
+ // For internal use only.
+ // Behaves like an Array's method, not like a jQuery method.
+ push: push,
+ sort: arr.sort,
+ splice: arr.splice
+};
+
+jQuery.extend = jQuery.fn.extend = function() {
+ var options, name, src, copy, copyIsArray, clone,
+ target = arguments[ 0 ] || {},
+ i = 1,
+ length = arguments.length,
+ deep = false;
+
+ // Handle a deep copy situation
+ if ( typeof target === "boolean" ) {
+ deep = target;
+
+ // Skip the boolean and the target
+ target = arguments[ i ] || {};
+ i++;
+ }
+
+ // Handle case when target is a string or something (possible in deep copy)
+ if ( typeof target !== "object" && !isFunction( target ) ) {
+ target = {};
+ }
+
+ // Extend jQuery itself if only one argument is passed
+ if ( i === length ) {
+ target = this;
+ i--;
+ }
+
+ for ( ; i < length; i++ ) {
+
+ // Only deal with non-null/undefined values
+ if ( ( options = arguments[ i ] ) != null ) {
+
+ // Extend the base object
+ for ( name in options ) {
+ copy = options[ name ];
+
+ // Prevent Object.prototype pollution
+ // Prevent never-ending loop
+ if ( name === "__proto__" || target === copy ) {
+ continue;
+ }
+
+ // Recurse if we're merging plain objects or arrays
+ if ( deep && copy && ( jQuery.isPlainObject( copy ) ||
+ ( copyIsArray = Array.isArray( copy ) ) ) ) {
+ src = target[ name ];
+
+ // Ensure proper type for the source value
+ if ( copyIsArray && !Array.isArray( src ) ) {
+ clone = [];
+ } else if ( !copyIsArray && !jQuery.isPlainObject( src ) ) {
+ clone = {};
+ } else {
+ clone = src;
+ }
+ copyIsArray = false;
+
+ // Never move original objects, clone them
+ target[ name ] = jQuery.extend( deep, clone, copy );
+
+ // Don't bring in undefined values
+ } else if ( copy !== undefined ) {
+ target[ name ] = copy;
+ }
+ }
+ }
+ }
+
+ // Return the modified object
+ return target;
+};
+
+jQuery.extend( {
+
+ // Unique for each copy of jQuery on the page
+ expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ),
+
+ // Assume jQuery is ready without the ready module
+ isReady: true,
+
+ error: function( msg ) {
+ throw new Error( msg );
+ },
+
+ noop: function() {},
+
+ isPlainObject: function( obj ) {
+ var proto, Ctor;
+
+ // Detect obvious negatives
+ // Use toString instead of jQuery.type to catch host objects
+ if ( !obj || toString.call( obj ) !== "[object Object]" ) {
+ return false;
+ }
+
+ proto = getProto( obj );
+
+ // Objects with no prototype (e.g., `Object.create( null )`) are plain
+ if ( !proto ) {
+ return true;
+ }
+
+ // Objects with prototype are plain iff they were constructed by a global Object function
+ Ctor = hasOwn.call( proto, "constructor" ) && proto.constructor;
+ return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString;
+ },
+
+ isEmptyObject: function( obj ) {
+ var name;
+
+ for ( name in obj ) {
+ return false;
+ }
+ return true;
+ },
+
+ // Evaluates a script in a provided context; falls back to the global one
+ // if not specified.
+ globalEval: function( code, options, doc ) {
+ DOMEval( code, { nonce: options && options.nonce }, doc );
+ },
+
+ each: function( obj, callback ) {
+ var length, i = 0;
+
+ if ( isArrayLike( obj ) ) {
+ length = obj.length;
+ for ( ; i < length; i++ ) {
+ if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) {
+ break;
+ }
+ }
+ } else {
+ for ( i in obj ) {
+ if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) {
+ break;
+ }
+ }
+ }
+
+ return obj;
+ },
+
+ // results is for internal usage only
+ makeArray: function( arr, results ) {
+ var ret = results || [];
+
+ if ( arr != null ) {
+ if ( isArrayLike( Object( arr ) ) ) {
+ jQuery.merge( ret,
+ typeof arr === "string" ?
+ [ arr ] : arr
+ );
+ } else {
+ push.call( ret, arr );
+ }
+ }
+
+ return ret;
+ },
+
+ inArray: function( elem, arr, i ) {
+ return arr == null ? -1 : indexOf.call( arr, elem, i );
+ },
+
+ // Support: Android <=4.0 only, PhantomJS 1 only
+ // push.apply(_, arraylike) throws on ancient WebKit
+ merge: function( first, second ) {
+ var len = +second.length,
+ j = 0,
+ i = first.length;
+
+ for ( ; j < len; j++ ) {
+ first[ i++ ] = second[ j ];
+ }
+
+ first.length = i;
+
+ return first;
+ },
+
+ grep: function( elems, callback, invert ) {
+ var callbackInverse,
+ matches = [],
+ i = 0,
+ length = elems.length,
+ callbackExpect = !invert;
+
+ // Go through the array, only saving the items
+ // that pass the validator function
+ for ( ; i < length; i++ ) {
+ callbackInverse = !callback( elems[ i ], i );
+ if ( callbackInverse !== callbackExpect ) {
+ matches.push( elems[ i ] );
+ }
+ }
+
+ return matches;
+ },
+
+ // arg is for internal usage only
+ map: function( elems, callback, arg ) {
+ var length, value,
+ i = 0,
+ ret = [];
+
+ // Go through the array, translating each of the items to their new values
+ if ( isArrayLike( elems ) ) {
+ length = elems.length;
+ for ( ; i < length; i++ ) {
+ value = callback( elems[ i ], i, arg );
+
+ if ( value != null ) {
+ ret.push( value );
+ }
+ }
+
+ // Go through every key on the object,
+ } else {
+ for ( i in elems ) {
+ value = callback( elems[ i ], i, arg );
+
+ if ( value != null ) {
+ ret.push( value );
+ }
+ }
+ }
+
+ // Flatten any nested arrays
+ return flat( ret );
+ },
+
+ // A global GUID counter for objects
+ guid: 1,
+
+ // jQuery.support is not used in Core but other projects attach their
+ // properties to it so it needs to exist.
+ support: support
+} );
+
+if ( typeof Symbol === "function" ) {
+ jQuery.fn[ Symbol.iterator ] = arr[ Symbol.iterator ];
+}
+
+// Populate the class2type map
+jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " ),
+ function( _i, name ) {
+ class2type[ "[object " + name + "]" ] = name.toLowerCase();
+ } );
+
+function isArrayLike( obj ) {
+
+ // Support: real iOS 8.2 only (not reproducible in simulator)
+ // `in` check used to prevent JIT error (gh-2145)
+ // hasOwn isn't used here due to false negatives
+ // regarding Nodelist length in IE
+ var length = !!obj && "length" in obj && obj.length,
+ type = toType( obj );
+
+ if ( isFunction( obj ) || isWindow( obj ) ) {
+ return false;
+ }
+
+ return type === "array" || length === 0 ||
+ typeof length === "number" && length > 0 && ( length - 1 ) in obj;
+}
+var Sizzle =
+/*!
+ * Sizzle CSS Selector Engine v2.3.10
+ * https://sizzlejs.com/
+ *
+ * Copyright JS Foundation and other contributors
+ * Released under the MIT license
+ * https://js.foundation/
+ *
+ * Date: 2023-02-14
+ */
+( function( window ) {
+var i,
+ support,
+ Expr,
+ getText,
+ isXML,
+ tokenize,
+ compile,
+ select,
+ outermostContext,
+ sortInput,
+ hasDuplicate,
+
+ // Local document vars
+ setDocument,
+ document,
+ docElem,
+ documentIsHTML,
+ rbuggyQSA,
+ rbuggyMatches,
+ matches,
+ contains,
+
+ // Instance-specific data
+ expando = "sizzle" + 1 * new Date(),
+ preferredDoc = window.document,
+ dirruns = 0,
+ done = 0,
+ classCache = createCache(),
+ tokenCache = createCache(),
+ compilerCache = createCache(),
+ nonnativeSelectorCache = createCache(),
+ sortOrder = function( a, b ) {
+ if ( a === b ) {
+ hasDuplicate = true;
+ }
+ return 0;
+ },
+
+ // Instance methods
+ hasOwn = ( {} ).hasOwnProperty,
+ arr = [],
+ pop = arr.pop,
+ pushNative = arr.push,
+ push = arr.push,
+ slice = arr.slice,
+
+ // Use a stripped-down indexOf as it's faster than native
+ // https://jsperf.com/thor-indexof-vs-for/5
+ indexOf = function( list, elem ) {
+ var i = 0,
+ len = list.length;
+ for ( ; i < len; i++ ) {
+ if ( list[ i ] === elem ) {
+ return i;
+ }
+ }
+ return -1;
+ },
+
+ booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|" +
+ "ismap|loop|multiple|open|readonly|required|scoped",
+
+ // Regular expressions
+
+ // http://www.w3.org/TR/css3-selectors/#whitespace
+ whitespace = "[\\x20\\t\\r\\n\\f]",
+
+ // https://www.w3.org/TR/css-syntax-3/#ident-token-diagram
+ identifier = "(?:\\\\[\\da-fA-F]{1,6}" + whitespace +
+ "?|\\\\[^\\r\\n\\f]|[\\w-]|[^\0-\\x7f])+",
+
+ // Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors
+ attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace +
+
+ // Operator (capture 2)
+ "*([*^$|!~]?=)" + whitespace +
+
+ // "Attribute values must be CSS identifiers [capture 5]
+ // or strings [capture 3 or capture 4]"
+ "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" +
+ whitespace + "*\\]",
+
+ pseudos = ":(" + identifier + ")(?:\\((" +
+
+ // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments:
+ // 1. quoted (capture 3; capture 4 or capture 5)
+ "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" +
+
+ // 2. simple (capture 6)
+ "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" +
+
+ // 3. anything else (capture 2)
+ ".*" +
+ ")\\)|)",
+
+ // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter
+ rwhitespace = new RegExp( whitespace + "+", "g" ),
+ rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" +
+ whitespace + "+$", "g" ),
+
+ rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ),
+ rleadingCombinator = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace +
+ "*" ),
+ rdescend = new RegExp( whitespace + "|>" ),
+
+ rpseudo = new RegExp( pseudos ),
+ ridentifier = new RegExp( "^" + identifier + "$" ),
+
+ matchExpr = {
+ "ID": new RegExp( "^#(" + identifier + ")" ),
+ "CLASS": new RegExp( "^\\.(" + identifier + ")" ),
+ "TAG": new RegExp( "^(" + identifier + "|[*])" ),
+ "ATTR": new RegExp( "^" + attributes ),
+ "PSEUDO": new RegExp( "^" + pseudos ),
+ "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" +
+ whitespace + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" +
+ whitespace + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ),
+ "bool": new RegExp( "^(?:" + booleans + ")$", "i" ),
+
+ // For use in libraries implementing .is()
+ // We use this for POS matching in `select`
+ "needsContext": new RegExp( "^" + whitespace +
+ "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + whitespace +
+ "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" )
+ },
+
+ rhtml = /HTML$/i,
+ rinputs = /^(?:input|select|textarea|button)$/i,
+ rheader = /^h\d$/i,
+
+ rnative = /^[^{]+\{\s*\[native \w/,
+
+ // Easily-parseable/retrievable ID or TAG or CLASS selectors
+ rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,
+
+ rsibling = /[+~]/,
+
+ // CSS escapes
+ // http://www.w3.org/TR/CSS21/syndata.html#escaped-characters
+ runescape = new RegExp( "\\\\[\\da-fA-F]{1,6}" + whitespace + "?|\\\\([^\\r\\n\\f])", "g" ),
+ funescape = function( escape, nonHex ) {
+ var high = "0x" + escape.slice( 1 ) - 0x10000;
+
+ return nonHex ?
+
+ // Strip the backslash prefix from a non-hex escape sequence
+ nonHex :
+
+ // Replace a hexadecimal escape sequence with the encoded Unicode code point
+ // Support: IE <=11+
+ // For values outside the Basic Multilingual Plane (BMP), manually construct a
+ // surrogate pair
+ high < 0 ?
+ String.fromCharCode( high + 0x10000 ) :
+ String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 );
+ },
+
+ // CSS string/identifier serialization
+ // https://drafts.csswg.org/cssom/#common-serializing-idioms
+ rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,
+ fcssescape = function( ch, asCodePoint ) {
+ if ( asCodePoint ) {
+
+ // U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER
+ if ( ch === "\0" ) {
+ return "\uFFFD";
+ }
+
+ // Control characters and (dependent upon position) numbers get escaped as code points
+ return ch.slice( 0, -1 ) + "\\" +
+ ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " ";
+ }
+
+ // Other potentially-special ASCII characters get backslash-escaped
+ return "\\" + ch;
+ },
+
+ // Used for iframes
+ // See setDocument()
+ // Removing the function wrapper causes a "Permission Denied"
+ // error in IE
+ unloadHandler = function() {
+ setDocument();
+ },
+
+ inDisabledFieldset = addCombinator(
+ function( elem ) {
+ return elem.disabled === true && elem.nodeName.toLowerCase() === "fieldset";
+ },
+ { dir: "parentNode", next: "legend" }
+ );
+
+// Optimize for push.apply( _, NodeList )
+try {
+ push.apply(
+ ( arr = slice.call( preferredDoc.childNodes ) ),
+ preferredDoc.childNodes
+ );
+
+ // Support: Android<4.0
+ // Detect silently failing push.apply
+ // eslint-disable-next-line no-unused-expressions
+ arr[ preferredDoc.childNodes.length ].nodeType;
+} catch ( e ) {
+ push = { apply: arr.length ?
+
+ // Leverage slice if possible
+ function( target, els ) {
+ pushNative.apply( target, slice.call( els ) );
+ } :
+
+ // Support: IE<9
+ // Otherwise append directly
+ function( target, els ) {
+ var j = target.length,
+ i = 0;
+
+ // Can't trust NodeList.length
+ while ( ( target[ j++ ] = els[ i++ ] ) ) {}
+ target.length = j - 1;
+ }
+ };
+}
+
+function Sizzle( selector, context, results, seed ) {
+ var m, i, elem, nid, match, groups, newSelector,
+ newContext = context && context.ownerDocument,
+
+ // nodeType defaults to 9, since context defaults to document
+ nodeType = context ? context.nodeType : 9;
+
+ results = results || [];
+
+ // Return early from calls with invalid selector or context
+ if ( typeof selector !== "string" || !selector ||
+ nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) {
+
+ return results;
+ }
+
+ // Try to shortcut find operations (as opposed to filters) in HTML documents
+ if ( !seed ) {
+ setDocument( context );
+ context = context || document;
+
+ if ( documentIsHTML ) {
+
+ // If the selector is sufficiently simple, try using a "get*By*" DOM method
+ // (excepting DocumentFragment context, where the methods don't exist)
+ if ( nodeType !== 11 && ( match = rquickExpr.exec( selector ) ) ) {
+
+ // ID selector
+ if ( ( m = match[ 1 ] ) ) {
+
+ // Document context
+ if ( nodeType === 9 ) {
+ if ( ( elem = context.getElementById( m ) ) ) {
+
+ // Support: IE, Opera, Webkit
+ // TODO: identify versions
+ // getElementById can match elements by name instead of ID
+ if ( elem.id === m ) {
+ results.push( elem );
+ return results;
+ }
+ } else {
+ return results;
+ }
+
+ // Element context
+ } else {
+
+ // Support: IE, Opera, Webkit
+ // TODO: identify versions
+ // getElementById can match elements by name instead of ID
+ if ( newContext && ( elem = newContext.getElementById( m ) ) &&
+ contains( context, elem ) &&
+ elem.id === m ) {
+
+ results.push( elem );
+ return results;
+ }
+ }
+
+ // Type selector
+ } else if ( match[ 2 ] ) {
+ push.apply( results, context.getElementsByTagName( selector ) );
+ return results;
+
+ // Class selector
+ } else if ( ( m = match[ 3 ] ) && support.getElementsByClassName &&
+ context.getElementsByClassName ) {
+
+ push.apply( results, context.getElementsByClassName( m ) );
+ return results;
+ }
+ }
+
+ // Take advantage of querySelectorAll
+ if ( support.qsa &&
+ !nonnativeSelectorCache[ selector + " " ] &&
+ ( !rbuggyQSA || !rbuggyQSA.test( selector ) ) &&
+
+ // Support: IE 8 only
+ // Exclude object elements
+ ( nodeType !== 1 || context.nodeName.toLowerCase() !== "object" ) ) {
+
+ newSelector = selector;
+ newContext = context;
+
+ // qSA considers elements outside a scoping root when evaluating child or
+ // descendant combinators, which is not what we want.
+ // In such cases, we work around the behavior by prefixing every selector in the
+ // list with an ID selector referencing the scope context.
+ // The technique has to be used as well when a leading combinator is used
+ // as such selectors are not recognized by querySelectorAll.
+ // Thanks to Andrew Dupont for this technique.
+ if ( nodeType === 1 &&
+ ( rdescend.test( selector ) || rleadingCombinator.test( selector ) ) ) {
+
+ // Expand context for sibling selectors
+ newContext = rsibling.test( selector ) && testContext( context.parentNode ) ||
+ context;
+
+ // We can use :scope instead of the ID hack if the browser
+ // supports it & if we're not changing the context.
+ if ( newContext !== context || !support.scope ) {
+
+ // Capture the context ID, setting it first if necessary
+ if ( ( nid = context.getAttribute( "id" ) ) ) {
+ nid = nid.replace( rcssescape, fcssescape );
+ } else {
+ context.setAttribute( "id", ( nid = expando ) );
+ }
+ }
+
+ // Prefix every selector in the list
+ groups = tokenize( selector );
+ i = groups.length;
+ while ( i-- ) {
+ groups[ i ] = ( nid ? "#" + nid : ":scope" ) + " " +
+ toSelector( groups[ i ] );
+ }
+ newSelector = groups.join( "," );
+ }
+
+ try {
+ push.apply( results,
+ newContext.querySelectorAll( newSelector )
+ );
+ return results;
+ } catch ( qsaError ) {
+ nonnativeSelectorCache( selector, true );
+ } finally {
+ if ( nid === expando ) {
+ context.removeAttribute( "id" );
+ }
+ }
+ }
+ }
+ }
+
+ // All others
+ return select( selector.replace( rtrim, "$1" ), context, results, seed );
+}
+
+/**
+ * Create key-value caches of limited size
+ * @returns {function(string, object)} Returns the Object data after storing it on itself with
+ * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength)
+ * deleting the oldest entry
+ */
+function createCache() {
+ var keys = [];
+
+ function cache( key, value ) {
+
+ // Use (key + " ") to avoid collision with native prototype properties (see Issue #157)
+ if ( keys.push( key + " " ) > Expr.cacheLength ) {
+
+ // Only keep the most recent entries
+ delete cache[ keys.shift() ];
+ }
+ return ( cache[ key + " " ] = value );
+ }
+ return cache;
+}
+
+/**
+ * Mark a function for special use by Sizzle
+ * @param {Function} fn The function to mark
+ */
+function markFunction( fn ) {
+ fn[ expando ] = true;
+ return fn;
+}
+
+/**
+ * Support testing using an element
+ * @param {Function} fn Passed the created element and returns a boolean result
+ */
+function assert( fn ) {
+ var el = document.createElement( "fieldset" );
+
+ try {
+ return !!fn( el );
+ } catch ( e ) {
+ return false;
+ } finally {
+
+ // Remove from its parent by default
+ if ( el.parentNode ) {
+ el.parentNode.removeChild( el );
+ }
+
+ // release memory in IE
+ el = null;
+ }
+}
+
+/**
+ * Adds the same handler for all of the specified attrs
+ * @param {String} attrs Pipe-separated list of attributes
+ * @param {Function} handler The method that will be applied
+ */
+function addHandle( attrs, handler ) {
+ var arr = attrs.split( "|" ),
+ i = arr.length;
+
+ while ( i-- ) {
+ Expr.attrHandle[ arr[ i ] ] = handler;
+ }
+}
+
+/**
+ * Checks document order of two siblings
+ * @param {Element} a
+ * @param {Element} b
+ * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b
+ */
+function siblingCheck( a, b ) {
+ var cur = b && a,
+ diff = cur && a.nodeType === 1 && b.nodeType === 1 &&
+ a.sourceIndex - b.sourceIndex;
+
+ // Use IE sourceIndex if available on both nodes
+ if ( diff ) {
+ return diff;
+ }
+
+ // Check if b follows a
+ if ( cur ) {
+ while ( ( cur = cur.nextSibling ) ) {
+ if ( cur === b ) {
+ return -1;
+ }
+ }
+ }
+
+ return a ? 1 : -1;
+}
+
+/**
+ * Returns a function to use in pseudos for input types
+ * @param {String} type
+ */
+function createInputPseudo( type ) {
+ return function( elem ) {
+ var name = elem.nodeName.toLowerCase();
+ return name === "input" && elem.type === type;
+ };
+}
+
+/**
+ * Returns a function to use in pseudos for buttons
+ * @param {String} type
+ */
+function createButtonPseudo( type ) {
+ return function( elem ) {
+ var name = elem.nodeName.toLowerCase();
+ return ( name === "input" || name === "button" ) && elem.type === type;
+ };
+}
+
+/**
+ * Returns a function to use in pseudos for :enabled/:disabled
+ * @param {Boolean} disabled true for :disabled; false for :enabled
+ */
+function createDisabledPseudo( disabled ) {
+
+ // Known :disabled false positives: fieldset[disabled] > legend:nth-of-type(n+2) :can-disable
+ return function( elem ) {
+
+ // Only certain elements can match :enabled or :disabled
+ // https://html.spec.whatwg.org/multipage/scripting.html#selector-enabled
+ // https://html.spec.whatwg.org/multipage/scripting.html#selector-disabled
+ if ( "form" in elem ) {
+
+ // Check for inherited disabledness on relevant non-disabled elements:
+ // * listed form-associated elements in a disabled fieldset
+ // https://html.spec.whatwg.org/multipage/forms.html#category-listed
+ // https://html.spec.whatwg.org/multipage/forms.html#concept-fe-disabled
+ // * option elements in a disabled optgroup
+ // https://html.spec.whatwg.org/multipage/forms.html#concept-option-disabled
+ // All such elements have a "form" property.
+ if ( elem.parentNode && elem.disabled === false ) {
+
+ // Option elements defer to a parent optgroup if present
+ if ( "label" in elem ) {
+ if ( "label" in elem.parentNode ) {
+ return elem.parentNode.disabled === disabled;
+ } else {
+ return elem.disabled === disabled;
+ }
+ }
+
+ // Support: IE 6 - 11
+ // Use the isDisabled shortcut property to check for disabled fieldset ancestors
+ return elem.isDisabled === disabled ||
+
+ // Where there is no isDisabled, check manually
+ /* jshint -W018 */
+ elem.isDisabled !== !disabled &&
+ inDisabledFieldset( elem ) === disabled;
+ }
+
+ return elem.disabled === disabled;
+
+ // Try to winnow out elements that can't be disabled before trusting the disabled property.
+ // Some victims get caught in our net (label, legend, menu, track), but it shouldn't
+ // even exist on them, let alone have a boolean value.
+ } else if ( "label" in elem ) {
+ return elem.disabled === disabled;
+ }
+
+ // Remaining elements are neither :enabled nor :disabled
+ return false;
+ };
+}
+
+/**
+ * Returns a function to use in pseudos for positionals
+ * @param {Function} fn
+ */
+function createPositionalPseudo( fn ) {
+ return markFunction( function( argument ) {
+ argument = +argument;
+ return markFunction( function( seed, matches ) {
+ var j,
+ matchIndexes = fn( [], seed.length, argument ),
+ i = matchIndexes.length;
+
+ // Match elements found at the specified indexes
+ while ( i-- ) {
+ if ( seed[ ( j = matchIndexes[ i ] ) ] ) {
+ seed[ j ] = !( matches[ j ] = seed[ j ] );
+ }
+ }
+ } );
+ } );
+}
+
+/**
+ * Checks a node for validity as a Sizzle context
+ * @param {Element|Object=} context
+ * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value
+ */
+function testContext( context ) {
+ return context && typeof context.getElementsByTagName !== "undefined" && context;
+}
+
+// Expose support vars for convenience
+support = Sizzle.support = {};
+
+/**
+ * Detects XML nodes
+ * @param {Element|Object} elem An element or a document
+ * @returns {Boolean} True iff elem is a non-HTML XML node
+ */
+isXML = Sizzle.isXML = function( elem ) {
+ var namespace = elem && elem.namespaceURI,
+ docElem = elem && ( elem.ownerDocument || elem ).documentElement;
+
+ // Support: IE <=8
+ // Assume HTML when documentElement doesn't yet exist, such as inside loading iframes
+ // https://bugs.jquery.com/ticket/4833
+ return !rhtml.test( namespace || docElem && docElem.nodeName || "HTML" );
+};
+
+/**
+ * Sets document-related variables once based on the current document
+ * @param {Element|Object} [doc] An element or document object to use to set the document
+ * @returns {Object} Returns the current document
+ */
+setDocument = Sizzle.setDocument = function( node ) {
+ var hasCompare, subWindow,
+ doc = node ? node.ownerDocument || node : preferredDoc;
+
+ // Return early if doc is invalid or already selected
+ // Support: IE 11+, Edge 17 - 18+
+ // IE/Edge sometimes throw a "Permission denied" error when strict-comparing
+ // two documents; shallow comparisons work.
+ // eslint-disable-next-line eqeqeq
+ if ( doc == document || doc.nodeType !== 9 || !doc.documentElement ) {
+ return document;
+ }
+
+ // Update global variables
+ document = doc;
+ docElem = document.documentElement;
+ documentIsHTML = !isXML( document );
+
+ // Support: IE 9 - 11+, Edge 12 - 18+
+ // Accessing iframe documents after unload throws "permission denied" errors (jQuery #13936)
+ // Support: IE 11+, Edge 17 - 18+
+ // IE/Edge sometimes throw a "Permission denied" error when strict-comparing
+ // two documents; shallow comparisons work.
+ // eslint-disable-next-line eqeqeq
+ if ( preferredDoc != document &&
+ ( subWindow = document.defaultView ) && subWindow.top !== subWindow ) {
+
+ // Support: IE 11, Edge
+ if ( subWindow.addEventListener ) {
+ subWindow.addEventListener( "unload", unloadHandler, false );
+
+ // Support: IE 9 - 10 only
+ } else if ( subWindow.attachEvent ) {
+ subWindow.attachEvent( "onunload", unloadHandler );
+ }
+ }
+
+ // Support: IE 8 - 11+, Edge 12 - 18+, Chrome <=16 - 25 only, Firefox <=3.6 - 31 only,
+ // Safari 4 - 5 only, Opera <=11.6 - 12.x only
+ // IE/Edge & older browsers don't support the :scope pseudo-class.
+ // Support: Safari 6.0 only
+ // Safari 6.0 supports :scope but it's an alias of :root there.
+ support.scope = assert( function( el ) {
+ docElem.appendChild( el ).appendChild( document.createElement( "div" ) );
+ return typeof el.querySelectorAll !== "undefined" &&
+ !el.querySelectorAll( ":scope fieldset div" ).length;
+ } );
+
+ // Support: Chrome 105 - 110+, Safari 15.4 - 16.3+
+ // Make sure the the `:has()` argument is parsed unforgivingly.
+ // We include `*` in the test to detect buggy implementations that are
+ // _selectively_ forgiving (specifically when the list includes at least
+ // one valid selector).
+ // Note that we treat complete lack of support for `:has()` as if it were
+ // spec-compliant support, which is fine because use of `:has()` in such
+ // environments will fail in the qSA path and fall back to jQuery traversal
+ // anyway.
+ support.cssHas = assert( function() {
+ try {
+ document.querySelector( ":has(*,:jqfake)" );
+ return false;
+ } catch ( e ) {
+ return true;
+ }
+ } );
+
+ /* Attributes
+ ---------------------------------------------------------------------- */
+
+ // Support: IE<8
+ // Verify that getAttribute really returns attributes and not properties
+ // (excepting IE8 booleans)
+ support.attributes = assert( function( el ) {
+ el.className = "i";
+ return !el.getAttribute( "className" );
+ } );
+
+ /* getElement(s)By*
+ ---------------------------------------------------------------------- */
+
+ // Check if getElementsByTagName("*") returns only elements
+ support.getElementsByTagName = assert( function( el ) {
+ el.appendChild( document.createComment( "" ) );
+ return !el.getElementsByTagName( "*" ).length;
+ } );
+
+ // Support: IE<9
+ support.getElementsByClassName = rnative.test( document.getElementsByClassName );
+
+ // Support: IE<10
+ // Check if getElementById returns elements by name
+ // The broken getElementById methods don't pick up programmatically-set names,
+ // so use a roundabout getElementsByName test
+ support.getById = assert( function( el ) {
+ docElem.appendChild( el ).id = expando;
+ return !document.getElementsByName || !document.getElementsByName( expando ).length;
+ } );
+
+ // ID filter and find
+ if ( support.getById ) {
+ Expr.filter[ "ID" ] = function( id ) {
+ var attrId = id.replace( runescape, funescape );
+ return function( elem ) {
+ return elem.getAttribute( "id" ) === attrId;
+ };
+ };
+ Expr.find[ "ID" ] = function( id, context ) {
+ if ( typeof context.getElementById !== "undefined" && documentIsHTML ) {
+ var elem = context.getElementById( id );
+ return elem ? [ elem ] : [];
+ }
+ };
+ } else {
+ Expr.filter[ "ID" ] = function( id ) {
+ var attrId = id.replace( runescape, funescape );
+ return function( elem ) {
+ var node = typeof elem.getAttributeNode !== "undefined" &&
+ elem.getAttributeNode( "id" );
+ return node && node.value === attrId;
+ };
+ };
+
+ // Support: IE 6 - 7 only
+ // getElementById is not reliable as a find shortcut
+ Expr.find[ "ID" ] = function( id, context ) {
+ if ( typeof context.getElementById !== "undefined" && documentIsHTML ) {
+ var node, i, elems,
+ elem = context.getElementById( id );
+
+ if ( elem ) {
+
+ // Verify the id attribute
+ node = elem.getAttributeNode( "id" );
+ if ( node && node.value === id ) {
+ return [ elem ];
+ }
+
+ // Fall back on getElementsByName
+ elems = context.getElementsByName( id );
+ i = 0;
+ while ( ( elem = elems[ i++ ] ) ) {
+ node = elem.getAttributeNode( "id" );
+ if ( node && node.value === id ) {
+ return [ elem ];
+ }
+ }
+ }
+
+ return [];
+ }
+ };
+ }
+
+ // Tag
+ Expr.find[ "TAG" ] = support.getElementsByTagName ?
+ function( tag, context ) {
+ if ( typeof context.getElementsByTagName !== "undefined" ) {
+ return context.getElementsByTagName( tag );
+
+ // DocumentFragment nodes don't have gEBTN
+ } else if ( support.qsa ) {
+ return context.querySelectorAll( tag );
+ }
+ } :
+
+ function( tag, context ) {
+ var elem,
+ tmp = [],
+ i = 0,
+
+ // By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too
+ results = context.getElementsByTagName( tag );
+
+ // Filter out possible comments
+ if ( tag === "*" ) {
+ while ( ( elem = results[ i++ ] ) ) {
+ if ( elem.nodeType === 1 ) {
+ tmp.push( elem );
+ }
+ }
+
+ return tmp;
+ }
+ return results;
+ };
+
+ // Class
+ Expr.find[ "CLASS" ] = support.getElementsByClassName && function( className, context ) {
+ if ( typeof context.getElementsByClassName !== "undefined" && documentIsHTML ) {
+ return context.getElementsByClassName( className );
+ }
+ };
+
+ /* QSA/matchesSelector
+ ---------------------------------------------------------------------- */
+
+ // QSA and matchesSelector support
+
+ // matchesSelector(:active) reports false when true (IE9/Opera 11.5)
+ rbuggyMatches = [];
+
+ // qSa(:focus) reports false when true (Chrome 21)
+ // We allow this because of a bug in IE8/9 that throws an error
+ // whenever `document.activeElement` is accessed on an iframe
+ // So, we allow :focus to pass through QSA all the time to avoid the IE error
+ // See https://bugs.jquery.com/ticket/13378
+ rbuggyQSA = [];
+
+ if ( ( support.qsa = rnative.test( document.querySelectorAll ) ) ) {
+
+ // Build QSA regex
+ // Regex strategy adopted from Diego Perini
+ assert( function( el ) {
+
+ var input;
+
+ // Select is set to empty string on purpose
+ // This is to test IE's treatment of not explicitly
+ // setting a boolean content attribute,
+ // since its presence should be enough
+ // https://bugs.jquery.com/ticket/12359
+ docElem.appendChild( el ).innerHTML = " " +
+ "" +
+ " ";
+
+ // Support: IE8, Opera 11-12.16
+ // Nothing should be selected when empty strings follow ^= or $= or *=
+ // The test attribute must be unknown in Opera but "safe" for WinRT
+ // https://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section
+ if ( el.querySelectorAll( "[msallowcapture^='']" ).length ) {
+ rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" );
+ }
+
+ // Support: IE8
+ // Boolean attributes and "value" are not treated correctly
+ if ( !el.querySelectorAll( "[selected]" ).length ) {
+ rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" );
+ }
+
+ // Support: Chrome<29, Android<4.4, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.8+
+ if ( !el.querySelectorAll( "[id~=" + expando + "-]" ).length ) {
+ rbuggyQSA.push( "~=" );
+ }
+
+ // Support: IE 11+, Edge 15 - 18+
+ // IE 11/Edge don't find elements on a `[name='']` query in some cases.
+ // Adding a temporary attribute to the document before the selection works
+ // around the issue.
+ // Interestingly, IE 10 & older don't seem to have the issue.
+ input = document.createElement( "input" );
+ input.setAttribute( "name", "" );
+ el.appendChild( input );
+ if ( !el.querySelectorAll( "[name='']" ).length ) {
+ rbuggyQSA.push( "\\[" + whitespace + "*name" + whitespace + "*=" +
+ whitespace + "*(?:''|\"\")" );
+ }
+
+ // Webkit/Opera - :checked should return selected option elements
+ // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
+ // IE8 throws error here and will not see later tests
+ if ( !el.querySelectorAll( ":checked" ).length ) {
+ rbuggyQSA.push( ":checked" );
+ }
+
+ // Support: Safari 8+, iOS 8+
+ // https://bugs.webkit.org/show_bug.cgi?id=136851
+ // In-page `selector#id sibling-combinator selector` fails
+ if ( !el.querySelectorAll( "a#" + expando + "+*" ).length ) {
+ rbuggyQSA.push( ".#.+[+~]" );
+ }
+
+ // Support: Firefox <=3.6 - 5 only
+ // Old Firefox doesn't throw on a badly-escaped identifier.
+ el.querySelectorAll( "\\\f" );
+ rbuggyQSA.push( "[\\r\\n\\f]" );
+ } );
+
+ assert( function( el ) {
+ el.innerHTML = " " +
+ " ";
+
+ // Support: Windows 8 Native Apps
+ // The type and name attributes are restricted during .innerHTML assignment
+ var input = document.createElement( "input" );
+ input.setAttribute( "type", "hidden" );
+ el.appendChild( input ).setAttribute( "name", "D" );
+
+ // Support: IE8
+ // Enforce case-sensitivity of name attribute
+ if ( el.querySelectorAll( "[name=d]" ).length ) {
+ rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" );
+ }
+
+ // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled)
+ // IE8 throws error here and will not see later tests
+ if ( el.querySelectorAll( ":enabled" ).length !== 2 ) {
+ rbuggyQSA.push( ":enabled", ":disabled" );
+ }
+
+ // Support: IE9-11+
+ // IE's :disabled selector does not pick up the children of disabled fieldsets
+ docElem.appendChild( el ).disabled = true;
+ if ( el.querySelectorAll( ":disabled" ).length !== 2 ) {
+ rbuggyQSA.push( ":enabled", ":disabled" );
+ }
+
+ // Support: Opera 10 - 11 only
+ // Opera 10-11 does not throw on post-comma invalid pseudos
+ el.querySelectorAll( "*,:x" );
+ rbuggyQSA.push( ",.*:" );
+ } );
+ }
+
+ if ( ( support.matchesSelector = rnative.test( ( matches = docElem.matches ||
+ docElem.webkitMatchesSelector ||
+ docElem.mozMatchesSelector ||
+ docElem.oMatchesSelector ||
+ docElem.msMatchesSelector ) ) ) ) {
+
+ assert( function( el ) {
+
+ // Check to see if it's possible to do matchesSelector
+ // on a disconnected node (IE 9)
+ support.disconnectedMatch = matches.call( el, "*" );
+
+ // This should fail with an exception
+ // Gecko does not error, returns false instead
+ matches.call( el, "[s!='']:x" );
+ rbuggyMatches.push( "!=", pseudos );
+ } );
+ }
+
+ if ( !support.cssHas ) {
+
+ // Support: Chrome 105 - 110+, Safari 15.4 - 16.3+
+ // Our regular `try-catch` mechanism fails to detect natively-unsupported
+ // pseudo-classes inside `:has()` (such as `:has(:contains("Foo"))`)
+ // in browsers that parse the `:has()` argument as a forgiving selector list.
+ // https://drafts.csswg.org/selectors/#relational now requires the argument
+ // to be parsed unforgivingly, but browsers have not yet fully adjusted.
+ rbuggyQSA.push( ":has" );
+ }
+
+ rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join( "|" ) );
+ rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join( "|" ) );
+
+ /* Contains
+ ---------------------------------------------------------------------- */
+ hasCompare = rnative.test( docElem.compareDocumentPosition );
+
+ // Element contains another
+ // Purposefully self-exclusive
+ // As in, an element does not contain itself
+ contains = hasCompare || rnative.test( docElem.contains ) ?
+ function( a, b ) {
+
+ // Support: IE <9 only
+ // IE doesn't have `contains` on `document` so we need to check for
+ // `documentElement` presence.
+ // We need to fall back to `a` when `documentElement` is missing
+ // as `ownerDocument` of elements within ` ` may have
+ // a null one - a default behavior of all modern browsers.
+ var adown = a.nodeType === 9 && a.documentElement || a,
+ bup = b && b.parentNode;
+ return a === bup || !!( bup && bup.nodeType === 1 && (
+ adown.contains ?
+ adown.contains( bup ) :
+ a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16
+ ) );
+ } :
+ function( a, b ) {
+ if ( b ) {
+ while ( ( b = b.parentNode ) ) {
+ if ( b === a ) {
+ return true;
+ }
+ }
+ }
+ return false;
+ };
+
+ /* Sorting
+ ---------------------------------------------------------------------- */
+
+ // Document order sorting
+ sortOrder = hasCompare ?
+ function( a, b ) {
+
+ // Flag for duplicate removal
+ if ( a === b ) {
+ hasDuplicate = true;
+ return 0;
+ }
+
+ // Sort on method existence if only one input has compareDocumentPosition
+ var compare = !a.compareDocumentPosition - !b.compareDocumentPosition;
+ if ( compare ) {
+ return compare;
+ }
+
+ // Calculate position if both inputs belong to the same document
+ // Support: IE 11+, Edge 17 - 18+
+ // IE/Edge sometimes throw a "Permission denied" error when strict-comparing
+ // two documents; shallow comparisons work.
+ // eslint-disable-next-line eqeqeq
+ compare = ( a.ownerDocument || a ) == ( b.ownerDocument || b ) ?
+ a.compareDocumentPosition( b ) :
+
+ // Otherwise we know they are disconnected
+ 1;
+
+ // Disconnected nodes
+ if ( compare & 1 ||
+ ( !support.sortDetached && b.compareDocumentPosition( a ) === compare ) ) {
+
+ // Choose the first element that is related to our preferred document
+ // Support: IE 11+, Edge 17 - 18+
+ // IE/Edge sometimes throw a "Permission denied" error when strict-comparing
+ // two documents; shallow comparisons work.
+ // eslint-disable-next-line eqeqeq
+ if ( a == document || a.ownerDocument == preferredDoc &&
+ contains( preferredDoc, a ) ) {
+ return -1;
+ }
+
+ // Support: IE 11+, Edge 17 - 18+
+ // IE/Edge sometimes throw a "Permission denied" error when strict-comparing
+ // two documents; shallow comparisons work.
+ // eslint-disable-next-line eqeqeq
+ if ( b == document || b.ownerDocument == preferredDoc &&
+ contains( preferredDoc, b ) ) {
+ return 1;
+ }
+
+ // Maintain original order
+ return sortInput ?
+ ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) :
+ 0;
+ }
+
+ return compare & 4 ? -1 : 1;
+ } :
+ function( a, b ) {
+
+ // Exit early if the nodes are identical
+ if ( a === b ) {
+ hasDuplicate = true;
+ return 0;
+ }
+
+ var cur,
+ i = 0,
+ aup = a.parentNode,
+ bup = b.parentNode,
+ ap = [ a ],
+ bp = [ b ];
+
+ // Parentless nodes are either documents or disconnected
+ if ( !aup || !bup ) {
+
+ // Support: IE 11+, Edge 17 - 18+
+ // IE/Edge sometimes throw a "Permission denied" error when strict-comparing
+ // two documents; shallow comparisons work.
+ /* eslint-disable eqeqeq */
+ return a == document ? -1 :
+ b == document ? 1 :
+ /* eslint-enable eqeqeq */
+ aup ? -1 :
+ bup ? 1 :
+ sortInput ?
+ ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) :
+ 0;
+
+ // If the nodes are siblings, we can do a quick check
+ } else if ( aup === bup ) {
+ return siblingCheck( a, b );
+ }
+
+ // Otherwise we need full lists of their ancestors for comparison
+ cur = a;
+ while ( ( cur = cur.parentNode ) ) {
+ ap.unshift( cur );
+ }
+ cur = b;
+ while ( ( cur = cur.parentNode ) ) {
+ bp.unshift( cur );
+ }
+
+ // Walk down the tree looking for a discrepancy
+ while ( ap[ i ] === bp[ i ] ) {
+ i++;
+ }
+
+ return i ?
+
+ // Do a sibling check if the nodes have a common ancestor
+ siblingCheck( ap[ i ], bp[ i ] ) :
+
+ // Otherwise nodes in our document sort first
+ // Support: IE 11+, Edge 17 - 18+
+ // IE/Edge sometimes throw a "Permission denied" error when strict-comparing
+ // two documents; shallow comparisons work.
+ /* eslint-disable eqeqeq */
+ ap[ i ] == preferredDoc ? -1 :
+ bp[ i ] == preferredDoc ? 1 :
+ /* eslint-enable eqeqeq */
+ 0;
+ };
+
+ return document;
+};
+
+Sizzle.matches = function( expr, elements ) {
+ return Sizzle( expr, null, null, elements );
+};
+
+Sizzle.matchesSelector = function( elem, expr ) {
+ setDocument( elem );
+
+ if ( support.matchesSelector && documentIsHTML &&
+ !nonnativeSelectorCache[ expr + " " ] &&
+ ( !rbuggyMatches || !rbuggyMatches.test( expr ) ) &&
+ ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) {
+
+ try {
+ var ret = matches.call( elem, expr );
+
+ // IE 9's matchesSelector returns false on disconnected nodes
+ if ( ret || support.disconnectedMatch ||
+
+ // As well, disconnected nodes are said to be in a document
+ // fragment in IE 9
+ elem.document && elem.document.nodeType !== 11 ) {
+ return ret;
+ }
+ } catch ( e ) {
+ nonnativeSelectorCache( expr, true );
+ }
+ }
+
+ return Sizzle( expr, document, null, [ elem ] ).length > 0;
+};
+
+Sizzle.contains = function( context, elem ) {
+
+ // Set document vars if needed
+ // Support: IE 11+, Edge 17 - 18+
+ // IE/Edge sometimes throw a "Permission denied" error when strict-comparing
+ // two documents; shallow comparisons work.
+ // eslint-disable-next-line eqeqeq
+ if ( ( context.ownerDocument || context ) != document ) {
+ setDocument( context );
+ }
+ return contains( context, elem );
+};
+
+Sizzle.attr = function( elem, name ) {
+
+ // Set document vars if needed
+ // Support: IE 11+, Edge 17 - 18+
+ // IE/Edge sometimes throw a "Permission denied" error when strict-comparing
+ // two documents; shallow comparisons work.
+ // eslint-disable-next-line eqeqeq
+ if ( ( elem.ownerDocument || elem ) != document ) {
+ setDocument( elem );
+ }
+
+ var fn = Expr.attrHandle[ name.toLowerCase() ],
+
+ // Don't get fooled by Object.prototype properties (jQuery #13807)
+ val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ?
+ fn( elem, name, !documentIsHTML ) :
+ undefined;
+
+ return val !== undefined ?
+ val :
+ support.attributes || !documentIsHTML ?
+ elem.getAttribute( name ) :
+ ( val = elem.getAttributeNode( name ) ) && val.specified ?
+ val.value :
+ null;
+};
+
+Sizzle.escape = function( sel ) {
+ return ( sel + "" ).replace( rcssescape, fcssescape );
+};
+
+Sizzle.error = function( msg ) {
+ throw new Error( "Syntax error, unrecognized expression: " + msg );
+};
+
+/**
+ * Document sorting and removing duplicates
+ * @param {ArrayLike} results
+ */
+Sizzle.uniqueSort = function( results ) {
+ var elem,
+ duplicates = [],
+ j = 0,
+ i = 0;
+
+ // Unless we *know* we can detect duplicates, assume their presence
+ hasDuplicate = !support.detectDuplicates;
+ sortInput = !support.sortStable && results.slice( 0 );
+ results.sort( sortOrder );
+
+ if ( hasDuplicate ) {
+ while ( ( elem = results[ i++ ] ) ) {
+ if ( elem === results[ i ] ) {
+ j = duplicates.push( i );
+ }
+ }
+ while ( j-- ) {
+ results.splice( duplicates[ j ], 1 );
+ }
+ }
+
+ // Clear input after sorting to release objects
+ // See https://github.com/jquery/sizzle/pull/225
+ sortInput = null;
+
+ return results;
+};
+
+/**
+ * Utility function for retrieving the text value of an array of DOM nodes
+ * @param {Array|Element} elem
+ */
+getText = Sizzle.getText = function( elem ) {
+ var node,
+ ret = "",
+ i = 0,
+ nodeType = elem.nodeType;
+
+ if ( !nodeType ) {
+
+ // If no nodeType, this is expected to be an array
+ while ( ( node = elem[ i++ ] ) ) {
+
+ // Do not traverse comment nodes
+ ret += getText( node );
+ }
+ } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) {
+
+ // Use textContent for elements
+ // innerText usage removed for consistency of new lines (jQuery #11153)
+ if ( typeof elem.textContent === "string" ) {
+ return elem.textContent;
+ } else {
+
+ // Traverse its children
+ for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {
+ ret += getText( elem );
+ }
+ }
+ } else if ( nodeType === 3 || nodeType === 4 ) {
+ return elem.nodeValue;
+ }
+
+ // Do not include comment or processing instruction nodes
+
+ return ret;
+};
+
+Expr = Sizzle.selectors = {
+
+ // Can be adjusted by the user
+ cacheLength: 50,
+
+ createPseudo: markFunction,
+
+ match: matchExpr,
+
+ attrHandle: {},
+
+ find: {},
+
+ relative: {
+ ">": { dir: "parentNode", first: true },
+ " ": { dir: "parentNode" },
+ "+": { dir: "previousSibling", first: true },
+ "~": { dir: "previousSibling" }
+ },
+
+ preFilter: {
+ "ATTR": function( match ) {
+ match[ 1 ] = match[ 1 ].replace( runescape, funescape );
+
+ // Move the given value to match[3] whether quoted or unquoted
+ match[ 3 ] = ( match[ 3 ] || match[ 4 ] ||
+ match[ 5 ] || "" ).replace( runescape, funescape );
+
+ if ( match[ 2 ] === "~=" ) {
+ match[ 3 ] = " " + match[ 3 ] + " ";
+ }
+
+ return match.slice( 0, 4 );
+ },
+
+ "CHILD": function( match ) {
+
+ /* matches from matchExpr["CHILD"]
+ 1 type (only|nth|...)
+ 2 what (child|of-type)
+ 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...)
+ 4 xn-component of xn+y argument ([+-]?\d*n|)
+ 5 sign of xn-component
+ 6 x of xn-component
+ 7 sign of y-component
+ 8 y of y-component
+ */
+ match[ 1 ] = match[ 1 ].toLowerCase();
+
+ if ( match[ 1 ].slice( 0, 3 ) === "nth" ) {
+
+ // nth-* requires argument
+ if ( !match[ 3 ] ) {
+ Sizzle.error( match[ 0 ] );
+ }
+
+ // numeric x and y parameters for Expr.filter.CHILD
+ // remember that false/true cast respectively to 0/1
+ match[ 4 ] = +( match[ 4 ] ?
+ match[ 5 ] + ( match[ 6 ] || 1 ) :
+ 2 * ( match[ 3 ] === "even" || match[ 3 ] === "odd" ) );
+ match[ 5 ] = +( ( match[ 7 ] + match[ 8 ] ) || match[ 3 ] === "odd" );
+
+ // other types prohibit arguments
+ } else if ( match[ 3 ] ) {
+ Sizzle.error( match[ 0 ] );
+ }
+
+ return match;
+ },
+
+ "PSEUDO": function( match ) {
+ var excess,
+ unquoted = !match[ 6 ] && match[ 2 ];
+
+ if ( matchExpr[ "CHILD" ].test( match[ 0 ] ) ) {
+ return null;
+ }
+
+ // Accept quoted arguments as-is
+ if ( match[ 3 ] ) {
+ match[ 2 ] = match[ 4 ] || match[ 5 ] || "";
+
+ // Strip excess characters from unquoted arguments
+ } else if ( unquoted && rpseudo.test( unquoted ) &&
+
+ // Get excess from tokenize (recursively)
+ ( excess = tokenize( unquoted, true ) ) &&
+
+ // advance to the next closing parenthesis
+ ( excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length ) ) {
+
+ // excess is a negative index
+ match[ 0 ] = match[ 0 ].slice( 0, excess );
+ match[ 2 ] = unquoted.slice( 0, excess );
+ }
+
+ // Return only captures needed by the pseudo filter method (type and argument)
+ return match.slice( 0, 3 );
+ }
+ },
+
+ filter: {
+
+ "TAG": function( nodeNameSelector ) {
+ var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase();
+ return nodeNameSelector === "*" ?
+ function() {
+ return true;
+ } :
+ function( elem ) {
+ return elem.nodeName && elem.nodeName.toLowerCase() === nodeName;
+ };
+ },
+
+ "CLASS": function( className ) {
+ var pattern = classCache[ className + " " ];
+
+ return pattern ||
+ ( pattern = new RegExp( "(^|" + whitespace +
+ ")" + className + "(" + whitespace + "|$)" ) ) && classCache(
+ className, function( elem ) {
+ return pattern.test(
+ typeof elem.className === "string" && elem.className ||
+ typeof elem.getAttribute !== "undefined" &&
+ elem.getAttribute( "class" ) ||
+ ""
+ );
+ } );
+ },
+
+ "ATTR": function( name, operator, check ) {
+ return function( elem ) {
+ var result = Sizzle.attr( elem, name );
+
+ if ( result == null ) {
+ return operator === "!=";
+ }
+ if ( !operator ) {
+ return true;
+ }
+
+ result += "";
+
+ /* eslint-disable max-len */
+
+ return operator === "=" ? result === check :
+ operator === "!=" ? result !== check :
+ operator === "^=" ? check && result.indexOf( check ) === 0 :
+ operator === "*=" ? check && result.indexOf( check ) > -1 :
+ operator === "$=" ? check && result.slice( -check.length ) === check :
+ operator === "~=" ? ( " " + result.replace( rwhitespace, " " ) + " " ).indexOf( check ) > -1 :
+ operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" :
+ false;
+ /* eslint-enable max-len */
+
+ };
+ },
+
+ "CHILD": function( type, what, _argument, first, last ) {
+ var simple = type.slice( 0, 3 ) !== "nth",
+ forward = type.slice( -4 ) !== "last",
+ ofType = what === "of-type";
+
+ return first === 1 && last === 0 ?
+
+ // Shortcut for :nth-*(n)
+ function( elem ) {
+ return !!elem.parentNode;
+ } :
+
+ function( elem, _context, xml ) {
+ var cache, uniqueCache, outerCache, node, nodeIndex, start,
+ dir = simple !== forward ? "nextSibling" : "previousSibling",
+ parent = elem.parentNode,
+ name = ofType && elem.nodeName.toLowerCase(),
+ useCache = !xml && !ofType,
+ diff = false;
+
+ if ( parent ) {
+
+ // :(first|last|only)-(child|of-type)
+ if ( simple ) {
+ while ( dir ) {
+ node = elem;
+ while ( ( node = node[ dir ] ) ) {
+ if ( ofType ?
+ node.nodeName.toLowerCase() === name :
+ node.nodeType === 1 ) {
+
+ return false;
+ }
+ }
+
+ // Reverse direction for :only-* (if we haven't yet done so)
+ start = dir = type === "only" && !start && "nextSibling";
+ }
+ return true;
+ }
+
+ start = [ forward ? parent.firstChild : parent.lastChild ];
+
+ // non-xml :nth-child(...) stores cache data on `parent`
+ if ( forward && useCache ) {
+
+ // Seek `elem` from a previously-cached index
+
+ // ...in a gzip-friendly way
+ node = parent;
+ outerCache = node[ expando ] || ( node[ expando ] = {} );
+
+ // Support: IE <9 only
+ // Defend against cloned attroperties (jQuery gh-1709)
+ uniqueCache = outerCache[ node.uniqueID ] ||
+ ( outerCache[ node.uniqueID ] = {} );
+
+ cache = uniqueCache[ type ] || [];
+ nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ];
+ diff = nodeIndex && cache[ 2 ];
+ node = nodeIndex && parent.childNodes[ nodeIndex ];
+
+ while ( ( node = ++nodeIndex && node && node[ dir ] ||
+
+ // Fallback to seeking `elem` from the start
+ ( diff = nodeIndex = 0 ) || start.pop() ) ) {
+
+ // When found, cache indexes on `parent` and break
+ if ( node.nodeType === 1 && ++diff && node === elem ) {
+ uniqueCache[ type ] = [ dirruns, nodeIndex, diff ];
+ break;
+ }
+ }
+
+ } else {
+
+ // Use previously-cached element index if available
+ if ( useCache ) {
+
+ // ...in a gzip-friendly way
+ node = elem;
+ outerCache = node[ expando ] || ( node[ expando ] = {} );
+
+ // Support: IE <9 only
+ // Defend against cloned attroperties (jQuery gh-1709)
+ uniqueCache = outerCache[ node.uniqueID ] ||
+ ( outerCache[ node.uniqueID ] = {} );
+
+ cache = uniqueCache[ type ] || [];
+ nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ];
+ diff = nodeIndex;
+ }
+
+ // xml :nth-child(...)
+ // or :nth-last-child(...) or :nth(-last)?-of-type(...)
+ if ( diff === false ) {
+
+ // Use the same loop as above to seek `elem` from the start
+ while ( ( node = ++nodeIndex && node && node[ dir ] ||
+ ( diff = nodeIndex = 0 ) || start.pop() ) ) {
+
+ if ( ( ofType ?
+ node.nodeName.toLowerCase() === name :
+ node.nodeType === 1 ) &&
+ ++diff ) {
+
+ // Cache the index of each encountered element
+ if ( useCache ) {
+ outerCache = node[ expando ] ||
+ ( node[ expando ] = {} );
+
+ // Support: IE <9 only
+ // Defend against cloned attroperties (jQuery gh-1709)
+ uniqueCache = outerCache[ node.uniqueID ] ||
+ ( outerCache[ node.uniqueID ] = {} );
+
+ uniqueCache[ type ] = [ dirruns, diff ];
+ }
+
+ if ( node === elem ) {
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ // Incorporate the offset, then check against cycle size
+ diff -= last;
+ return diff === first || ( diff % first === 0 && diff / first >= 0 );
+ }
+ };
+ },
+
+ "PSEUDO": function( pseudo, argument ) {
+
+ // pseudo-class names are case-insensitive
+ // http://www.w3.org/TR/selectors/#pseudo-classes
+ // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters
+ // Remember that setFilters inherits from pseudos
+ var args,
+ fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] ||
+ Sizzle.error( "unsupported pseudo: " + pseudo );
+
+ // The user may use createPseudo to indicate that
+ // arguments are needed to create the filter function
+ // just as Sizzle does
+ if ( fn[ expando ] ) {
+ return fn( argument );
+ }
+
+ // But maintain support for old signatures
+ if ( fn.length > 1 ) {
+ args = [ pseudo, pseudo, "", argument ];
+ return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ?
+ markFunction( function( seed, matches ) {
+ var idx,
+ matched = fn( seed, argument ),
+ i = matched.length;
+ while ( i-- ) {
+ idx = indexOf( seed, matched[ i ] );
+ seed[ idx ] = !( matches[ idx ] = matched[ i ] );
+ }
+ } ) :
+ function( elem ) {
+ return fn( elem, 0, args );
+ };
+ }
+
+ return fn;
+ }
+ },
+
+ pseudos: {
+
+ // Potentially complex pseudos
+ "not": markFunction( function( selector ) {
+
+ // Trim the selector passed to compile
+ // to avoid treating leading and trailing
+ // spaces as combinators
+ var input = [],
+ results = [],
+ matcher = compile( selector.replace( rtrim, "$1" ) );
+
+ return matcher[ expando ] ?
+ markFunction( function( seed, matches, _context, xml ) {
+ var elem,
+ unmatched = matcher( seed, null, xml, [] ),
+ i = seed.length;
+
+ // Match elements unmatched by `matcher`
+ while ( i-- ) {
+ if ( ( elem = unmatched[ i ] ) ) {
+ seed[ i ] = !( matches[ i ] = elem );
+ }
+ }
+ } ) :
+ function( elem, _context, xml ) {
+ input[ 0 ] = elem;
+ matcher( input, null, xml, results );
+
+ // Don't keep the element (issue #299)
+ input[ 0 ] = null;
+ return !results.pop();
+ };
+ } ),
+
+ "has": markFunction( function( selector ) {
+ return function( elem ) {
+ return Sizzle( selector, elem ).length > 0;
+ };
+ } ),
+
+ "contains": markFunction( function( text ) {
+ text = text.replace( runescape, funescape );
+ return function( elem ) {
+ return ( elem.textContent || getText( elem ) ).indexOf( text ) > -1;
+ };
+ } ),
+
+ // "Whether an element is represented by a :lang() selector
+ // is based solely on the element's language value
+ // being equal to the identifier C,
+ // or beginning with the identifier C immediately followed by "-".
+ // The matching of C against the element's language value is performed case-insensitively.
+ // The identifier C does not have to be a valid language name."
+ // http://www.w3.org/TR/selectors/#lang-pseudo
+ "lang": markFunction( function( lang ) {
+
+ // lang value must be a valid identifier
+ if ( !ridentifier.test( lang || "" ) ) {
+ Sizzle.error( "unsupported lang: " + lang );
+ }
+ lang = lang.replace( runescape, funescape ).toLowerCase();
+ return function( elem ) {
+ var elemLang;
+ do {
+ if ( ( elemLang = documentIsHTML ?
+ elem.lang :
+ elem.getAttribute( "xml:lang" ) || elem.getAttribute( "lang" ) ) ) {
+
+ elemLang = elemLang.toLowerCase();
+ return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0;
+ }
+ } while ( ( elem = elem.parentNode ) && elem.nodeType === 1 );
+ return false;
+ };
+ } ),
+
+ // Miscellaneous
+ "target": function( elem ) {
+ var hash = window.location && window.location.hash;
+ return hash && hash.slice( 1 ) === elem.id;
+ },
+
+ "root": function( elem ) {
+ return elem === docElem;
+ },
+
+ "focus": function( elem ) {
+ return elem === document.activeElement &&
+ ( !document.hasFocus || document.hasFocus() ) &&
+ !!( elem.type || elem.href || ~elem.tabIndex );
+ },
+
+ // Boolean properties
+ "enabled": createDisabledPseudo( false ),
+ "disabled": createDisabledPseudo( true ),
+
+ "checked": function( elem ) {
+
+ // In CSS3, :checked should return both checked and selected elements
+ // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
+ var nodeName = elem.nodeName.toLowerCase();
+ return ( nodeName === "input" && !!elem.checked ) ||
+ ( nodeName === "option" && !!elem.selected );
+ },
+
+ "selected": function( elem ) {
+
+ // Accessing this property makes selected-by-default
+ // options in Safari work properly
+ if ( elem.parentNode ) {
+ // eslint-disable-next-line no-unused-expressions
+ elem.parentNode.selectedIndex;
+ }
+
+ return elem.selected === true;
+ },
+
+ // Contents
+ "empty": function( elem ) {
+
+ // http://www.w3.org/TR/selectors/#empty-pseudo
+ // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5),
+ // but not by others (comment: 8; processing instruction: 7; etc.)
+ // nodeType < 6 works because attributes (2) do not appear as children
+ for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {
+ if ( elem.nodeType < 6 ) {
+ return false;
+ }
+ }
+ return true;
+ },
+
+ "parent": function( elem ) {
+ return !Expr.pseudos[ "empty" ]( elem );
+ },
+
+ // Element/input types
+ "header": function( elem ) {
+ return rheader.test( elem.nodeName );
+ },
+
+ "input": function( elem ) {
+ return rinputs.test( elem.nodeName );
+ },
+
+ "button": function( elem ) {
+ var name = elem.nodeName.toLowerCase();
+ return name === "input" && elem.type === "button" || name === "button";
+ },
+
+ "text": function( elem ) {
+ var attr;
+ return elem.nodeName.toLowerCase() === "input" &&
+ elem.type === "text" &&
+
+ // Support: IE <10 only
+ // New HTML5 attribute values (e.g., "search") appear with elem.type === "text"
+ ( ( attr = elem.getAttribute( "type" ) ) == null ||
+ attr.toLowerCase() === "text" );
+ },
+
+ // Position-in-collection
+ "first": createPositionalPseudo( function() {
+ return [ 0 ];
+ } ),
+
+ "last": createPositionalPseudo( function( _matchIndexes, length ) {
+ return [ length - 1 ];
+ } ),
+
+ "eq": createPositionalPseudo( function( _matchIndexes, length, argument ) {
+ return [ argument < 0 ? argument + length : argument ];
+ } ),
+
+ "even": createPositionalPseudo( function( matchIndexes, length ) {
+ var i = 0;
+ for ( ; i < length; i += 2 ) {
+ matchIndexes.push( i );
+ }
+ return matchIndexes;
+ } ),
+
+ "odd": createPositionalPseudo( function( matchIndexes, length ) {
+ var i = 1;
+ for ( ; i < length; i += 2 ) {
+ matchIndexes.push( i );
+ }
+ return matchIndexes;
+ } ),
+
+ "lt": createPositionalPseudo( function( matchIndexes, length, argument ) {
+ var i = argument < 0 ?
+ argument + length :
+ argument > length ?
+ length :
+ argument;
+ for ( ; --i >= 0; ) {
+ matchIndexes.push( i );
+ }
+ return matchIndexes;
+ } ),
+
+ "gt": createPositionalPseudo( function( matchIndexes, length, argument ) {
+ var i = argument < 0 ? argument + length : argument;
+ for ( ; ++i < length; ) {
+ matchIndexes.push( i );
+ }
+ return matchIndexes;
+ } )
+ }
+};
+
+Expr.pseudos[ "nth" ] = Expr.pseudos[ "eq" ];
+
+// Add button/input type pseudos
+for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) {
+ Expr.pseudos[ i ] = createInputPseudo( i );
+}
+for ( i in { submit: true, reset: true } ) {
+ Expr.pseudos[ i ] = createButtonPseudo( i );
+}
+
+// Easy API for creating new setFilters
+function setFilters() {}
+setFilters.prototype = Expr.filters = Expr.pseudos;
+Expr.setFilters = new setFilters();
+
+tokenize = Sizzle.tokenize = function( selector, parseOnly ) {
+ var matched, match, tokens, type,
+ soFar, groups, preFilters,
+ cached = tokenCache[ selector + " " ];
+
+ if ( cached ) {
+ return parseOnly ? 0 : cached.slice( 0 );
+ }
+
+ soFar = selector;
+ groups = [];
+ preFilters = Expr.preFilter;
+
+ while ( soFar ) {
+
+ // Comma and first run
+ if ( !matched || ( match = rcomma.exec( soFar ) ) ) {
+ if ( match ) {
+
+ // Don't consume trailing commas as valid
+ soFar = soFar.slice( match[ 0 ].length ) || soFar;
+ }
+ groups.push( ( tokens = [] ) );
+ }
+
+ matched = false;
+
+ // Combinators
+ if ( ( match = rleadingCombinator.exec( soFar ) ) ) {
+ matched = match.shift();
+ tokens.push( {
+ value: matched,
+
+ // Cast descendant combinators to space
+ type: match[ 0 ].replace( rtrim, " " )
+ } );
+ soFar = soFar.slice( matched.length );
+ }
+
+ // Filters
+ for ( type in Expr.filter ) {
+ if ( ( match = matchExpr[ type ].exec( soFar ) ) && ( !preFilters[ type ] ||
+ ( match = preFilters[ type ]( match ) ) ) ) {
+ matched = match.shift();
+ tokens.push( {
+ value: matched,
+ type: type,
+ matches: match
+ } );
+ soFar = soFar.slice( matched.length );
+ }
+ }
+
+ if ( !matched ) {
+ break;
+ }
+ }
+
+ // Return the length of the invalid excess
+ // if we're just parsing
+ // Otherwise, throw an error or return tokens
+ return parseOnly ?
+ soFar.length :
+ soFar ?
+ Sizzle.error( selector ) :
+
+ // Cache the tokens
+ tokenCache( selector, groups ).slice( 0 );
+};
+
+function toSelector( tokens ) {
+ var i = 0,
+ len = tokens.length,
+ selector = "";
+ for ( ; i < len; i++ ) {
+ selector += tokens[ i ].value;
+ }
+ return selector;
+}
+
+function addCombinator( matcher, combinator, base ) {
+ var dir = combinator.dir,
+ skip = combinator.next,
+ key = skip || dir,
+ checkNonElements = base && key === "parentNode",
+ doneName = done++;
+
+ return combinator.first ?
+
+ // Check against closest ancestor/preceding element
+ function( elem, context, xml ) {
+ while ( ( elem = elem[ dir ] ) ) {
+ if ( elem.nodeType === 1 || checkNonElements ) {
+ return matcher( elem, context, xml );
+ }
+ }
+ return false;
+ } :
+
+ // Check against all ancestor/preceding elements
+ function( elem, context, xml ) {
+ var oldCache, uniqueCache, outerCache,
+ newCache = [ dirruns, doneName ];
+
+ // We can't set arbitrary data on XML nodes, so they don't benefit from combinator caching
+ if ( xml ) {
+ while ( ( elem = elem[ dir ] ) ) {
+ if ( elem.nodeType === 1 || checkNonElements ) {
+ if ( matcher( elem, context, xml ) ) {
+ return true;
+ }
+ }
+ }
+ } else {
+ while ( ( elem = elem[ dir ] ) ) {
+ if ( elem.nodeType === 1 || checkNonElements ) {
+ outerCache = elem[ expando ] || ( elem[ expando ] = {} );
+
+ // Support: IE <9 only
+ // Defend against cloned attroperties (jQuery gh-1709)
+ uniqueCache = outerCache[ elem.uniqueID ] ||
+ ( outerCache[ elem.uniqueID ] = {} );
+
+ if ( skip && skip === elem.nodeName.toLowerCase() ) {
+ elem = elem[ dir ] || elem;
+ } else if ( ( oldCache = uniqueCache[ key ] ) &&
+ oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) {
+
+ // Assign to newCache so results back-propagate to previous elements
+ return ( newCache[ 2 ] = oldCache[ 2 ] );
+ } else {
+
+ // Reuse newcache so results back-propagate to previous elements
+ uniqueCache[ key ] = newCache;
+
+ // A match means we're done; a fail means we have to keep checking
+ if ( ( newCache[ 2 ] = matcher( elem, context, xml ) ) ) {
+ return true;
+ }
+ }
+ }
+ }
+ }
+ return false;
+ };
+}
+
+function elementMatcher( matchers ) {
+ return matchers.length > 1 ?
+ function( elem, context, xml ) {
+ var i = matchers.length;
+ while ( i-- ) {
+ if ( !matchers[ i ]( elem, context, xml ) ) {
+ return false;
+ }
+ }
+ return true;
+ } :
+ matchers[ 0 ];
+}
+
+function multipleContexts( selector, contexts, results ) {
+ var i = 0,
+ len = contexts.length;
+ for ( ; i < len; i++ ) {
+ Sizzle( selector, contexts[ i ], results );
+ }
+ return results;
+}
+
+function condense( unmatched, map, filter, context, xml ) {
+ var elem,
+ newUnmatched = [],
+ i = 0,
+ len = unmatched.length,
+ mapped = map != null;
+
+ for ( ; i < len; i++ ) {
+ if ( ( elem = unmatched[ i ] ) ) {
+ if ( !filter || filter( elem, context, xml ) ) {
+ newUnmatched.push( elem );
+ if ( mapped ) {
+ map.push( i );
+ }
+ }
+ }
+ }
+
+ return newUnmatched;
+}
+
+function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) {
+ if ( postFilter && !postFilter[ expando ] ) {
+ postFilter = setMatcher( postFilter );
+ }
+ if ( postFinder && !postFinder[ expando ] ) {
+ postFinder = setMatcher( postFinder, postSelector );
+ }
+ return markFunction( function( seed, results, context, xml ) {
+ var temp, i, elem,
+ preMap = [],
+ postMap = [],
+ preexisting = results.length,
+
+ // Get initial elements from seed or context
+ elems = seed || multipleContexts(
+ selector || "*",
+ context.nodeType ? [ context ] : context,
+ []
+ ),
+
+ // Prefilter to get matcher input, preserving a map for seed-results synchronization
+ matcherIn = preFilter && ( seed || !selector ) ?
+ condense( elems, preMap, preFilter, context, xml ) :
+ elems,
+
+ matcherOut = matcher ?
+
+ // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results,
+ postFinder || ( seed ? preFilter : preexisting || postFilter ) ?
+
+ // ...intermediate processing is necessary
+ [] :
+
+ // ...otherwise use results directly
+ results :
+ matcherIn;
+
+ // Find primary matches
+ if ( matcher ) {
+ matcher( matcherIn, matcherOut, context, xml );
+ }
+
+ // Apply postFilter
+ if ( postFilter ) {
+ temp = condense( matcherOut, postMap );
+ postFilter( temp, [], context, xml );
+
+ // Un-match failing elements by moving them back to matcherIn
+ i = temp.length;
+ while ( i-- ) {
+ if ( ( elem = temp[ i ] ) ) {
+ matcherOut[ postMap[ i ] ] = !( matcherIn[ postMap[ i ] ] = elem );
+ }
+ }
+ }
+
+ if ( seed ) {
+ if ( postFinder || preFilter ) {
+ if ( postFinder ) {
+
+ // Get the final matcherOut by condensing this intermediate into postFinder contexts
+ temp = [];
+ i = matcherOut.length;
+ while ( i-- ) {
+ if ( ( elem = matcherOut[ i ] ) ) {
+
+ // Restore matcherIn since elem is not yet a final match
+ temp.push( ( matcherIn[ i ] = elem ) );
+ }
+ }
+ postFinder( null, ( matcherOut = [] ), temp, xml );
+ }
+
+ // Move matched elements from seed to results to keep them synchronized
+ i = matcherOut.length;
+ while ( i-- ) {
+ if ( ( elem = matcherOut[ i ] ) &&
+ ( temp = postFinder ? indexOf( seed, elem ) : preMap[ i ] ) > -1 ) {
+
+ seed[ temp ] = !( results[ temp ] = elem );
+ }
+ }
+ }
+
+ // Add elements to results, through postFinder if defined
+ } else {
+ matcherOut = condense(
+ matcherOut === results ?
+ matcherOut.splice( preexisting, matcherOut.length ) :
+ matcherOut
+ );
+ if ( postFinder ) {
+ postFinder( null, results, matcherOut, xml );
+ } else {
+ push.apply( results, matcherOut );
+ }
+ }
+ } );
+}
+
+function matcherFromTokens( tokens ) {
+ var checkContext, matcher, j,
+ len = tokens.length,
+ leadingRelative = Expr.relative[ tokens[ 0 ].type ],
+ implicitRelative = leadingRelative || Expr.relative[ " " ],
+ i = leadingRelative ? 1 : 0,
+
+ // The foundational matcher ensures that elements are reachable from top-level context(s)
+ matchContext = addCombinator( function( elem ) {
+ return elem === checkContext;
+ }, implicitRelative, true ),
+ matchAnyContext = addCombinator( function( elem ) {
+ return indexOf( checkContext, elem ) > -1;
+ }, implicitRelative, true ),
+ matchers = [ function( elem, context, xml ) {
+ var ret = ( !leadingRelative && ( xml || context !== outermostContext ) ) || (
+ ( checkContext = context ).nodeType ?
+ matchContext( elem, context, xml ) :
+ matchAnyContext( elem, context, xml ) );
+
+ // Avoid hanging onto element (issue #299)
+ checkContext = null;
+ return ret;
+ } ];
+
+ for ( ; i < len; i++ ) {
+ if ( ( matcher = Expr.relative[ tokens[ i ].type ] ) ) {
+ matchers = [ addCombinator( elementMatcher( matchers ), matcher ) ];
+ } else {
+ matcher = Expr.filter[ tokens[ i ].type ].apply( null, tokens[ i ].matches );
+
+ // Return special upon seeing a positional matcher
+ if ( matcher[ expando ] ) {
+
+ // Find the next relative operator (if any) for proper handling
+ j = ++i;
+ for ( ; j < len; j++ ) {
+ if ( Expr.relative[ tokens[ j ].type ] ) {
+ break;
+ }
+ }
+ return setMatcher(
+ i > 1 && elementMatcher( matchers ),
+ i > 1 && toSelector(
+
+ // If the preceding token was a descendant combinator, insert an implicit any-element `*`
+ tokens
+ .slice( 0, i - 1 )
+ .concat( { value: tokens[ i - 2 ].type === " " ? "*" : "" } )
+ ).replace( rtrim, "$1" ),
+ matcher,
+ i < j && matcherFromTokens( tokens.slice( i, j ) ),
+ j < len && matcherFromTokens( ( tokens = tokens.slice( j ) ) ),
+ j < len && toSelector( tokens )
+ );
+ }
+ matchers.push( matcher );
+ }
+ }
+
+ return elementMatcher( matchers );
+}
+
+function matcherFromGroupMatchers( elementMatchers, setMatchers ) {
+ var bySet = setMatchers.length > 0,
+ byElement = elementMatchers.length > 0,
+ superMatcher = function( seed, context, xml, results, outermost ) {
+ var elem, j, matcher,
+ matchedCount = 0,
+ i = "0",
+ unmatched = seed && [],
+ setMatched = [],
+ contextBackup = outermostContext,
+
+ // We must always have either seed elements or outermost context
+ elems = seed || byElement && Expr.find[ "TAG" ]( "*", outermost ),
+
+ // Use integer dirruns iff this is the outermost matcher
+ dirrunsUnique = ( dirruns += contextBackup == null ? 1 : Math.random() || 0.1 ),
+ len = elems.length;
+
+ if ( outermost ) {
+
+ // Support: IE 11+, Edge 17 - 18+
+ // IE/Edge sometimes throw a "Permission denied" error when strict-comparing
+ // two documents; shallow comparisons work.
+ // eslint-disable-next-line eqeqeq
+ outermostContext = context == document || context || outermost;
+ }
+
+ // Add elements passing elementMatchers directly to results
+ // Support: IE<9, Safari
+ // Tolerate NodeList properties (IE: "length"; Safari: ) matching elements by id
+ for ( ; i !== len && ( elem = elems[ i ] ) != null; i++ ) {
+ if ( byElement && elem ) {
+ j = 0;
+
+ // Support: IE 11+, Edge 17 - 18+
+ // IE/Edge sometimes throw a "Permission denied" error when strict-comparing
+ // two documents; shallow comparisons work.
+ // eslint-disable-next-line eqeqeq
+ if ( !context && elem.ownerDocument != document ) {
+ setDocument( elem );
+ xml = !documentIsHTML;
+ }
+ while ( ( matcher = elementMatchers[ j++ ] ) ) {
+ if ( matcher( elem, context || document, xml ) ) {
+ results.push( elem );
+ break;
+ }
+ }
+ if ( outermost ) {
+ dirruns = dirrunsUnique;
+ }
+ }
+
+ // Track unmatched elements for set filters
+ if ( bySet ) {
+
+ // They will have gone through all possible matchers
+ if ( ( elem = !matcher && elem ) ) {
+ matchedCount--;
+ }
+
+ // Lengthen the array for every element, matched or not
+ if ( seed ) {
+ unmatched.push( elem );
+ }
+ }
+ }
+
+ // `i` is now the count of elements visited above, and adding it to `matchedCount`
+ // makes the latter nonnegative.
+ matchedCount += i;
+
+ // Apply set filters to unmatched elements
+ // NOTE: This can be skipped if there are no unmatched elements (i.e., `matchedCount`
+ // equals `i`), unless we didn't visit _any_ elements in the above loop because we have
+ // no element matchers and no seed.
+ // Incrementing an initially-string "0" `i` allows `i` to remain a string only in that
+ // case, which will result in a "00" `matchedCount` that differs from `i` but is also
+ // numerically zero.
+ if ( bySet && i !== matchedCount ) {
+ j = 0;
+ while ( ( matcher = setMatchers[ j++ ] ) ) {
+ matcher( unmatched, setMatched, context, xml );
+ }
+
+ if ( seed ) {
+
+ // Reintegrate element matches to eliminate the need for sorting
+ if ( matchedCount > 0 ) {
+ while ( i-- ) {
+ if ( !( unmatched[ i ] || setMatched[ i ] ) ) {
+ setMatched[ i ] = pop.call( results );
+ }
+ }
+ }
+
+ // Discard index placeholder values to get only actual matches
+ setMatched = condense( setMatched );
+ }
+
+ // Add matches to results
+ push.apply( results, setMatched );
+
+ // Seedless set matches succeeding multiple successful matchers stipulate sorting
+ if ( outermost && !seed && setMatched.length > 0 &&
+ ( matchedCount + setMatchers.length ) > 1 ) {
+
+ Sizzle.uniqueSort( results );
+ }
+ }
+
+ // Override manipulation of globals by nested matchers
+ if ( outermost ) {
+ dirruns = dirrunsUnique;
+ outermostContext = contextBackup;
+ }
+
+ return unmatched;
+ };
+
+ return bySet ?
+ markFunction( superMatcher ) :
+ superMatcher;
+}
+
+compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) {
+ var i,
+ setMatchers = [],
+ elementMatchers = [],
+ cached = compilerCache[ selector + " " ];
+
+ if ( !cached ) {
+
+ // Generate a function of recursive functions that can be used to check each element
+ if ( !match ) {
+ match = tokenize( selector );
+ }
+ i = match.length;
+ while ( i-- ) {
+ cached = matcherFromTokens( match[ i ] );
+ if ( cached[ expando ] ) {
+ setMatchers.push( cached );
+ } else {
+ elementMatchers.push( cached );
+ }
+ }
+
+ // Cache the compiled function
+ cached = compilerCache(
+ selector,
+ matcherFromGroupMatchers( elementMatchers, setMatchers )
+ );
+
+ // Save selector and tokenization
+ cached.selector = selector;
+ }
+ return cached;
+};
+
+/**
+ * A low-level selection function that works with Sizzle's compiled
+ * selector functions
+ * @param {String|Function} selector A selector or a pre-compiled
+ * selector function built with Sizzle.compile
+ * @param {Element} context
+ * @param {Array} [results]
+ * @param {Array} [seed] A set of elements to match against
+ */
+select = Sizzle.select = function( selector, context, results, seed ) {
+ var i, tokens, token, type, find,
+ compiled = typeof selector === "function" && selector,
+ match = !seed && tokenize( ( selector = compiled.selector || selector ) );
+
+ results = results || [];
+
+ // Try to minimize operations if there is only one selector in the list and no seed
+ // (the latter of which guarantees us context)
+ if ( match.length === 1 ) {
+
+ // Reduce context if the leading compound selector is an ID
+ tokens = match[ 0 ] = match[ 0 ].slice( 0 );
+ if ( tokens.length > 2 && ( token = tokens[ 0 ] ).type === "ID" &&
+ context.nodeType === 9 && documentIsHTML && Expr.relative[ tokens[ 1 ].type ] ) {
+
+ context = ( Expr.find[ "ID" ]( token.matches[ 0 ]
+ .replace( runescape, funescape ), context ) || [] )[ 0 ];
+ if ( !context ) {
+ return results;
+
+ // Precompiled matchers will still verify ancestry, so step up a level
+ } else if ( compiled ) {
+ context = context.parentNode;
+ }
+
+ selector = selector.slice( tokens.shift().value.length );
+ }
+
+ // Fetch a seed set for right-to-left matching
+ i = matchExpr[ "needsContext" ].test( selector ) ? 0 : tokens.length;
+ while ( i-- ) {
+ token = tokens[ i ];
+
+ // Abort if we hit a combinator
+ if ( Expr.relative[ ( type = token.type ) ] ) {
+ break;
+ }
+ if ( ( find = Expr.find[ type ] ) ) {
+
+ // Search, expanding context for leading sibling combinators
+ if ( ( seed = find(
+ token.matches[ 0 ].replace( runescape, funescape ),
+ rsibling.test( tokens[ 0 ].type ) && testContext( context.parentNode ) ||
+ context
+ ) ) ) {
+
+ // If seed is empty or no tokens remain, we can return early
+ tokens.splice( i, 1 );
+ selector = seed.length && toSelector( tokens );
+ if ( !selector ) {
+ push.apply( results, seed );
+ return results;
+ }
+
+ break;
+ }
+ }
+ }
+ }
+
+ // Compile and execute a filtering function if one is not provided
+ // Provide `match` to avoid retokenization if we modified the selector above
+ ( compiled || compile( selector, match ) )(
+ seed,
+ context,
+ !documentIsHTML,
+ results,
+ !context || rsibling.test( selector ) && testContext( context.parentNode ) || context
+ );
+ return results;
+};
+
+// One-time assignments
+
+// Sort stability
+support.sortStable = expando.split( "" ).sort( sortOrder ).join( "" ) === expando;
+
+// Support: Chrome 14-35+
+// Always assume duplicates if they aren't passed to the comparison function
+support.detectDuplicates = !!hasDuplicate;
+
+// Initialize against the default document
+setDocument();
+
+// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27)
+// Detached nodes confoundingly follow *each other*
+support.sortDetached = assert( function( el ) {
+
+ // Should return 1, but returns 4 (following)
+ return el.compareDocumentPosition( document.createElement( "fieldset" ) ) & 1;
+} );
+
+// Support: IE<8
+// Prevent attribute/property "interpolation"
+// https://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx
+if ( !assert( function( el ) {
+ el.innerHTML = " ";
+ return el.firstChild.getAttribute( "href" ) === "#";
+} ) ) {
+ addHandle( "type|href|height|width", function( elem, name, isXML ) {
+ if ( !isXML ) {
+ return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 );
+ }
+ } );
+}
+
+// Support: IE<9
+// Use defaultValue in place of getAttribute("value")
+if ( !support.attributes || !assert( function( el ) {
+ el.innerHTML = " ";
+ el.firstChild.setAttribute( "value", "" );
+ return el.firstChild.getAttribute( "value" ) === "";
+} ) ) {
+ addHandle( "value", function( elem, _name, isXML ) {
+ if ( !isXML && elem.nodeName.toLowerCase() === "input" ) {
+ return elem.defaultValue;
+ }
+ } );
+}
+
+// Support: IE<9
+// Use getAttributeNode to fetch booleans when getAttribute lies
+if ( !assert( function( el ) {
+ return el.getAttribute( "disabled" ) == null;
+} ) ) {
+ addHandle( booleans, function( elem, name, isXML ) {
+ var val;
+ if ( !isXML ) {
+ return elem[ name ] === true ? name.toLowerCase() :
+ ( val = elem.getAttributeNode( name ) ) && val.specified ?
+ val.value :
+ null;
+ }
+ } );
+}
+
+return Sizzle;
+
+} )( window );
+
+
+
+jQuery.find = Sizzle;
+jQuery.expr = Sizzle.selectors;
+
+// Deprecated
+jQuery.expr[ ":" ] = jQuery.expr.pseudos;
+jQuery.uniqueSort = jQuery.unique = Sizzle.uniqueSort;
+jQuery.text = Sizzle.getText;
+jQuery.isXMLDoc = Sizzle.isXML;
+jQuery.contains = Sizzle.contains;
+jQuery.escapeSelector = Sizzle.escape;
+
+
+
+
+var dir = function( elem, dir, until ) {
+ var matched = [],
+ truncate = until !== undefined;
+
+ while ( ( elem = elem[ dir ] ) && elem.nodeType !== 9 ) {
+ if ( elem.nodeType === 1 ) {
+ if ( truncate && jQuery( elem ).is( until ) ) {
+ break;
+ }
+ matched.push( elem );
+ }
+ }
+ return matched;
+};
+
+
+var siblings = function( n, elem ) {
+ var matched = [];
+
+ for ( ; n; n = n.nextSibling ) {
+ if ( n.nodeType === 1 && n !== elem ) {
+ matched.push( n );
+ }
+ }
+
+ return matched;
+};
+
+
+var rneedsContext = jQuery.expr.match.needsContext;
+
+
+
+function nodeName( elem, name ) {
+
+ return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase();
+
+}
+var rsingleTag = ( /^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i );
+
+
+
+// Implement the identical functionality for filter and not
+function winnow( elements, qualifier, not ) {
+ if ( isFunction( qualifier ) ) {
+ return jQuery.grep( elements, function( elem, i ) {
+ return !!qualifier.call( elem, i, elem ) !== not;
+ } );
+ }
+
+ // Single element
+ if ( qualifier.nodeType ) {
+ return jQuery.grep( elements, function( elem ) {
+ return ( elem === qualifier ) !== not;
+ } );
+ }
+
+ // Arraylike of elements (jQuery, arguments, Array)
+ if ( typeof qualifier !== "string" ) {
+ return jQuery.grep( elements, function( elem ) {
+ return ( indexOf.call( qualifier, elem ) > -1 ) !== not;
+ } );
+ }
+
+ // Filtered directly for both simple and complex selectors
+ return jQuery.filter( qualifier, elements, not );
+}
+
+jQuery.filter = function( expr, elems, not ) {
+ var elem = elems[ 0 ];
+
+ if ( not ) {
+ expr = ":not(" + expr + ")";
+ }
+
+ if ( elems.length === 1 && elem.nodeType === 1 ) {
+ return jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : [];
+ }
+
+ return jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) {
+ return elem.nodeType === 1;
+ } ) );
+};
+
+jQuery.fn.extend( {
+ find: function( selector ) {
+ var i, ret,
+ len = this.length,
+ self = this;
+
+ if ( typeof selector !== "string" ) {
+ return this.pushStack( jQuery( selector ).filter( function() {
+ for ( i = 0; i < len; i++ ) {
+ if ( jQuery.contains( self[ i ], this ) ) {
+ return true;
+ }
+ }
+ } ) );
+ }
+
+ ret = this.pushStack( [] );
+
+ for ( i = 0; i < len; i++ ) {
+ jQuery.find( selector, self[ i ], ret );
+ }
+
+ return len > 1 ? jQuery.uniqueSort( ret ) : ret;
+ },
+ filter: function( selector ) {
+ return this.pushStack( winnow( this, selector || [], false ) );
+ },
+ not: function( selector ) {
+ return this.pushStack( winnow( this, selector || [], true ) );
+ },
+ is: function( selector ) {
+ return !!winnow(
+ this,
+
+ // If this is a positional/relative selector, check membership in the returned set
+ // so $("p:first").is("p:last") won't return true for a doc with two "p".
+ typeof selector === "string" && rneedsContext.test( selector ) ?
+ jQuery( selector ) :
+ selector || [],
+ false
+ ).length;
+ }
+} );
+
+
+// Initialize a jQuery object
+
+
+// A central reference to the root jQuery(document)
+var rootjQuery,
+
+ // A simple way to check for HTML strings
+ // Prioritize #id over to avoid XSS via location.hash (trac-9521)
+ // Strict HTML recognition (trac-11290: must start with <)
+ // Shortcut simple #id case for speed
+ rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/,
+
+ init = jQuery.fn.init = function( selector, context, root ) {
+ var match, elem;
+
+ // HANDLE: $(""), $(null), $(undefined), $(false)
+ if ( !selector ) {
+ return this;
+ }
+
+ // Method init() accepts an alternate rootjQuery
+ // so migrate can support jQuery.sub (gh-2101)
+ root = root || rootjQuery;
+
+ // Handle HTML strings
+ if ( typeof selector === "string" ) {
+ if ( selector[ 0 ] === "<" &&
+ selector[ selector.length - 1 ] === ">" &&
+ selector.length >= 3 ) {
+
+ // Assume that strings that start and end with <> are HTML and skip the regex check
+ match = [ null, selector, null ];
+
+ } else {
+ match = rquickExpr.exec( selector );
+ }
+
+ // Match html or make sure no context is specified for #id
+ if ( match && ( match[ 1 ] || !context ) ) {
+
+ // HANDLE: $(html) -> $(array)
+ if ( match[ 1 ] ) {
+ context = context instanceof jQuery ? context[ 0 ] : context;
+
+ // Option to run scripts is true for back-compat
+ // Intentionally let the error be thrown if parseHTML is not present
+ jQuery.merge( this, jQuery.parseHTML(
+ match[ 1 ],
+ context && context.nodeType ? context.ownerDocument || context : document,
+ true
+ ) );
+
+ // HANDLE: $(html, props)
+ if ( rsingleTag.test( match[ 1 ] ) && jQuery.isPlainObject( context ) ) {
+ for ( match in context ) {
+
+ // Properties of context are called as methods if possible
+ if ( isFunction( this[ match ] ) ) {
+ this[ match ]( context[ match ] );
+
+ // ...and otherwise set as attributes
+ } else {
+ this.attr( match, context[ match ] );
+ }
+ }
+ }
+
+ return this;
+
+ // HANDLE: $(#id)
+ } else {
+ elem = document.getElementById( match[ 2 ] );
+
+ if ( elem ) {
+
+ // Inject the element directly into the jQuery object
+ this[ 0 ] = elem;
+ this.length = 1;
+ }
+ return this;
+ }
+
+ // HANDLE: $(expr, $(...))
+ } else if ( !context || context.jquery ) {
+ return ( context || root ).find( selector );
+
+ // HANDLE: $(expr, context)
+ // (which is just equivalent to: $(context).find(expr)
+ } else {
+ return this.constructor( context ).find( selector );
+ }
+
+ // HANDLE: $(DOMElement)
+ } else if ( selector.nodeType ) {
+ this[ 0 ] = selector;
+ this.length = 1;
+ return this;
+
+ // HANDLE: $(function)
+ // Shortcut for document ready
+ } else if ( isFunction( selector ) ) {
+ return root.ready !== undefined ?
+ root.ready( selector ) :
+
+ // Execute immediately if ready is not present
+ selector( jQuery );
+ }
+
+ return jQuery.makeArray( selector, this );
+ };
+
+// Give the init function the jQuery prototype for later instantiation
+init.prototype = jQuery.fn;
+
+// Initialize central reference
+rootjQuery = jQuery( document );
+
+
+var rparentsprev = /^(?:parents|prev(?:Until|All))/,
+
+ // Methods guaranteed to produce a unique set when starting from a unique set
+ guaranteedUnique = {
+ children: true,
+ contents: true,
+ next: true,
+ prev: true
+ };
+
+jQuery.fn.extend( {
+ has: function( target ) {
+ var targets = jQuery( target, this ),
+ l = targets.length;
+
+ return this.filter( function() {
+ var i = 0;
+ for ( ; i < l; i++ ) {
+ if ( jQuery.contains( this, targets[ i ] ) ) {
+ return true;
+ }
+ }
+ } );
+ },
+
+ closest: function( selectors, context ) {
+ var cur,
+ i = 0,
+ l = this.length,
+ matched = [],
+ targets = typeof selectors !== "string" && jQuery( selectors );
+
+ // Positional selectors never match, since there's no _selection_ context
+ if ( !rneedsContext.test( selectors ) ) {
+ for ( ; i < l; i++ ) {
+ for ( cur = this[ i ]; cur && cur !== context; cur = cur.parentNode ) {
+
+ // Always skip document fragments
+ if ( cur.nodeType < 11 && ( targets ?
+ targets.index( cur ) > -1 :
+
+ // Don't pass non-elements to Sizzle
+ cur.nodeType === 1 &&
+ jQuery.find.matchesSelector( cur, selectors ) ) ) {
+
+ matched.push( cur );
+ break;
+ }
+ }
+ }
+ }
+
+ return this.pushStack( matched.length > 1 ? jQuery.uniqueSort( matched ) : matched );
+ },
+
+ // Determine the position of an element within the set
+ index: function( elem ) {
+
+ // No argument, return index in parent
+ if ( !elem ) {
+ return ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1;
+ }
+
+ // Index in selector
+ if ( typeof elem === "string" ) {
+ return indexOf.call( jQuery( elem ), this[ 0 ] );
+ }
+
+ // Locate the position of the desired element
+ return indexOf.call( this,
+
+ // If it receives a jQuery object, the first element is used
+ elem.jquery ? elem[ 0 ] : elem
+ );
+ },
+
+ add: function( selector, context ) {
+ return this.pushStack(
+ jQuery.uniqueSort(
+ jQuery.merge( this.get(), jQuery( selector, context ) )
+ )
+ );
+ },
+
+ addBack: function( selector ) {
+ return this.add( selector == null ?
+ this.prevObject : this.prevObject.filter( selector )
+ );
+ }
+} );
+
+function sibling( cur, dir ) {
+ while ( ( cur = cur[ dir ] ) && cur.nodeType !== 1 ) {}
+ return cur;
+}
+
+jQuery.each( {
+ parent: function( elem ) {
+ var parent = elem.parentNode;
+ return parent && parent.nodeType !== 11 ? parent : null;
+ },
+ parents: function( elem ) {
+ return dir( elem, "parentNode" );
+ },
+ parentsUntil: function( elem, _i, until ) {
+ return dir( elem, "parentNode", until );
+ },
+ next: function( elem ) {
+ return sibling( elem, "nextSibling" );
+ },
+ prev: function( elem ) {
+ return sibling( elem, "previousSibling" );
+ },
+ nextAll: function( elem ) {
+ return dir( elem, "nextSibling" );
+ },
+ prevAll: function( elem ) {
+ return dir( elem, "previousSibling" );
+ },
+ nextUntil: function( elem, _i, until ) {
+ return dir( elem, "nextSibling", until );
+ },
+ prevUntil: function( elem, _i, until ) {
+ return dir( elem, "previousSibling", until );
+ },
+ siblings: function( elem ) {
+ return siblings( ( elem.parentNode || {} ).firstChild, elem );
+ },
+ children: function( elem ) {
+ return siblings( elem.firstChild );
+ },
+ contents: function( elem ) {
+ if ( elem.contentDocument != null &&
+
+ // Support: IE 11+
+ // elements with no `data` attribute has an object
+ // `contentDocument` with a `null` prototype.
+ getProto( elem.contentDocument ) ) {
+
+ return elem.contentDocument;
+ }
+
+ // Support: IE 9 - 11 only, iOS 7 only, Android Browser <=4.3 only
+ // Treat the template element as a regular one in browsers that
+ // don't support it.
+ if ( nodeName( elem, "template" ) ) {
+ elem = elem.content || elem;
+ }
+
+ return jQuery.merge( [], elem.childNodes );
+ }
+}, function( name, fn ) {
+ jQuery.fn[ name ] = function( until, selector ) {
+ var matched = jQuery.map( this, fn, until );
+
+ if ( name.slice( -5 ) !== "Until" ) {
+ selector = until;
+ }
+
+ if ( selector && typeof selector === "string" ) {
+ matched = jQuery.filter( selector, matched );
+ }
+
+ if ( this.length > 1 ) {
+
+ // Remove duplicates
+ if ( !guaranteedUnique[ name ] ) {
+ jQuery.uniqueSort( matched );
+ }
+
+ // Reverse order for parents* and prev-derivatives
+ if ( rparentsprev.test( name ) ) {
+ matched.reverse();
+ }
+ }
+
+ return this.pushStack( matched );
+ };
+} );
+var rnothtmlwhite = ( /[^\x20\t\r\n\f]+/g );
+
+
+
+// Convert String-formatted options into Object-formatted ones
+function createOptions( options ) {
+ var object = {};
+ jQuery.each( options.match( rnothtmlwhite ) || [], function( _, flag ) {
+ object[ flag ] = true;
+ } );
+ return object;
+}
+
+/*
+ * Create a callback list using the following parameters:
+ *
+ * options: an optional list of space-separated options that will change how
+ * the callback list behaves or a more traditional option object
+ *
+ * By default a callback list will act like an event callback list and can be
+ * "fired" multiple times.
+ *
+ * Possible options:
+ *
+ * once: will ensure the callback list can only be fired once (like a Deferred)
+ *
+ * memory: will keep track of previous values and will call any callback added
+ * after the list has been fired right away with the latest "memorized"
+ * values (like a Deferred)
+ *
+ * unique: will ensure a callback can only be added once (no duplicate in the list)
+ *
+ * stopOnFalse: interrupt callings when a callback returns false
+ *
+ */
+jQuery.Callbacks = function( options ) {
+
+ // Convert options from String-formatted to Object-formatted if needed
+ // (we check in cache first)
+ options = typeof options === "string" ?
+ createOptions( options ) :
+ jQuery.extend( {}, options );
+
+ var // Flag to know if list is currently firing
+ firing,
+
+ // Last fire value for non-forgettable lists
+ memory,
+
+ // Flag to know if list was already fired
+ fired,
+
+ // Flag to prevent firing
+ locked,
+
+ // Actual callback list
+ list = [],
+
+ // Queue of execution data for repeatable lists
+ queue = [],
+
+ // Index of currently firing callback (modified by add/remove as needed)
+ firingIndex = -1,
+
+ // Fire callbacks
+ fire = function() {
+
+ // Enforce single-firing
+ locked = locked || options.once;
+
+ // Execute callbacks for all pending executions,
+ // respecting firingIndex overrides and runtime changes
+ fired = firing = true;
+ for ( ; queue.length; firingIndex = -1 ) {
+ memory = queue.shift();
+ while ( ++firingIndex < list.length ) {
+
+ // Run callback and check for early termination
+ if ( list[ firingIndex ].apply( memory[ 0 ], memory[ 1 ] ) === false &&
+ options.stopOnFalse ) {
+
+ // Jump to end and forget the data so .add doesn't re-fire
+ firingIndex = list.length;
+ memory = false;
+ }
+ }
+ }
+
+ // Forget the data if we're done with it
+ if ( !options.memory ) {
+ memory = false;
+ }
+
+ firing = false;
+
+ // Clean up if we're done firing for good
+ if ( locked ) {
+
+ // Keep an empty list if we have data for future add calls
+ if ( memory ) {
+ list = [];
+
+ // Otherwise, this object is spent
+ } else {
+ list = "";
+ }
+ }
+ },
+
+ // Actual Callbacks object
+ self = {
+
+ // Add a callback or a collection of callbacks to the list
+ add: function() {
+ if ( list ) {
+
+ // If we have memory from a past run, we should fire after adding
+ if ( memory && !firing ) {
+ firingIndex = list.length - 1;
+ queue.push( memory );
+ }
+
+ ( function add( args ) {
+ jQuery.each( args, function( _, arg ) {
+ if ( isFunction( arg ) ) {
+ if ( !options.unique || !self.has( arg ) ) {
+ list.push( arg );
+ }
+ } else if ( arg && arg.length && toType( arg ) !== "string" ) {
+
+ // Inspect recursively
+ add( arg );
+ }
+ } );
+ } )( arguments );
+
+ if ( memory && !firing ) {
+ fire();
+ }
+ }
+ return this;
+ },
+
+ // Remove a callback from the list
+ remove: function() {
+ jQuery.each( arguments, function( _, arg ) {
+ var index;
+ while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {
+ list.splice( index, 1 );
+
+ // Handle firing indexes
+ if ( index <= firingIndex ) {
+ firingIndex--;
+ }
+ }
+ } );
+ return this;
+ },
+
+ // Check if a given callback is in the list.
+ // If no argument is given, return whether or not list has callbacks attached.
+ has: function( fn ) {
+ return fn ?
+ jQuery.inArray( fn, list ) > -1 :
+ list.length > 0;
+ },
+
+ // Remove all callbacks from the list
+ empty: function() {
+ if ( list ) {
+ list = [];
+ }
+ return this;
+ },
+
+ // Disable .fire and .add
+ // Abort any current/pending executions
+ // Clear all callbacks and values
+ disable: function() {
+ locked = queue = [];
+ list = memory = "";
+ return this;
+ },
+ disabled: function() {
+ return !list;
+ },
+
+ // Disable .fire
+ // Also disable .add unless we have memory (since it would have no effect)
+ // Abort any pending executions
+ lock: function() {
+ locked = queue = [];
+ if ( !memory && !firing ) {
+ list = memory = "";
+ }
+ return this;
+ },
+ locked: function() {
+ return !!locked;
+ },
+
+ // Call all callbacks with the given context and arguments
+ fireWith: function( context, args ) {
+ if ( !locked ) {
+ args = args || [];
+ args = [ context, args.slice ? args.slice() : args ];
+ queue.push( args );
+ if ( !firing ) {
+ fire();
+ }
+ }
+ return this;
+ },
+
+ // Call all the callbacks with the given arguments
+ fire: function() {
+ self.fireWith( this, arguments );
+ return this;
+ },
+
+ // To know if the callbacks have already been called at least once
+ fired: function() {
+ return !!fired;
+ }
+ };
+
+ return self;
+};
+
+
+function Identity( v ) {
+ return v;
+}
+function Thrower( ex ) {
+ throw ex;
+}
+
+function adoptValue( value, resolve, reject, noValue ) {
+ var method;
+
+ try {
+
+ // Check for promise aspect first to privilege synchronous behavior
+ if ( value && isFunction( ( method = value.promise ) ) ) {
+ method.call( value ).done( resolve ).fail( reject );
+
+ // Other thenables
+ } else if ( value && isFunction( ( method = value.then ) ) ) {
+ method.call( value, resolve, reject );
+
+ // Other non-thenables
+ } else {
+
+ // Control `resolve` arguments by letting Array#slice cast boolean `noValue` to integer:
+ // * false: [ value ].slice( 0 ) => resolve( value )
+ // * true: [ value ].slice( 1 ) => resolve()
+ resolve.apply( undefined, [ value ].slice( noValue ) );
+ }
+
+ // For Promises/A+, convert exceptions into rejections
+ // Since jQuery.when doesn't unwrap thenables, we can skip the extra checks appearing in
+ // Deferred#then to conditionally suppress rejection.
+ } catch ( value ) {
+
+ // Support: Android 4.0 only
+ // Strict mode functions invoked without .call/.apply get global-object context
+ reject.apply( undefined, [ value ] );
+ }
+}
+
+jQuery.extend( {
+
+ Deferred: function( func ) {
+ var tuples = [
+
+ // action, add listener, callbacks,
+ // ... .then handlers, argument index, [final state]
+ [ "notify", "progress", jQuery.Callbacks( "memory" ),
+ jQuery.Callbacks( "memory" ), 2 ],
+ [ "resolve", "done", jQuery.Callbacks( "once memory" ),
+ jQuery.Callbacks( "once memory" ), 0, "resolved" ],
+ [ "reject", "fail", jQuery.Callbacks( "once memory" ),
+ jQuery.Callbacks( "once memory" ), 1, "rejected" ]
+ ],
+ state = "pending",
+ promise = {
+ state: function() {
+ return state;
+ },
+ always: function() {
+ deferred.done( arguments ).fail( arguments );
+ return this;
+ },
+ "catch": function( fn ) {
+ return promise.then( null, fn );
+ },
+
+ // Keep pipe for back-compat
+ pipe: function( /* fnDone, fnFail, fnProgress */ ) {
+ var fns = arguments;
+
+ return jQuery.Deferred( function( newDefer ) {
+ jQuery.each( tuples, function( _i, tuple ) {
+
+ // Map tuples (progress, done, fail) to arguments (done, fail, progress)
+ var fn = isFunction( fns[ tuple[ 4 ] ] ) && fns[ tuple[ 4 ] ];
+
+ // deferred.progress(function() { bind to newDefer or newDefer.notify })
+ // deferred.done(function() { bind to newDefer or newDefer.resolve })
+ // deferred.fail(function() { bind to newDefer or newDefer.reject })
+ deferred[ tuple[ 1 ] ]( function() {
+ var returned = fn && fn.apply( this, arguments );
+ if ( returned && isFunction( returned.promise ) ) {
+ returned.promise()
+ .progress( newDefer.notify )
+ .done( newDefer.resolve )
+ .fail( newDefer.reject );
+ } else {
+ newDefer[ tuple[ 0 ] + "With" ](
+ this,
+ fn ? [ returned ] : arguments
+ );
+ }
+ } );
+ } );
+ fns = null;
+ } ).promise();
+ },
+ then: function( onFulfilled, onRejected, onProgress ) {
+ var maxDepth = 0;
+ function resolve( depth, deferred, handler, special ) {
+ return function() {
+ var that = this,
+ args = arguments,
+ mightThrow = function() {
+ var returned, then;
+
+ // Support: Promises/A+ section 2.3.3.3.3
+ // https://promisesaplus.com/#point-59
+ // Ignore double-resolution attempts
+ if ( depth < maxDepth ) {
+ return;
+ }
+
+ returned = handler.apply( that, args );
+
+ // Support: Promises/A+ section 2.3.1
+ // https://promisesaplus.com/#point-48
+ if ( returned === deferred.promise() ) {
+ throw new TypeError( "Thenable self-resolution" );
+ }
+
+ // Support: Promises/A+ sections 2.3.3.1, 3.5
+ // https://promisesaplus.com/#point-54
+ // https://promisesaplus.com/#point-75
+ // Retrieve `then` only once
+ then = returned &&
+
+ // Support: Promises/A+ section 2.3.4
+ // https://promisesaplus.com/#point-64
+ // Only check objects and functions for thenability
+ ( typeof returned === "object" ||
+ typeof returned === "function" ) &&
+ returned.then;
+
+ // Handle a returned thenable
+ if ( isFunction( then ) ) {
+
+ // Special processors (notify) just wait for resolution
+ if ( special ) {
+ then.call(
+ returned,
+ resolve( maxDepth, deferred, Identity, special ),
+ resolve( maxDepth, deferred, Thrower, special )
+ );
+
+ // Normal processors (resolve) also hook into progress
+ } else {
+
+ // ...and disregard older resolution values
+ maxDepth++;
+
+ then.call(
+ returned,
+ resolve( maxDepth, deferred, Identity, special ),
+ resolve( maxDepth, deferred, Thrower, special ),
+ resolve( maxDepth, deferred, Identity,
+ deferred.notifyWith )
+ );
+ }
+
+ // Handle all other returned values
+ } else {
+
+ // Only substitute handlers pass on context
+ // and multiple values (non-spec behavior)
+ if ( handler !== Identity ) {
+ that = undefined;
+ args = [ returned ];
+ }
+
+ // Process the value(s)
+ // Default process is resolve
+ ( special || deferred.resolveWith )( that, args );
+ }
+ },
+
+ // Only normal processors (resolve) catch and reject exceptions
+ process = special ?
+ mightThrow :
+ function() {
+ try {
+ mightThrow();
+ } catch ( e ) {
+
+ if ( jQuery.Deferred.exceptionHook ) {
+ jQuery.Deferred.exceptionHook( e,
+ process.stackTrace );
+ }
+
+ // Support: Promises/A+ section 2.3.3.3.4.1
+ // https://promisesaplus.com/#point-61
+ // Ignore post-resolution exceptions
+ if ( depth + 1 >= maxDepth ) {
+
+ // Only substitute handlers pass on context
+ // and multiple values (non-spec behavior)
+ if ( handler !== Thrower ) {
+ that = undefined;
+ args = [ e ];
+ }
+
+ deferred.rejectWith( that, args );
+ }
+ }
+ };
+
+ // Support: Promises/A+ section 2.3.3.3.1
+ // https://promisesaplus.com/#point-57
+ // Re-resolve promises immediately to dodge false rejection from
+ // subsequent errors
+ if ( depth ) {
+ process();
+ } else {
+
+ // Call an optional hook to record the stack, in case of exception
+ // since it's otherwise lost when execution goes async
+ if ( jQuery.Deferred.getStackHook ) {
+ process.stackTrace = jQuery.Deferred.getStackHook();
+ }
+ window.setTimeout( process );
+ }
+ };
+ }
+
+ return jQuery.Deferred( function( newDefer ) {
+
+ // progress_handlers.add( ... )
+ tuples[ 0 ][ 3 ].add(
+ resolve(
+ 0,
+ newDefer,
+ isFunction( onProgress ) ?
+ onProgress :
+ Identity,
+ newDefer.notifyWith
+ )
+ );
+
+ // fulfilled_handlers.add( ... )
+ tuples[ 1 ][ 3 ].add(
+ resolve(
+ 0,
+ newDefer,
+ isFunction( onFulfilled ) ?
+ onFulfilled :
+ Identity
+ )
+ );
+
+ // rejected_handlers.add( ... )
+ tuples[ 2 ][ 3 ].add(
+ resolve(
+ 0,
+ newDefer,
+ isFunction( onRejected ) ?
+ onRejected :
+ Thrower
+ )
+ );
+ } ).promise();
+ },
+
+ // Get a promise for this deferred
+ // If obj is provided, the promise aspect is added to the object
+ promise: function( obj ) {
+ return obj != null ? jQuery.extend( obj, promise ) : promise;
+ }
+ },
+ deferred = {};
+
+ // Add list-specific methods
+ jQuery.each( tuples, function( i, tuple ) {
+ var list = tuple[ 2 ],
+ stateString = tuple[ 5 ];
+
+ // promise.progress = list.add
+ // promise.done = list.add
+ // promise.fail = list.add
+ promise[ tuple[ 1 ] ] = list.add;
+
+ // Handle state
+ if ( stateString ) {
+ list.add(
+ function() {
+
+ // state = "resolved" (i.e., fulfilled)
+ // state = "rejected"
+ state = stateString;
+ },
+
+ // rejected_callbacks.disable
+ // fulfilled_callbacks.disable
+ tuples[ 3 - i ][ 2 ].disable,
+
+ // rejected_handlers.disable
+ // fulfilled_handlers.disable
+ tuples[ 3 - i ][ 3 ].disable,
+
+ // progress_callbacks.lock
+ tuples[ 0 ][ 2 ].lock,
+
+ // progress_handlers.lock
+ tuples[ 0 ][ 3 ].lock
+ );
+ }
+
+ // progress_handlers.fire
+ // fulfilled_handlers.fire
+ // rejected_handlers.fire
+ list.add( tuple[ 3 ].fire );
+
+ // deferred.notify = function() { deferred.notifyWith(...) }
+ // deferred.resolve = function() { deferred.resolveWith(...) }
+ // deferred.reject = function() { deferred.rejectWith(...) }
+ deferred[ tuple[ 0 ] ] = function() {
+ deferred[ tuple[ 0 ] + "With" ]( this === deferred ? undefined : this, arguments );
+ return this;
+ };
+
+ // deferred.notifyWith = list.fireWith
+ // deferred.resolveWith = list.fireWith
+ // deferred.rejectWith = list.fireWith
+ deferred[ tuple[ 0 ] + "With" ] = list.fireWith;
+ } );
+
+ // Make the deferred a promise
+ promise.promise( deferred );
+
+ // Call given func if any
+ if ( func ) {
+ func.call( deferred, deferred );
+ }
+
+ // All done!
+ return deferred;
+ },
+
+ // Deferred helper
+ when: function( singleValue ) {
+ var
+
+ // count of uncompleted subordinates
+ remaining = arguments.length,
+
+ // count of unprocessed arguments
+ i = remaining,
+
+ // subordinate fulfillment data
+ resolveContexts = Array( i ),
+ resolveValues = slice.call( arguments ),
+
+ // the primary Deferred
+ primary = jQuery.Deferred(),
+
+ // subordinate callback factory
+ updateFunc = function( i ) {
+ return function( value ) {
+ resolveContexts[ i ] = this;
+ resolveValues[ i ] = arguments.length > 1 ? slice.call( arguments ) : value;
+ if ( !( --remaining ) ) {
+ primary.resolveWith( resolveContexts, resolveValues );
+ }
+ };
+ };
+
+ // Single- and empty arguments are adopted like Promise.resolve
+ if ( remaining <= 1 ) {
+ adoptValue( singleValue, primary.done( updateFunc( i ) ).resolve, primary.reject,
+ !remaining );
+
+ // Use .then() to unwrap secondary thenables (cf. gh-3000)
+ if ( primary.state() === "pending" ||
+ isFunction( resolveValues[ i ] && resolveValues[ i ].then ) ) {
+
+ return primary.then();
+ }
+ }
+
+ // Multiple arguments are aggregated like Promise.all array elements
+ while ( i-- ) {
+ adoptValue( resolveValues[ i ], updateFunc( i ), primary.reject );
+ }
+
+ return primary.promise();
+ }
+} );
+
+
+// These usually indicate a programmer mistake during development,
+// warn about them ASAP rather than swallowing them by default.
+var rerrorNames = /^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;
+
+jQuery.Deferred.exceptionHook = function( error, stack ) {
+
+ // Support: IE 8 - 9 only
+ // Console exists when dev tools are open, which can happen at any time
+ if ( window.console && window.console.warn && error && rerrorNames.test( error.name ) ) {
+ window.console.warn( "jQuery.Deferred exception: " + error.message, error.stack, stack );
+ }
+};
+
+
+
+
+jQuery.readyException = function( error ) {
+ window.setTimeout( function() {
+ throw error;
+ } );
+};
+
+
+
+
+// The deferred used on DOM ready
+var readyList = jQuery.Deferred();
+
+jQuery.fn.ready = function( fn ) {
+
+ readyList
+ .then( fn )
+
+ // Wrap jQuery.readyException in a function so that the lookup
+ // happens at the time of error handling instead of callback
+ // registration.
+ .catch( function( error ) {
+ jQuery.readyException( error );
+ } );
+
+ return this;
+};
+
+jQuery.extend( {
+
+ // Is the DOM ready to be used? Set to true once it occurs.
+ isReady: false,
+
+ // A counter to track how many items to wait for before
+ // the ready event fires. See trac-6781
+ readyWait: 1,
+
+ // Handle when the DOM is ready
+ ready: function( wait ) {
+
+ // Abort if there are pending holds or we're already ready
+ if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) {
+ return;
+ }
+
+ // Remember that the DOM is ready
+ jQuery.isReady = true;
+
+ // If a normal DOM Ready event fired, decrement, and wait if need be
+ if ( wait !== true && --jQuery.readyWait > 0 ) {
+ return;
+ }
+
+ // If there are functions bound, to execute
+ readyList.resolveWith( document, [ jQuery ] );
+ }
+} );
+
+jQuery.ready.then = readyList.then;
+
+// The ready event handler and self cleanup method
+function completed() {
+ document.removeEventListener( "DOMContentLoaded", completed );
+ window.removeEventListener( "load", completed );
+ jQuery.ready();
+}
+
+// Catch cases where $(document).ready() is called
+// after the browser event has already occurred.
+// Support: IE <=9 - 10 only
+// Older IE sometimes signals "interactive" too soon
+if ( document.readyState === "complete" ||
+ ( document.readyState !== "loading" && !document.documentElement.doScroll ) ) {
+
+ // Handle it asynchronously to allow scripts the opportunity to delay ready
+ window.setTimeout( jQuery.ready );
+
+} else {
+
+ // Use the handy event callback
+ document.addEventListener( "DOMContentLoaded", completed );
+
+ // A fallback to window.onload, that will always work
+ window.addEventListener( "load", completed );
+}
+
+
+
+
+// Multifunctional method to get and set values of a collection
+// The value/s can optionally be executed if it's a function
+var access = function( elems, fn, key, value, chainable, emptyGet, raw ) {
+ var i = 0,
+ len = elems.length,
+ bulk = key == null;
+
+ // Sets many values
+ if ( toType( key ) === "object" ) {
+ chainable = true;
+ for ( i in key ) {
+ access( elems, fn, i, key[ i ], true, emptyGet, raw );
+ }
+
+ // Sets one value
+ } else if ( value !== undefined ) {
+ chainable = true;
+
+ if ( !isFunction( value ) ) {
+ raw = true;
+ }
+
+ if ( bulk ) {
+
+ // Bulk operations run against the entire set
+ if ( raw ) {
+ fn.call( elems, value );
+ fn = null;
+
+ // ...except when executing function values
+ } else {
+ bulk = fn;
+ fn = function( elem, _key, value ) {
+ return bulk.call( jQuery( elem ), value );
+ };
+ }
+ }
+
+ if ( fn ) {
+ for ( ; i < len; i++ ) {
+ fn(
+ elems[ i ], key, raw ?
+ value :
+ value.call( elems[ i ], i, fn( elems[ i ], key ) )
+ );
+ }
+ }
+ }
+
+ if ( chainable ) {
+ return elems;
+ }
+
+ // Gets
+ if ( bulk ) {
+ return fn.call( elems );
+ }
+
+ return len ? fn( elems[ 0 ], key ) : emptyGet;
+};
+
+
+// Matches dashed string for camelizing
+var rmsPrefix = /^-ms-/,
+ rdashAlpha = /-([a-z])/g;
+
+// Used by camelCase as callback to replace()
+function fcamelCase( _all, letter ) {
+ return letter.toUpperCase();
+}
+
+// Convert dashed to camelCase; used by the css and data modules
+// Support: IE <=9 - 11, Edge 12 - 15
+// Microsoft forgot to hump their vendor prefix (trac-9572)
+function camelCase( string ) {
+ return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase );
+}
+var acceptData = function( owner ) {
+
+ // Accepts only:
+ // - Node
+ // - Node.ELEMENT_NODE
+ // - Node.DOCUMENT_NODE
+ // - Object
+ // - Any
+ return owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType );
+};
+
+
+
+
+function Data() {
+ this.expando = jQuery.expando + Data.uid++;
+}
+
+Data.uid = 1;
+
+Data.prototype = {
+
+ cache: function( owner ) {
+
+ // Check if the owner object already has a cache
+ var value = owner[ this.expando ];
+
+ // If not, create one
+ if ( !value ) {
+ value = {};
+
+ // We can accept data for non-element nodes in modern browsers,
+ // but we should not, see trac-8335.
+ // Always return an empty object.
+ if ( acceptData( owner ) ) {
+
+ // If it is a node unlikely to be stringify-ed or looped over
+ // use plain assignment
+ if ( owner.nodeType ) {
+ owner[ this.expando ] = value;
+
+ // Otherwise secure it in a non-enumerable property
+ // configurable must be true to allow the property to be
+ // deleted when data is removed
+ } else {
+ Object.defineProperty( owner, this.expando, {
+ value: value,
+ configurable: true
+ } );
+ }
+ }
+ }
+
+ return value;
+ },
+ set: function( owner, data, value ) {
+ var prop,
+ cache = this.cache( owner );
+
+ // Handle: [ owner, key, value ] args
+ // Always use camelCase key (gh-2257)
+ if ( typeof data === "string" ) {
+ cache[ camelCase( data ) ] = value;
+
+ // Handle: [ owner, { properties } ] args
+ } else {
+
+ // Copy the properties one-by-one to the cache object
+ for ( prop in data ) {
+ cache[ camelCase( prop ) ] = data[ prop ];
+ }
+ }
+ return cache;
+ },
+ get: function( owner, key ) {
+ return key === undefined ?
+ this.cache( owner ) :
+
+ // Always use camelCase key (gh-2257)
+ owner[ this.expando ] && owner[ this.expando ][ camelCase( key ) ];
+ },
+ access: function( owner, key, value ) {
+
+ // In cases where either:
+ //
+ // 1. No key was specified
+ // 2. A string key was specified, but no value provided
+ //
+ // Take the "read" path and allow the get method to determine
+ // which value to return, respectively either:
+ //
+ // 1. The entire cache object
+ // 2. The data stored at the key
+ //
+ if ( key === undefined ||
+ ( ( key && typeof key === "string" ) && value === undefined ) ) {
+
+ return this.get( owner, key );
+ }
+
+ // When the key is not a string, or both a key and value
+ // are specified, set or extend (existing objects) with either:
+ //
+ // 1. An object of properties
+ // 2. A key and value
+ //
+ this.set( owner, key, value );
+
+ // Since the "set" path can have two possible entry points
+ // return the expected data based on which path was taken[*]
+ return value !== undefined ? value : key;
+ },
+ remove: function( owner, key ) {
+ var i,
+ cache = owner[ this.expando ];
+
+ if ( cache === undefined ) {
+ return;
+ }
+
+ if ( key !== undefined ) {
+
+ // Support array or space separated string of keys
+ if ( Array.isArray( key ) ) {
+
+ // If key is an array of keys...
+ // We always set camelCase keys, so remove that.
+ key = key.map( camelCase );
+ } else {
+ key = camelCase( key );
+
+ // If a key with the spaces exists, use it.
+ // Otherwise, create an array by matching non-whitespace
+ key = key in cache ?
+ [ key ] :
+ ( key.match( rnothtmlwhite ) || [] );
+ }
+
+ i = key.length;
+
+ while ( i-- ) {
+ delete cache[ key[ i ] ];
+ }
+ }
+
+ // Remove the expando if there's no more data
+ if ( key === undefined || jQuery.isEmptyObject( cache ) ) {
+
+ // Support: Chrome <=35 - 45
+ // Webkit & Blink performance suffers when deleting properties
+ // from DOM nodes, so set to undefined instead
+ // https://bugs.chromium.org/p/chromium/issues/detail?id=378607 (bug restricted)
+ if ( owner.nodeType ) {
+ owner[ this.expando ] = undefined;
+ } else {
+ delete owner[ this.expando ];
+ }
+ }
+ },
+ hasData: function( owner ) {
+ var cache = owner[ this.expando ];
+ return cache !== undefined && !jQuery.isEmptyObject( cache );
+ }
+};
+var dataPriv = new Data();
+
+var dataUser = new Data();
+
+
+
+// Implementation Summary
+//
+// 1. Enforce API surface and semantic compatibility with 1.9.x branch
+// 2. Improve the module's maintainability by reducing the storage
+// paths to a single mechanism.
+// 3. Use the same single mechanism to support "private" and "user" data.
+// 4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData)
+// 5. Avoid exposing implementation details on user objects (eg. expando properties)
+// 6. Provide a clear path for implementation upgrade to WeakMap in 2014
+
+var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,
+ rmultiDash = /[A-Z]/g;
+
+function getData( data ) {
+ if ( data === "true" ) {
+ return true;
+ }
+
+ if ( data === "false" ) {
+ return false;
+ }
+
+ if ( data === "null" ) {
+ return null;
+ }
+
+ // Only convert to a number if it doesn't change the string
+ if ( data === +data + "" ) {
+ return +data;
+ }
+
+ if ( rbrace.test( data ) ) {
+ return JSON.parse( data );
+ }
+
+ return data;
+}
+
+function dataAttr( elem, key, data ) {
+ var name;
+
+ // If nothing was found internally, try to fetch any
+ // data from the HTML5 data-* attribute
+ if ( data === undefined && elem.nodeType === 1 ) {
+ name = "data-" + key.replace( rmultiDash, "-$&" ).toLowerCase();
+ data = elem.getAttribute( name );
+
+ if ( typeof data === "string" ) {
+ try {
+ data = getData( data );
+ } catch ( e ) {}
+
+ // Make sure we set the data so it isn't changed later
+ dataUser.set( elem, key, data );
+ } else {
+ data = undefined;
+ }
+ }
+ return data;
+}
+
+jQuery.extend( {
+ hasData: function( elem ) {
+ return dataUser.hasData( elem ) || dataPriv.hasData( elem );
+ },
+
+ data: function( elem, name, data ) {
+ return dataUser.access( elem, name, data );
+ },
+
+ removeData: function( elem, name ) {
+ dataUser.remove( elem, name );
+ },
+
+ // TODO: Now that all calls to _data and _removeData have been replaced
+ // with direct calls to dataPriv methods, these can be deprecated.
+ _data: function( elem, name, data ) {
+ return dataPriv.access( elem, name, data );
+ },
+
+ _removeData: function( elem, name ) {
+ dataPriv.remove( elem, name );
+ }
+} );
+
+jQuery.fn.extend( {
+ data: function( key, value ) {
+ var i, name, data,
+ elem = this[ 0 ],
+ attrs = elem && elem.attributes;
+
+ // Gets all values
+ if ( key === undefined ) {
+ if ( this.length ) {
+ data = dataUser.get( elem );
+
+ if ( elem.nodeType === 1 && !dataPriv.get( elem, "hasDataAttrs" ) ) {
+ i = attrs.length;
+ while ( i-- ) {
+
+ // Support: IE 11 only
+ // The attrs elements can be null (trac-14894)
+ if ( attrs[ i ] ) {
+ name = attrs[ i ].name;
+ if ( name.indexOf( "data-" ) === 0 ) {
+ name = camelCase( name.slice( 5 ) );
+ dataAttr( elem, name, data[ name ] );
+ }
+ }
+ }
+ dataPriv.set( elem, "hasDataAttrs", true );
+ }
+ }
+
+ return data;
+ }
+
+ // Sets multiple values
+ if ( typeof key === "object" ) {
+ return this.each( function() {
+ dataUser.set( this, key );
+ } );
+ }
+
+ return access( this, function( value ) {
+ var data;
+
+ // The calling jQuery object (element matches) is not empty
+ // (and therefore has an element appears at this[ 0 ]) and the
+ // `value` parameter was not undefined. An empty jQuery object
+ // will result in `undefined` for elem = this[ 0 ] which will
+ // throw an exception if an attempt to read a data cache is made.
+ if ( elem && value === undefined ) {
+
+ // Attempt to get data from the cache
+ // The key will always be camelCased in Data
+ data = dataUser.get( elem, key );
+ if ( data !== undefined ) {
+ return data;
+ }
+
+ // Attempt to "discover" the data in
+ // HTML5 custom data-* attrs
+ data = dataAttr( elem, key );
+ if ( data !== undefined ) {
+ return data;
+ }
+
+ // We tried really hard, but the data doesn't exist.
+ return;
+ }
+
+ // Set the data...
+ this.each( function() {
+
+ // We always store the camelCased key
+ dataUser.set( this, key, value );
+ } );
+ }, null, value, arguments.length > 1, null, true );
+ },
+
+ removeData: function( key ) {
+ return this.each( function() {
+ dataUser.remove( this, key );
+ } );
+ }
+} );
+
+
+jQuery.extend( {
+ queue: function( elem, type, data ) {
+ var queue;
+
+ if ( elem ) {
+ type = ( type || "fx" ) + "queue";
+ queue = dataPriv.get( elem, type );
+
+ // Speed up dequeue by getting out quickly if this is just a lookup
+ if ( data ) {
+ if ( !queue || Array.isArray( data ) ) {
+ queue = dataPriv.access( elem, type, jQuery.makeArray( data ) );
+ } else {
+ queue.push( data );
+ }
+ }
+ return queue || [];
+ }
+ },
+
+ dequeue: function( elem, type ) {
+ type = type || "fx";
+
+ var queue = jQuery.queue( elem, type ),
+ startLength = queue.length,
+ fn = queue.shift(),
+ hooks = jQuery._queueHooks( elem, type ),
+ next = function() {
+ jQuery.dequeue( elem, type );
+ };
+
+ // If the fx queue is dequeued, always remove the progress sentinel
+ if ( fn === "inprogress" ) {
+ fn = queue.shift();
+ startLength--;
+ }
+
+ if ( fn ) {
+
+ // Add a progress sentinel to prevent the fx queue from being
+ // automatically dequeued
+ if ( type === "fx" ) {
+ queue.unshift( "inprogress" );
+ }
+
+ // Clear up the last queue stop function
+ delete hooks.stop;
+ fn.call( elem, next, hooks );
+ }
+
+ if ( !startLength && hooks ) {
+ hooks.empty.fire();
+ }
+ },
+
+ // Not public - generate a queueHooks object, or return the current one
+ _queueHooks: function( elem, type ) {
+ var key = type + "queueHooks";
+ return dataPriv.get( elem, key ) || dataPriv.access( elem, key, {
+ empty: jQuery.Callbacks( "once memory" ).add( function() {
+ dataPriv.remove( elem, [ type + "queue", key ] );
+ } )
+ } );
+ }
+} );
+
+jQuery.fn.extend( {
+ queue: function( type, data ) {
+ var setter = 2;
+
+ if ( typeof type !== "string" ) {
+ data = type;
+ type = "fx";
+ setter--;
+ }
+
+ if ( arguments.length < setter ) {
+ return jQuery.queue( this[ 0 ], type );
+ }
+
+ return data === undefined ?
+ this :
+ this.each( function() {
+ var queue = jQuery.queue( this, type, data );
+
+ // Ensure a hooks for this queue
+ jQuery._queueHooks( this, type );
+
+ if ( type === "fx" && queue[ 0 ] !== "inprogress" ) {
+ jQuery.dequeue( this, type );
+ }
+ } );
+ },
+ dequeue: function( type ) {
+ return this.each( function() {
+ jQuery.dequeue( this, type );
+ } );
+ },
+ clearQueue: function( type ) {
+ return this.queue( type || "fx", [] );
+ },
+
+ // Get a promise resolved when queues of a certain type
+ // are emptied (fx is the type by default)
+ promise: function( type, obj ) {
+ var tmp,
+ count = 1,
+ defer = jQuery.Deferred(),
+ elements = this,
+ i = this.length,
+ resolve = function() {
+ if ( !( --count ) ) {
+ defer.resolveWith( elements, [ elements ] );
+ }
+ };
+
+ if ( typeof type !== "string" ) {
+ obj = type;
+ type = undefined;
+ }
+ type = type || "fx";
+
+ while ( i-- ) {
+ tmp = dataPriv.get( elements[ i ], type + "queueHooks" );
+ if ( tmp && tmp.empty ) {
+ count++;
+ tmp.empty.add( resolve );
+ }
+ }
+ resolve();
+ return defer.promise( obj );
+ }
+} );
+var pnum = ( /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/ ).source;
+
+var rcssNum = new RegExp( "^(?:([+-])=|)(" + pnum + ")([a-z%]*)$", "i" );
+
+
+var cssExpand = [ "Top", "Right", "Bottom", "Left" ];
+
+var documentElement = document.documentElement;
+
+
+
+ var isAttached = function( elem ) {
+ return jQuery.contains( elem.ownerDocument, elem );
+ },
+ composed = { composed: true };
+
+ // Support: IE 9 - 11+, Edge 12 - 18+, iOS 10.0 - 10.2 only
+ // Check attachment across shadow DOM boundaries when possible (gh-3504)
+ // Support: iOS 10.0-10.2 only
+ // Early iOS 10 versions support `attachShadow` but not `getRootNode`,
+ // leading to errors. We need to check for `getRootNode`.
+ if ( documentElement.getRootNode ) {
+ isAttached = function( elem ) {
+ return jQuery.contains( elem.ownerDocument, elem ) ||
+ elem.getRootNode( composed ) === elem.ownerDocument;
+ };
+ }
+var isHiddenWithinTree = function( elem, el ) {
+
+ // isHiddenWithinTree might be called from jQuery#filter function;
+ // in that case, element will be second argument
+ elem = el || elem;
+
+ // Inline style trumps all
+ return elem.style.display === "none" ||
+ elem.style.display === "" &&
+
+ // Otherwise, check computed style
+ // Support: Firefox <=43 - 45
+ // Disconnected elements can have computed display: none, so first confirm that elem is
+ // in the document.
+ isAttached( elem ) &&
+
+ jQuery.css( elem, "display" ) === "none";
+ };
+
+
+
+function adjustCSS( elem, prop, valueParts, tween ) {
+ var adjusted, scale,
+ maxIterations = 20,
+ currentValue = tween ?
+ function() {
+ return tween.cur();
+ } :
+ function() {
+ return jQuery.css( elem, prop, "" );
+ },
+ initial = currentValue(),
+ unit = valueParts && valueParts[ 3 ] || ( jQuery.cssNumber[ prop ] ? "" : "px" ),
+
+ // Starting value computation is required for potential unit mismatches
+ initialInUnit = elem.nodeType &&
+ ( jQuery.cssNumber[ prop ] || unit !== "px" && +initial ) &&
+ rcssNum.exec( jQuery.css( elem, prop ) );
+
+ if ( initialInUnit && initialInUnit[ 3 ] !== unit ) {
+
+ // Support: Firefox <=54
+ // Halve the iteration target value to prevent interference from CSS upper bounds (gh-2144)
+ initial = initial / 2;
+
+ // Trust units reported by jQuery.css
+ unit = unit || initialInUnit[ 3 ];
+
+ // Iteratively approximate from a nonzero starting point
+ initialInUnit = +initial || 1;
+
+ while ( maxIterations-- ) {
+
+ // Evaluate and update our best guess (doubling guesses that zero out).
+ // Finish if the scale equals or crosses 1 (making the old*new product non-positive).
+ jQuery.style( elem, prop, initialInUnit + unit );
+ if ( ( 1 - scale ) * ( 1 - ( scale = currentValue() / initial || 0.5 ) ) <= 0 ) {
+ maxIterations = 0;
+ }
+ initialInUnit = initialInUnit / scale;
+
+ }
+
+ initialInUnit = initialInUnit * 2;
+ jQuery.style( elem, prop, initialInUnit + unit );
+
+ // Make sure we update the tween properties later on
+ valueParts = valueParts || [];
+ }
+
+ if ( valueParts ) {
+ initialInUnit = +initialInUnit || +initial || 0;
+
+ // Apply relative offset (+=/-=) if specified
+ adjusted = valueParts[ 1 ] ?
+ initialInUnit + ( valueParts[ 1 ] + 1 ) * valueParts[ 2 ] :
+ +valueParts[ 2 ];
+ if ( tween ) {
+ tween.unit = unit;
+ tween.start = initialInUnit;
+ tween.end = adjusted;
+ }
+ }
+ return adjusted;
+}
+
+
+var defaultDisplayMap = {};
+
+function getDefaultDisplay( elem ) {
+ var temp,
+ doc = elem.ownerDocument,
+ nodeName = elem.nodeName,
+ display = defaultDisplayMap[ nodeName ];
+
+ if ( display ) {
+ return display;
+ }
+
+ temp = doc.body.appendChild( doc.createElement( nodeName ) );
+ display = jQuery.css( temp, "display" );
+
+ temp.parentNode.removeChild( temp );
+
+ if ( display === "none" ) {
+ display = "block";
+ }
+ defaultDisplayMap[ nodeName ] = display;
+
+ return display;
+}
+
+function showHide( elements, show ) {
+ var display, elem,
+ values = [],
+ index = 0,
+ length = elements.length;
+
+ // Determine new display value for elements that need to change
+ for ( ; index < length; index++ ) {
+ elem = elements[ index ];
+ if ( !elem.style ) {
+ continue;
+ }
+
+ display = elem.style.display;
+ if ( show ) {
+
+ // Since we force visibility upon cascade-hidden elements, an immediate (and slow)
+ // check is required in this first loop unless we have a nonempty display value (either
+ // inline or about-to-be-restored)
+ if ( display === "none" ) {
+ values[ index ] = dataPriv.get( elem, "display" ) || null;
+ if ( !values[ index ] ) {
+ elem.style.display = "";
+ }
+ }
+ if ( elem.style.display === "" && isHiddenWithinTree( elem ) ) {
+ values[ index ] = getDefaultDisplay( elem );
+ }
+ } else {
+ if ( display !== "none" ) {
+ values[ index ] = "none";
+
+ // Remember what we're overwriting
+ dataPriv.set( elem, "display", display );
+ }
+ }
+ }
+
+ // Set the display of the elements in a second loop to avoid constant reflow
+ for ( index = 0; index < length; index++ ) {
+ if ( values[ index ] != null ) {
+ elements[ index ].style.display = values[ index ];
+ }
+ }
+
+ return elements;
+}
+
+jQuery.fn.extend( {
+ show: function() {
+ return showHide( this, true );
+ },
+ hide: function() {
+ return showHide( this );
+ },
+ toggle: function( state ) {
+ if ( typeof state === "boolean" ) {
+ return state ? this.show() : this.hide();
+ }
+
+ return this.each( function() {
+ if ( isHiddenWithinTree( this ) ) {
+ jQuery( this ).show();
+ } else {
+ jQuery( this ).hide();
+ }
+ } );
+ }
+} );
+var rcheckableType = ( /^(?:checkbox|radio)$/i );
+
+var rtagName = ( /<([a-z][^\/\0>\x20\t\r\n\f]*)/i );
+
+var rscriptType = ( /^$|^module$|\/(?:java|ecma)script/i );
+
+
+
+( function() {
+ var fragment = document.createDocumentFragment(),
+ div = fragment.appendChild( document.createElement( "div" ) ),
+ input = document.createElement( "input" );
+
+ // Support: Android 4.0 - 4.3 only
+ // Check state lost if the name is set (trac-11217)
+ // Support: Windows Web Apps (WWA)
+ // `name` and `type` must use .setAttribute for WWA (trac-14901)
+ input.setAttribute( "type", "radio" );
+ input.setAttribute( "checked", "checked" );
+ input.setAttribute( "name", "t" );
+
+ div.appendChild( input );
+
+ // Support: Android <=4.1 only
+ // Older WebKit doesn't clone checked state correctly in fragments
+ support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked;
+
+ // Support: IE <=11 only
+ // Make sure textarea (and checkbox) defaultValue is properly cloned
+ div.innerHTML = "";
+ support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue;
+
+ // Support: IE <=9 only
+ // IE <=9 replaces tags with their contents when inserted outside of
+ // the select element.
+ div.innerHTML = " ";
+ support.option = !!div.lastChild;
+} )();
+
+
+// We have to close these tags to support XHTML (trac-13200)
+var wrapMap = {
+
+ // XHTML parsers do not magically insert elements in the
+ // same way that tag soup parsers do. So we cannot shorten
+ // this by omitting or other required elements.
+ thead: [ 1, "" ],
+ col: [ 2, "" ],
+ tr: [ 2, "" ],
+ td: [ 3, "" ],
+
+ _default: [ 0, "", "" ]
+};
+
+wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;
+wrapMap.th = wrapMap.td;
+
+// Support: IE <=9 only
+if ( !support.option ) {
+ wrapMap.optgroup = wrapMap.option = [ 1, "", " " ];
+}
+
+
+function getAll( context, tag ) {
+
+ // Support: IE <=9 - 11 only
+ // Use typeof to avoid zero-argument method invocation on host objects (trac-15151)
+ var ret;
+
+ if ( typeof context.getElementsByTagName !== "undefined" ) {
+ ret = context.getElementsByTagName( tag || "*" );
+
+ } else if ( typeof context.querySelectorAll !== "undefined" ) {
+ ret = context.querySelectorAll( tag || "*" );
+
+ } else {
+ ret = [];
+ }
+
+ if ( tag === undefined || tag && nodeName( context, tag ) ) {
+ return jQuery.merge( [ context ], ret );
+ }
+
+ return ret;
+}
+
+
+// Mark scripts as having already been evaluated
+function setGlobalEval( elems, refElements ) {
+ var i = 0,
+ l = elems.length;
+
+ for ( ; i < l; i++ ) {
+ dataPriv.set(
+ elems[ i ],
+ "globalEval",
+ !refElements || dataPriv.get( refElements[ i ], "globalEval" )
+ );
+ }
+}
+
+
+var rhtml = /<|?\w+;/;
+
+function buildFragment( elems, context, scripts, selection, ignored ) {
+ var elem, tmp, tag, wrap, attached, j,
+ fragment = context.createDocumentFragment(),
+ nodes = [],
+ i = 0,
+ l = elems.length;
+
+ for ( ; i < l; i++ ) {
+ elem = elems[ i ];
+
+ if ( elem || elem === 0 ) {
+
+ // Add nodes directly
+ if ( toType( elem ) === "object" ) {
+
+ // Support: Android <=4.0 only, PhantomJS 1 only
+ // push.apply(_, arraylike) throws on ancient WebKit
+ jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem );
+
+ // Convert non-html into a text node
+ } else if ( !rhtml.test( elem ) ) {
+ nodes.push( context.createTextNode( elem ) );
+
+ // Convert html into DOM nodes
+ } else {
+ tmp = tmp || fragment.appendChild( context.createElement( "div" ) );
+
+ // Deserialize a standard representation
+ tag = ( rtagName.exec( elem ) || [ "", "" ] )[ 1 ].toLowerCase();
+ wrap = wrapMap[ tag ] || wrapMap._default;
+ tmp.innerHTML = wrap[ 1 ] + jQuery.htmlPrefilter( elem ) + wrap[ 2 ];
+
+ // Descend through wrappers to the right content
+ j = wrap[ 0 ];
+ while ( j-- ) {
+ tmp = tmp.lastChild;
+ }
+
+ // Support: Android <=4.0 only, PhantomJS 1 only
+ // push.apply(_, arraylike) throws on ancient WebKit
+ jQuery.merge( nodes, tmp.childNodes );
+
+ // Remember the top-level container
+ tmp = fragment.firstChild;
+
+ // Ensure the created nodes are orphaned (trac-12392)
+ tmp.textContent = "";
+ }
+ }
+ }
+
+ // Remove wrapper from fragment
+ fragment.textContent = "";
+
+ i = 0;
+ while ( ( elem = nodes[ i++ ] ) ) {
+
+ // Skip elements already in the context collection (trac-4087)
+ if ( selection && jQuery.inArray( elem, selection ) > -1 ) {
+ if ( ignored ) {
+ ignored.push( elem );
+ }
+ continue;
+ }
+
+ attached = isAttached( elem );
+
+ // Append to fragment
+ tmp = getAll( fragment.appendChild( elem ), "script" );
+
+ // Preserve script evaluation history
+ if ( attached ) {
+ setGlobalEval( tmp );
+ }
+
+ // Capture executables
+ if ( scripts ) {
+ j = 0;
+ while ( ( elem = tmp[ j++ ] ) ) {
+ if ( rscriptType.test( elem.type || "" ) ) {
+ scripts.push( elem );
+ }
+ }
+ }
+ }
+
+ return fragment;
+}
+
+
+var rtypenamespace = /^([^.]*)(?:\.(.+)|)/;
+
+function returnTrue() {
+ return true;
+}
+
+function returnFalse() {
+ return false;
+}
+
+// Support: IE <=9 - 11+
+// focus() and blur() are asynchronous, except when they are no-op.
+// So expect focus to be synchronous when the element is already active,
+// and blur to be synchronous when the element is not already active.
+// (focus and blur are always synchronous in other supported browsers,
+// this just defines when we can count on it).
+function expectSync( elem, type ) {
+ return ( elem === safeActiveElement() ) === ( type === "focus" );
+}
+
+// Support: IE <=9 only
+// Accessing document.activeElement can throw unexpectedly
+// https://bugs.jquery.com/ticket/13393
+function safeActiveElement() {
+ try {
+ return document.activeElement;
+ } catch ( err ) { }
+}
+
+function on( elem, types, selector, data, fn, one ) {
+ var origFn, type;
+
+ // Types can be a map of types/handlers
+ if ( typeof types === "object" ) {
+
+ // ( types-Object, selector, data )
+ if ( typeof selector !== "string" ) {
+
+ // ( types-Object, data )
+ data = data || selector;
+ selector = undefined;
+ }
+ for ( type in types ) {
+ on( elem, type, selector, data, types[ type ], one );
+ }
+ return elem;
+ }
+
+ if ( data == null && fn == null ) {
+
+ // ( types, fn )
+ fn = selector;
+ data = selector = undefined;
+ } else if ( fn == null ) {
+ if ( typeof selector === "string" ) {
+
+ // ( types, selector, fn )
+ fn = data;
+ data = undefined;
+ } else {
+
+ // ( types, data, fn )
+ fn = data;
+ data = selector;
+ selector = undefined;
+ }
+ }
+ if ( fn === false ) {
+ fn = returnFalse;
+ } else if ( !fn ) {
+ return elem;
+ }
+
+ if ( one === 1 ) {
+ origFn = fn;
+ fn = function( event ) {
+
+ // Can use an empty set, since event contains the info
+ jQuery().off( event );
+ return origFn.apply( this, arguments );
+ };
+
+ // Use same guid so caller can remove using origFn
+ fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ );
+ }
+ return elem.each( function() {
+ jQuery.event.add( this, types, fn, data, selector );
+ } );
+}
+
+/*
+ * Helper functions for managing events -- not part of the public interface.
+ * Props to Dean Edwards' addEvent library for many of the ideas.
+ */
+jQuery.event = {
+
+ global: {},
+
+ add: function( elem, types, handler, data, selector ) {
+
+ var handleObjIn, eventHandle, tmp,
+ events, t, handleObj,
+ special, handlers, type, namespaces, origType,
+ elemData = dataPriv.get( elem );
+
+ // Only attach events to objects that accept data
+ if ( !acceptData( elem ) ) {
+ return;
+ }
+
+ // Caller can pass in an object of custom data in lieu of the handler
+ if ( handler.handler ) {
+ handleObjIn = handler;
+ handler = handleObjIn.handler;
+ selector = handleObjIn.selector;
+ }
+
+ // Ensure that invalid selectors throw exceptions at attach time
+ // Evaluate against documentElement in case elem is a non-element node (e.g., document)
+ if ( selector ) {
+ jQuery.find.matchesSelector( documentElement, selector );
+ }
+
+ // Make sure that the handler has a unique ID, used to find/remove it later
+ if ( !handler.guid ) {
+ handler.guid = jQuery.guid++;
+ }
+
+ // Init the element's event structure and main handler, if this is the first
+ if ( !( events = elemData.events ) ) {
+ events = elemData.events = Object.create( null );
+ }
+ if ( !( eventHandle = elemData.handle ) ) {
+ eventHandle = elemData.handle = function( e ) {
+
+ // Discard the second event of a jQuery.event.trigger() and
+ // when an event is called after a page has unloaded
+ return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ?
+ jQuery.event.dispatch.apply( elem, arguments ) : undefined;
+ };
+ }
+
+ // Handle multiple events separated by a space
+ types = ( types || "" ).match( rnothtmlwhite ) || [ "" ];
+ t = types.length;
+ while ( t-- ) {
+ tmp = rtypenamespace.exec( types[ t ] ) || [];
+ type = origType = tmp[ 1 ];
+ namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort();
+
+ // There *must* be a type, no attaching namespace-only handlers
+ if ( !type ) {
+ continue;
+ }
+
+ // If event changes its type, use the special event handlers for the changed type
+ special = jQuery.event.special[ type ] || {};
+
+ // If selector defined, determine special event api type, otherwise given type
+ type = ( selector ? special.delegateType : special.bindType ) || type;
+
+ // Update special based on newly reset type
+ special = jQuery.event.special[ type ] || {};
+
+ // handleObj is passed to all event handlers
+ handleObj = jQuery.extend( {
+ type: type,
+ origType: origType,
+ data: data,
+ handler: handler,
+ guid: handler.guid,
+ selector: selector,
+ needsContext: selector && jQuery.expr.match.needsContext.test( selector ),
+ namespace: namespaces.join( "." )
+ }, handleObjIn );
+
+ // Init the event handler queue if we're the first
+ if ( !( handlers = events[ type ] ) ) {
+ handlers = events[ type ] = [];
+ handlers.delegateCount = 0;
+
+ // Only use addEventListener if the special events handler returns false
+ if ( !special.setup ||
+ special.setup.call( elem, data, namespaces, eventHandle ) === false ) {
+
+ if ( elem.addEventListener ) {
+ elem.addEventListener( type, eventHandle );
+ }
+ }
+ }
+
+ if ( special.add ) {
+ special.add.call( elem, handleObj );
+
+ if ( !handleObj.handler.guid ) {
+ handleObj.handler.guid = handler.guid;
+ }
+ }
+
+ // Add to the element's handler list, delegates in front
+ if ( selector ) {
+ handlers.splice( handlers.delegateCount++, 0, handleObj );
+ } else {
+ handlers.push( handleObj );
+ }
+
+ // Keep track of which events have ever been used, for event optimization
+ jQuery.event.global[ type ] = true;
+ }
+
+ },
+
+ // Detach an event or set of events from an element
+ remove: function( elem, types, handler, selector, mappedTypes ) {
+
+ var j, origCount, tmp,
+ events, t, handleObj,
+ special, handlers, type, namespaces, origType,
+ elemData = dataPriv.hasData( elem ) && dataPriv.get( elem );
+
+ if ( !elemData || !( events = elemData.events ) ) {
+ return;
+ }
+
+ // Once for each type.namespace in types; type may be omitted
+ types = ( types || "" ).match( rnothtmlwhite ) || [ "" ];
+ t = types.length;
+ while ( t-- ) {
+ tmp = rtypenamespace.exec( types[ t ] ) || [];
+ type = origType = tmp[ 1 ];
+ namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort();
+
+ // Unbind all events (on this namespace, if provided) for the element
+ if ( !type ) {
+ for ( type in events ) {
+ jQuery.event.remove( elem, type + types[ t ], handler, selector, true );
+ }
+ continue;
+ }
+
+ special = jQuery.event.special[ type ] || {};
+ type = ( selector ? special.delegateType : special.bindType ) || type;
+ handlers = events[ type ] || [];
+ tmp = tmp[ 2 ] &&
+ new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" );
+
+ // Remove matching events
+ origCount = j = handlers.length;
+ while ( j-- ) {
+ handleObj = handlers[ j ];
+
+ if ( ( mappedTypes || origType === handleObj.origType ) &&
+ ( !handler || handler.guid === handleObj.guid ) &&
+ ( !tmp || tmp.test( handleObj.namespace ) ) &&
+ ( !selector || selector === handleObj.selector ||
+ selector === "**" && handleObj.selector ) ) {
+ handlers.splice( j, 1 );
+
+ if ( handleObj.selector ) {
+ handlers.delegateCount--;
+ }
+ if ( special.remove ) {
+ special.remove.call( elem, handleObj );
+ }
+ }
+ }
+
+ // Remove generic event handler if we removed something and no more handlers exist
+ // (avoids potential for endless recursion during removal of special event handlers)
+ if ( origCount && !handlers.length ) {
+ if ( !special.teardown ||
+ special.teardown.call( elem, namespaces, elemData.handle ) === false ) {
+
+ jQuery.removeEvent( elem, type, elemData.handle );
+ }
+
+ delete events[ type ];
+ }
+ }
+
+ // Remove data and the expando if it's no longer used
+ if ( jQuery.isEmptyObject( events ) ) {
+ dataPriv.remove( elem, "handle events" );
+ }
+ },
+
+ dispatch: function( nativeEvent ) {
+
+ var i, j, ret, matched, handleObj, handlerQueue,
+ args = new Array( arguments.length ),
+
+ // Make a writable jQuery.Event from the native event object
+ event = jQuery.event.fix( nativeEvent ),
+
+ handlers = (
+ dataPriv.get( this, "events" ) || Object.create( null )
+ )[ event.type ] || [],
+ special = jQuery.event.special[ event.type ] || {};
+
+ // Use the fix-ed jQuery.Event rather than the (read-only) native event
+ args[ 0 ] = event;
+
+ for ( i = 1; i < arguments.length; i++ ) {
+ args[ i ] = arguments[ i ];
+ }
+
+ event.delegateTarget = this;
+
+ // Call the preDispatch hook for the mapped type, and let it bail if desired
+ if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) {
+ return;
+ }
+
+ // Determine handlers
+ handlerQueue = jQuery.event.handlers.call( this, event, handlers );
+
+ // Run delegates first; they may want to stop propagation beneath us
+ i = 0;
+ while ( ( matched = handlerQueue[ i++ ] ) && !event.isPropagationStopped() ) {
+ event.currentTarget = matched.elem;
+
+ j = 0;
+ while ( ( handleObj = matched.handlers[ j++ ] ) &&
+ !event.isImmediatePropagationStopped() ) {
+
+ // If the event is namespaced, then each handler is only invoked if it is
+ // specially universal or its namespaces are a superset of the event's.
+ if ( !event.rnamespace || handleObj.namespace === false ||
+ event.rnamespace.test( handleObj.namespace ) ) {
+
+ event.handleObj = handleObj;
+ event.data = handleObj.data;
+
+ ret = ( ( jQuery.event.special[ handleObj.origType ] || {} ).handle ||
+ handleObj.handler ).apply( matched.elem, args );
+
+ if ( ret !== undefined ) {
+ if ( ( event.result = ret ) === false ) {
+ event.preventDefault();
+ event.stopPropagation();
+ }
+ }
+ }
+ }
+ }
+
+ // Call the postDispatch hook for the mapped type
+ if ( special.postDispatch ) {
+ special.postDispatch.call( this, event );
+ }
+
+ return event.result;
+ },
+
+ handlers: function( event, handlers ) {
+ var i, handleObj, sel, matchedHandlers, matchedSelectors,
+ handlerQueue = [],
+ delegateCount = handlers.delegateCount,
+ cur = event.target;
+
+ // Find delegate handlers
+ if ( delegateCount &&
+
+ // Support: IE <=9
+ // Black-hole SVG instance trees (trac-13180)
+ cur.nodeType &&
+
+ // Support: Firefox <=42
+ // Suppress spec-violating clicks indicating a non-primary pointer button (trac-3861)
+ // https://www.w3.org/TR/DOM-Level-3-Events/#event-type-click
+ // Support: IE 11 only
+ // ...but not arrow key "clicks" of radio inputs, which can have `button` -1 (gh-2343)
+ !( event.type === "click" && event.button >= 1 ) ) {
+
+ for ( ; cur !== this; cur = cur.parentNode || this ) {
+
+ // Don't check non-elements (trac-13208)
+ // Don't process clicks on disabled elements (trac-6911, trac-8165, trac-11382, trac-11764)
+ if ( cur.nodeType === 1 && !( event.type === "click" && cur.disabled === true ) ) {
+ matchedHandlers = [];
+ matchedSelectors = {};
+ for ( i = 0; i < delegateCount; i++ ) {
+ handleObj = handlers[ i ];
+
+ // Don't conflict with Object.prototype properties (trac-13203)
+ sel = handleObj.selector + " ";
+
+ if ( matchedSelectors[ sel ] === undefined ) {
+ matchedSelectors[ sel ] = handleObj.needsContext ?
+ jQuery( sel, this ).index( cur ) > -1 :
+ jQuery.find( sel, this, null, [ cur ] ).length;
+ }
+ if ( matchedSelectors[ sel ] ) {
+ matchedHandlers.push( handleObj );
+ }
+ }
+ if ( matchedHandlers.length ) {
+ handlerQueue.push( { elem: cur, handlers: matchedHandlers } );
+ }
+ }
+ }
+ }
+
+ // Add the remaining (directly-bound) handlers
+ cur = this;
+ if ( delegateCount < handlers.length ) {
+ handlerQueue.push( { elem: cur, handlers: handlers.slice( delegateCount ) } );
+ }
+
+ return handlerQueue;
+ },
+
+ addProp: function( name, hook ) {
+ Object.defineProperty( jQuery.Event.prototype, name, {
+ enumerable: true,
+ configurable: true,
+
+ get: isFunction( hook ) ?
+ function() {
+ if ( this.originalEvent ) {
+ return hook( this.originalEvent );
+ }
+ } :
+ function() {
+ if ( this.originalEvent ) {
+ return this.originalEvent[ name ];
+ }
+ },
+
+ set: function( value ) {
+ Object.defineProperty( this, name, {
+ enumerable: true,
+ configurable: true,
+ writable: true,
+ value: value
+ } );
+ }
+ } );
+ },
+
+ fix: function( originalEvent ) {
+ return originalEvent[ jQuery.expando ] ?
+ originalEvent :
+ new jQuery.Event( originalEvent );
+ },
+
+ special: {
+ load: {
+
+ // Prevent triggered image.load events from bubbling to window.load
+ noBubble: true
+ },
+ click: {
+
+ // Utilize native event to ensure correct state for checkable inputs
+ setup: function( data ) {
+
+ // For mutual compressibility with _default, replace `this` access with a local var.
+ // `|| data` is dead code meant only to preserve the variable through minification.
+ var el = this || data;
+
+ // Claim the first handler
+ if ( rcheckableType.test( el.type ) &&
+ el.click && nodeName( el, "input" ) ) {
+
+ // dataPriv.set( el, "click", ... )
+ leverageNative( el, "click", returnTrue );
+ }
+
+ // Return false to allow normal processing in the caller
+ return false;
+ },
+ trigger: function( data ) {
+
+ // For mutual compressibility with _default, replace `this` access with a local var.
+ // `|| data` is dead code meant only to preserve the variable through minification.
+ var el = this || data;
+
+ // Force setup before triggering a click
+ if ( rcheckableType.test( el.type ) &&
+ el.click && nodeName( el, "input" ) ) {
+
+ leverageNative( el, "click" );
+ }
+
+ // Return non-false to allow normal event-path propagation
+ return true;
+ },
+
+ // For cross-browser consistency, suppress native .click() on links
+ // Also prevent it if we're currently inside a leveraged native-event stack
+ _default: function( event ) {
+ var target = event.target;
+ return rcheckableType.test( target.type ) &&
+ target.click && nodeName( target, "input" ) &&
+ dataPriv.get( target, "click" ) ||
+ nodeName( target, "a" );
+ }
+ },
+
+ beforeunload: {
+ postDispatch: function( event ) {
+
+ // Support: Firefox 20+
+ // Firefox doesn't alert if the returnValue field is not set.
+ if ( event.result !== undefined && event.originalEvent ) {
+ event.originalEvent.returnValue = event.result;
+ }
+ }
+ }
+ }
+};
+
+// Ensure the presence of an event listener that handles manually-triggered
+// synthetic events by interrupting progress until reinvoked in response to
+// *native* events that it fires directly, ensuring that state changes have
+// already occurred before other listeners are invoked.
+function leverageNative( el, type, expectSync ) {
+
+ // Missing expectSync indicates a trigger call, which must force setup through jQuery.event.add
+ if ( !expectSync ) {
+ if ( dataPriv.get( el, type ) === undefined ) {
+ jQuery.event.add( el, type, returnTrue );
+ }
+ return;
+ }
+
+ // Register the controller as a special universal handler for all event namespaces
+ dataPriv.set( el, type, false );
+ jQuery.event.add( el, type, {
+ namespace: false,
+ handler: function( event ) {
+ var notAsync, result,
+ saved = dataPriv.get( this, type );
+
+ if ( ( event.isTrigger & 1 ) && this[ type ] ) {
+
+ // Interrupt processing of the outer synthetic .trigger()ed event
+ // Saved data should be false in such cases, but might be a leftover capture object
+ // from an async native handler (gh-4350)
+ if ( !saved.length ) {
+
+ // Store arguments for use when handling the inner native event
+ // There will always be at least one argument (an event object), so this array
+ // will not be confused with a leftover capture object.
+ saved = slice.call( arguments );
+ dataPriv.set( this, type, saved );
+
+ // Trigger the native event and capture its result
+ // Support: IE <=9 - 11+
+ // focus() and blur() are asynchronous
+ notAsync = expectSync( this, type );
+ this[ type ]();
+ result = dataPriv.get( this, type );
+ if ( saved !== result || notAsync ) {
+ dataPriv.set( this, type, false );
+ } else {
+ result = {};
+ }
+ if ( saved !== result ) {
+
+ // Cancel the outer synthetic event
+ event.stopImmediatePropagation();
+ event.preventDefault();
+
+ // Support: Chrome 86+
+ // In Chrome, if an element having a focusout handler is blurred by
+ // clicking outside of it, it invokes the handler synchronously. If
+ // that handler calls `.remove()` on the element, the data is cleared,
+ // leaving `result` undefined. We need to guard against this.
+ return result && result.value;
+ }
+
+ // If this is an inner synthetic event for an event with a bubbling surrogate
+ // (focus or blur), assume that the surrogate already propagated from triggering the
+ // native event and prevent that from happening again here.
+ // This technically gets the ordering wrong w.r.t. to `.trigger()` (in which the
+ // bubbling surrogate propagates *after* the non-bubbling base), but that seems
+ // less bad than duplication.
+ } else if ( ( jQuery.event.special[ type ] || {} ).delegateType ) {
+ event.stopPropagation();
+ }
+
+ // If this is a native event triggered above, everything is now in order
+ // Fire an inner synthetic event with the original arguments
+ } else if ( saved.length ) {
+
+ // ...and capture the result
+ dataPriv.set( this, type, {
+ value: jQuery.event.trigger(
+
+ // Support: IE <=9 - 11+
+ // Extend with the prototype to reset the above stopImmediatePropagation()
+ jQuery.extend( saved[ 0 ], jQuery.Event.prototype ),
+ saved.slice( 1 ),
+ this
+ )
+ } );
+
+ // Abort handling of the native event
+ event.stopImmediatePropagation();
+ }
+ }
+ } );
+}
+
+jQuery.removeEvent = function( elem, type, handle ) {
+
+ // This "if" is needed for plain objects
+ if ( elem.removeEventListener ) {
+ elem.removeEventListener( type, handle );
+ }
+};
+
+jQuery.Event = function( src, props ) {
+
+ // Allow instantiation without the 'new' keyword
+ if ( !( this instanceof jQuery.Event ) ) {
+ return new jQuery.Event( src, props );
+ }
+
+ // Event object
+ if ( src && src.type ) {
+ this.originalEvent = src;
+ this.type = src.type;
+
+ // Events bubbling up the document may have been marked as prevented
+ // by a handler lower down the tree; reflect the correct value.
+ this.isDefaultPrevented = src.defaultPrevented ||
+ src.defaultPrevented === undefined &&
+
+ // Support: Android <=2.3 only
+ src.returnValue === false ?
+ returnTrue :
+ returnFalse;
+
+ // Create target properties
+ // Support: Safari <=6 - 7 only
+ // Target should not be a text node (trac-504, trac-13143)
+ this.target = ( src.target && src.target.nodeType === 3 ) ?
+ src.target.parentNode :
+ src.target;
+
+ this.currentTarget = src.currentTarget;
+ this.relatedTarget = src.relatedTarget;
+
+ // Event type
+ } else {
+ this.type = src;
+ }
+
+ // Put explicitly provided properties onto the event object
+ if ( props ) {
+ jQuery.extend( this, props );
+ }
+
+ // Create a timestamp if incoming event doesn't have one
+ this.timeStamp = src && src.timeStamp || Date.now();
+
+ // Mark it as fixed
+ this[ jQuery.expando ] = true;
+};
+
+// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding
+// https://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
+jQuery.Event.prototype = {
+ constructor: jQuery.Event,
+ isDefaultPrevented: returnFalse,
+ isPropagationStopped: returnFalse,
+ isImmediatePropagationStopped: returnFalse,
+ isSimulated: false,
+
+ preventDefault: function() {
+ var e = this.originalEvent;
+
+ this.isDefaultPrevented = returnTrue;
+
+ if ( e && !this.isSimulated ) {
+ e.preventDefault();
+ }
+ },
+ stopPropagation: function() {
+ var e = this.originalEvent;
+
+ this.isPropagationStopped = returnTrue;
+
+ if ( e && !this.isSimulated ) {
+ e.stopPropagation();
+ }
+ },
+ stopImmediatePropagation: function() {
+ var e = this.originalEvent;
+
+ this.isImmediatePropagationStopped = returnTrue;
+
+ if ( e && !this.isSimulated ) {
+ e.stopImmediatePropagation();
+ }
+
+ this.stopPropagation();
+ }
+};
+
+// Includes all common event props including KeyEvent and MouseEvent specific props
+jQuery.each( {
+ altKey: true,
+ bubbles: true,
+ cancelable: true,
+ changedTouches: true,
+ ctrlKey: true,
+ detail: true,
+ eventPhase: true,
+ metaKey: true,
+ pageX: true,
+ pageY: true,
+ shiftKey: true,
+ view: true,
+ "char": true,
+ code: true,
+ charCode: true,
+ key: true,
+ keyCode: true,
+ button: true,
+ buttons: true,
+ clientX: true,
+ clientY: true,
+ offsetX: true,
+ offsetY: true,
+ pointerId: true,
+ pointerType: true,
+ screenX: true,
+ screenY: true,
+ targetTouches: true,
+ toElement: true,
+ touches: true,
+ which: true
+}, jQuery.event.addProp );
+
+jQuery.each( { focus: "focusin", blur: "focusout" }, function( type, delegateType ) {
+ jQuery.event.special[ type ] = {
+
+ // Utilize native event if possible so blur/focus sequence is correct
+ setup: function() {
+
+ // Claim the first handler
+ // dataPriv.set( this, "focus", ... )
+ // dataPriv.set( this, "blur", ... )
+ leverageNative( this, type, expectSync );
+
+ // Return false to allow normal processing in the caller
+ return false;
+ },
+ trigger: function() {
+
+ // Force setup before trigger
+ leverageNative( this, type );
+
+ // Return non-false to allow normal event-path propagation
+ return true;
+ },
+
+ // Suppress native focus or blur if we're currently inside
+ // a leveraged native-event stack
+ _default: function( event ) {
+ return dataPriv.get( event.target, type );
+ },
+
+ delegateType: delegateType
+ };
+} );
+
+// Create mouseenter/leave events using mouseover/out and event-time checks
+// so that event delegation works in jQuery.
+// Do the same for pointerenter/pointerleave and pointerover/pointerout
+//
+// Support: Safari 7 only
+// Safari sends mouseenter too often; see:
+// https://bugs.chromium.org/p/chromium/issues/detail?id=470258
+// for the description of the bug (it existed in older Chrome versions as well).
+jQuery.each( {
+ mouseenter: "mouseover",
+ mouseleave: "mouseout",
+ pointerenter: "pointerover",
+ pointerleave: "pointerout"
+}, function( orig, fix ) {
+ jQuery.event.special[ orig ] = {
+ delegateType: fix,
+ bindType: fix,
+
+ handle: function( event ) {
+ var ret,
+ target = this,
+ related = event.relatedTarget,
+ handleObj = event.handleObj;
+
+ // For mouseenter/leave call the handler if related is outside the target.
+ // NB: No relatedTarget if the mouse left/entered the browser window
+ if ( !related || ( related !== target && !jQuery.contains( target, related ) ) ) {
+ event.type = handleObj.origType;
+ ret = handleObj.handler.apply( this, arguments );
+ event.type = fix;
+ }
+ return ret;
+ }
+ };
+} );
+
+jQuery.fn.extend( {
+
+ on: function( types, selector, data, fn ) {
+ return on( this, types, selector, data, fn );
+ },
+ one: function( types, selector, data, fn ) {
+ return on( this, types, selector, data, fn, 1 );
+ },
+ off: function( types, selector, fn ) {
+ var handleObj, type;
+ if ( types && types.preventDefault && types.handleObj ) {
+
+ // ( event ) dispatched jQuery.Event
+ handleObj = types.handleObj;
+ jQuery( types.delegateTarget ).off(
+ handleObj.namespace ?
+ handleObj.origType + "." + handleObj.namespace :
+ handleObj.origType,
+ handleObj.selector,
+ handleObj.handler
+ );
+ return this;
+ }
+ if ( typeof types === "object" ) {
+
+ // ( types-object [, selector] )
+ for ( type in types ) {
+ this.off( type, selector, types[ type ] );
+ }
+ return this;
+ }
+ if ( selector === false || typeof selector === "function" ) {
+
+ // ( types [, fn] )
+ fn = selector;
+ selector = undefined;
+ }
+ if ( fn === false ) {
+ fn = returnFalse;
+ }
+ return this.each( function() {
+ jQuery.event.remove( this, types, fn, selector );
+ } );
+ }
+} );
+
+
+var
+
+ // Support: IE <=10 - 11, Edge 12 - 13 only
+ // In IE/Edge using regex groups here causes severe slowdowns.
+ // See https://connect.microsoft.com/IE/feedback/details/1736512/
+ rnoInnerhtml = /