mirror of
https://github.com/transatoshi-mw/grin-web-wallet.git
synced 2025-10-06 15:52:47 +00:00
1278 lines
34 KiB
JavaScript
Executable File
1278 lines
34 KiB
JavaScript
Executable File
// 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;
|