diff --git a/backend/backend.go b/backend/backend.go
index dc12816ca0..aa8bccc0dc 100644
--- a/backend/backend.go
+++ b/backend/backend.go
@@ -405,7 +405,7 @@ func (backend *Backend) OnDeviceUninit(f func(string)) {
// Start starts the background services. It returns a channel of events to handle by the library
// client.
func (backend *Backend) Start() <-chan interface{} {
- go backend.listenHID()
+ usb.NewManager(backend.arguments.MainDirectoryPath(), backend.Register, backend.Deregister).Start()
return backend.events
}
@@ -414,13 +414,9 @@ func (backend *Backend) Events() <-chan interface{} {
return backend.events
}
-// DevicesRegistered returns a slice of device IDs of registered devices.
-func (backend *Backend) DevicesRegistered() []string {
- deviceIDs := []string{}
- for deviceID := range backend.devices {
- deviceIDs = append(deviceIDs, deviceID)
- }
- return deviceIDs
+// DevicesRegistered returns a map of device IDs to device of registered devices.
+func (backend *Backend) DevicesRegistered() map[string]device.Interface {
+ return backend.devices
}
func (backend *Backend) uninitAccounts() {
@@ -516,10 +512,6 @@ func (backend *Backend) Deregister(deviceID string) {
}
}
-func (backend *Backend) listenHID() {
- usb.NewManager(backend.arguments.MainDirectoryPath(), backend.Register, backend.Deregister).ListenHID()
-}
-
// Rates return the latest rates.
func (backend *Backend) Rates() map[string]map[string]float64 {
return backend.ratesUpdater.Last()
diff --git a/backend/devices/device/device.go b/backend/devices/device/device.go
index d4732e5a5f..0b830fd7e3 100644
--- a/backend/devices/device/device.go
+++ b/backend/devices/device/device.go
@@ -36,7 +36,7 @@ const (
type Interface interface {
Init(testing bool)
// ProductName returns the product name of the device in lowercase.
- // ProductName() string
+ ProductName() string
// Identifier returns the hash of the type and the serial number.
Identifier() string
diff --git a/backend/devices/usb/manager.go b/backend/devices/usb/manager.go
index 276f84ca3b..038cfc33cc 100644
--- a/backend/devices/usb/manager.go
+++ b/backend/devices/usb/manager.go
@@ -16,7 +16,6 @@ package usb
import (
"encoding/hex"
- "fmt"
"os"
"regexp"
"time"
@@ -31,23 +30,26 @@ import (
)
const (
- vendorID = 0x03eb
- productID = 0x2402
+ bitboxVendorID = 0x03eb
+ bitboxProductID = 0x2402
)
+func isBitBox(deviceInfo hid.DeviceInfo) bool {
+ return deviceInfo.VendorID == bitboxVendorID && deviceInfo.ProductID == bitboxProductID && (deviceInfo.UsagePage == 0xffff || deviceInfo.Interface == 0)
+}
+
// DeviceInfos returns a slice of all found bitbox devices.
func DeviceInfos() []hid.DeviceInfo {
deviceInfos := []hid.DeviceInfo{}
- for _, deviceInfo := range hid.Enumerate(vendorID, productID) {
- if deviceInfo.Interface != 0 && deviceInfo.UsagePage != 0xffff {
- continue
- }
+ for _, deviceInfo := range hid.Enumerate(0, 0) {
// If Enumerate() is called too quickly after a device is inserted, the HID device input
// report is not yet ready.
if deviceInfo.Serial == "" || deviceInfo.Product == "" {
continue
}
- deviceInfos = append(deviceInfos, deviceInfo)
+ if isBitBox(deviceInfo) {
+ deviceInfos = append(deviceInfos, deviceInfo)
+ }
}
return deviceInfos
}
@@ -78,36 +80,33 @@ func NewManager(
channelConfigDir: channelConfigDir,
onRegister: onRegister,
onUnregister: onUnregister,
- log: logging.Get().WithGroup("manager"),
+
+ log: logging.Get().WithGroup("manager"),
}
}
-func deviceIdentifier(productName string, path string) string {
- return hex.EncodeToString([]byte(fmt.Sprintf("%s%s", productName, path)))
+func deviceIdentifier(deviceInfo hid.DeviceInfo) string {
+ return hex.EncodeToString([]byte(deviceInfo.Path))
}
-func (manager *Manager) register(deviceInfo hid.DeviceInfo) error {
- deviceID := deviceIdentifier(bitbox.ProductName, deviceInfo.Path)
- // Skip if already registered.
- if _, ok := manager.devices[deviceID]; ok {
- return nil
- }
- manager.log.WithField("device-id", deviceID).Info("Registering device")
+func (manager *Manager) makeBitBox(deviceInfo hid.DeviceInfo) (*bitbox.Device, error) {
+ deviceID := deviceIdentifier(deviceInfo)
+ manager.log.WithField("device-id", deviceID).Info("Registering BitBox")
bootloader := deviceInfo.Product == "bootloader" || deviceInfo.Product == "Digital Bitbox bootloader"
match := regexp.MustCompile(`v([0-9]+\.[0-9]+\.[0-9]+)`).FindStringSubmatch(deviceInfo.Serial)
if len(match) != 2 {
manager.log.WithField("serial", deviceInfo.Serial).Error("Serial number is malformed")
- return errp.Newf("Could not find the firmware version in '%s'.", deviceInfo.Serial)
+ return nil, errp.Newf("Could not find the firmware version in '%s'.", deviceInfo.Serial)
}
firmwareVersion, err := semver.NewSemVerFromString(match[1])
if err != nil {
- return errp.WithContext(errp.WithMessage(err, "Failed to read version from serial number"),
+ return nil, errp.WithContext(errp.WithMessage(err, "Failed to read version from serial number"),
errp.Context{"serial": deviceInfo.Serial})
}
hidDevice, err := deviceInfo.Open()
if err != nil {
- return errp.WithMessage(err, "Failed to open device")
+ return nil, errp.WithMessage(err, "Failed to open device")
}
usbWriteReportSize := 64
@@ -128,23 +127,19 @@ func (manager *Manager) register(deviceInfo hid.DeviceInfo) error {
NewCommunication(hidDevice, usbWriteReportSize, usbReadReportSize),
)
if err != nil {
- return errp.WithMessage(err, "Failed to establish communication to device")
- }
- if err := manager.onRegister(device); err != nil {
- return errp.WithMessage(err, "Failed to execute on-register")
+ return nil, errp.WithMessage(err, "Failed to establish communication to device")
}
- manager.devices[deviceID] = device
// Unlock the device automatically if the user set the PIN as an environment variable.
pin := os.Getenv("BITBOX_PIN")
if pin != "" {
if _, _, err := device.Login(pin); err != nil {
- return errp.WithMessage(err, "Failed to unlock the BitBox with the provided PIN.")
+ return nil, errp.WithMessage(err, "Failed to unlock the BitBox with the provided PIN.")
}
manager.log.Info("Successfully unlocked the device with the PIN from the environment.")
}
- return nil
+ return device, nil
}
// checkIfRemoved returns true if a device was plugged in, but is not plugged in anymore.
@@ -154,7 +149,7 @@ func (manager *Manager) checkIfRemoved(deviceID string) bool {
// multiple times.
for i := 0; i < 5; i++ {
for _, deviceInfo := range DeviceInfos() {
- if deviceIdentifier(bitbox.ProductName, deviceInfo.Path) == deviceID {
+ if deviceIdentifier(deviceInfo) == deviceID {
return false
}
}
@@ -163,8 +158,7 @@ func (manager *Manager) checkIfRemoved(deviceID string) bool {
return true
}
-// ListenHID listens for inserted/removed devices forever. Run this in a goroutine.
-func (manager *Manager) ListenHID() {
+func (manager *Manager) listen() {
for {
for deviceID, device := range manager.devices {
// Check if device was removed.
@@ -179,10 +173,32 @@ func (manager *Manager) ListenHID() {
// Check if device was inserted.
deviceInfos := DeviceInfos()
for _, deviceInfo := range deviceInfos {
- if err := manager.register(deviceInfo); err != nil {
- manager.log.WithError(err).Error("Failed to register device")
+ deviceID := deviceIdentifier(deviceInfo)
+ // Skip if already registered.
+ if _, ok := manager.devices[deviceID]; ok {
+ continue
+ }
+ var device device.Interface
+ if isBitBox(deviceInfo) {
+ var err error
+ device, err = manager.makeBitBox(deviceInfo)
+ if err != nil {
+ manager.log.WithError(err).Error("Failed to register bitbox")
+ continue
+ }
+ } else {
+ panic("unrecognized device")
+ }
+ manager.devices[deviceID] = device
+ if err := manager.onRegister(device); err != nil {
+ manager.log.WithError(err).Error("Failed to execute on-register")
}
}
time.Sleep(time.Second)
}
}
+
+// Start listens for inserted/removed devices forever. Run this in a goroutine.
+func (manager *Manager) Start() {
+ go manager.listen()
+}
diff --git a/backend/handlers/handlers.go b/backend/handlers/handlers.go
index 62f254ce40..25fdebd071 100644
--- a/backend/handlers/handlers.go
+++ b/backend/handlers/handlers.go
@@ -58,7 +58,7 @@ type Backend interface {
OnAccountUninit(f func(btc.Interface))
OnDeviceInit(f func(device.Interface))
OnDeviceUninit(f func(deviceID string))
- DevicesRegistered() []string
+ DevicesRegistered() map[string]device.Interface
Start() <-chan interface{}
Keystores() keystore.Keystores
RegisterKeystore(keystore.Keystore)
@@ -286,7 +286,11 @@ func (handlers *Handlers) getAccountsStatusHandler(_ *http.Request) (interface{}
}
func (handlers *Handlers) getDevicesRegisteredHandler(_ *http.Request) (interface{}, error) {
- return handlers.backend.DevicesRegistered(), nil
+ jsonDevices := map[string]string{}
+ for deviceID, device := range handlers.backend.DevicesRegistered() {
+ jsonDevices[deviceID] = device.ProductName()
+ }
+ return jsonDevices, nil
}
func (handlers *Handlers) registerTestKeyStoreHandler(r *http.Request) (interface{}, error) {
diff --git a/frontends/web/src/app.jsx b/frontends/web/src/app.jsx
index 74cd380abb..6b0b903f23 100644
--- a/frontends/web/src/app.jsx
+++ b/frontends/web/src/app.jsx
@@ -19,13 +19,12 @@
import { i18nEditorActive } from './i18n/i18n';
import TranslationHelper from './components/translationhelper/translationhelper';
import { Component, h } from 'preact';
-import { route } from 'preact-router';
import { apiGet } from './utils/request';
import { apiWebsocket } from './utils/websocket';
import { Update } from './components/update/update';
import Sidebar from './components/sidebar/sidebar';
import Container from './components/container/Container';
-import Device from './routes/device/device';
+import { DeviceSwitch } from './routes/device/deviceswitch';
import Account from './routes/account/account';
import Send from './routes/account/send/send';
import Receive from './routes/account/receive/receive';
@@ -38,9 +37,10 @@ import { Confirm } from './components/confirm/Confirm';
export class App extends Component {
state = {
- accounts: [],
+ accounts: null,
accountsInitialized: false,
deviceIDs: [],
+ devices: {},
activeSidebar: false,
}
@@ -95,22 +95,16 @@ export class App extends Component {
}
onDevicesRegisteredChanged = () => {
- apiGet('devices/registered').then(deviceIDs => {
- this.setState({ deviceIDs });
+ apiGet('devices/registered').then(devices => {
+ const deviceIDs = Object.keys(devices);
+ this.setState({ devices, deviceIDs });
});
}
onAccountsStatusChanged = () => {
apiGet('accounts-status').then(status => {
const accountsInitialized = status === 'initialized';
- this.setState({
- accountsInitialized
- });
- if (!accountsInitialized) {
- console.log('app.jsx route /'); // eslint-disable-line no-console
- route('/', true);
- }
-
+ this.setState({ accountsInitialized });
apiGet('accounts').then(accounts => this.setState({ accounts }));
});
}
@@ -121,6 +115,7 @@ export class App extends Component {
render({}, {
accounts,
+ devices,
deviceIDs,
accountsInitialized,
activeSidebar,
@@ -143,8 +138,7 @@ export class App extends Component {
accounts={accounts} />
+ deviceIDs={deviceIDs} />
@@ -162,13 +156,15 @@ export class App extends Component {
// showCreate={true} // Does not exist!
// deviceIDs={deviceIDs} // Does not exist!
/>
-
-
+
+ key={devices}
+ deviceID={null}
+ devices={devices} />
diff --git a/frontends/web/src/routes/account/account.jsx b/frontends/web/src/routes/account/account.jsx
index 835cdb6f5d..0cc8e6965f 100644
--- a/frontends/web/src/routes/account/account.jsx
+++ b/frontends/web/src/routes/account/account.jsx
@@ -71,6 +71,10 @@ export default class Account extends Component {
}
componentDidUpdate(prevProps) {
+ if (this.props.code && (!this.props.accounts || this.props.accounts.length === 0)) {
+ console.log('account.jsx route /'); // eslint-disable-line no-console
+ route('/', true);
+ }
if (!this.props.code) {
if (this.props.accounts && this.props.accounts.length) {
console.log('route', `/account/${this.props.accounts[0].code}`); // eslint-disable-line no-console
diff --git a/frontends/web/src/routes/device/device.jsx b/frontends/web/src/routes/device/device.jsx
index 05b1c01642..c44be3202a 100644
--- a/frontends/web/src/routes/device/device.jsx
+++ b/frontends/web/src/routes/device/device.jsx
@@ -15,11 +15,9 @@
*/
import { Component, h } from 'preact';
-import { route } from 'preact-router';
import { translate } from 'react-i18next';
import { apiGet } from '../../utils/request';
import { apiWebsocket } from '../../utils/websocket';
-import Waiting from './waiting';
import Unlock from './unlock';
import Bootloader from './upgrade/bootloader';
import RequireUpgrade from './upgrade/require_upgrade';
@@ -54,7 +52,6 @@ export default class Device extends Component {
firmwareVersion: null,
deviceRegistered: false,
deviceStatus: null,
- accountsStatus: null,
goal: null,
success: null,
}
@@ -62,11 +59,7 @@ export default class Device extends Component {
componentDidMount() {
this.onDevicesRegisteredChanged();
this.onDeviceStatusChanged();
- this.onAccountsStatusChanged();
this.unsubscribe = apiWebsocket(({ type, data, deviceID }) => {
- if (type === 'backend' && data === 'accountsStatusChanged') {
- this.onAccountsStatusChanged();
- }
if (type === 'devices' && data === 'registeredChanged') {
this.onDevicesRegisteredChanged();
}
@@ -88,26 +81,11 @@ export default class Device extends Component {
}
}
- onAccountsStatusChanged = () => {
- apiGet('accounts-status').then(status => {
- if (status === 'initialized' && this.props.default) {
- console.log('device.jsx route to /account'); // eslint-disable-line no-console
- route('/account', true);
- }
- this.setState({
- accountsStatus: status === 'initialized'
- });
- });
- }
-
onDevicesRegisteredChanged = () => {
- apiGet('devices/registered').then(deviceIDs => {
+ apiGet('devices/registered').then(devices => {
+ const deviceIDs = Object.keys(devices);
const deviceRegistered = deviceIDs.includes(this.getDeviceID());
- if (this.props.default && deviceIDs.length === 1) {
- console.log('device.jsx route to', '/device/' + deviceIDs[0]); // eslint-disable-line no-console
- route('/device/' + deviceIDs[0], true);
- }
this.setState({
deviceRegistered,
deviceStatus: null
@@ -128,7 +106,7 @@ export default class Device extends Component {
}
getDeviceID() {
- return this.props.deviceID || this.props.deviceIDs[0] || null;
+ return this.props.deviceID || null;
}
handleCreate = () => {
@@ -150,17 +128,12 @@ export default class Device extends Component {
render({
t,
deviceID,
- deviceIDs,
}, {
deviceRegistered,
deviceStatus,
- accountsStatus,
goal,
success,
}) {
- if (!deviceIDs.length && !accountsStatus) {
- return ;
- }
if (!deviceRegistered || !deviceStatus) {
return null;
}
diff --git a/frontends/web/src/routes/device/deviceswitch.tsx b/frontends/web/src/routes/device/deviceswitch.tsx
new file mode 100644
index 0000000000..42866c16dd
--- /dev/null
+++ b/frontends/web/src/routes/device/deviceswitch.tsx
@@ -0,0 +1,53 @@
+/**
+ * Copyright 2018 Shift Devices AG
+ *
+ * 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.
+ */
+
+import { Component, h, RenderableProps } from 'preact';
+import { route } from 'preact-router';
+import Device from './device';
+import Waiting from './waiting';
+
+interface Props {
+ devices: {
+ [deviceID: string]: string,
+ };
+ deviceID: string | null;
+}
+
+class DeviceSwitch extends Component {
+ public componentDidMount() {
+ const deviceIDs = Object.keys(this.props.devices);
+ if (this.props.deviceID !== null && !deviceIDs.includes(this.props.deviceID)) {
+ route('/', true);
+ }
+ if (this.props.deviceID === null && deviceIDs.length > 0) {
+ route(`/device/${deviceIDs[0]}`, true);
+ }
+ }
+
+ public render({ deviceID, devices }: RenderableProps) {
+ if (this.props.default || deviceID === null || !Object.keys(devices).includes(deviceID)) {
+ return ;
+ }
+ switch (devices[deviceID]) {
+ case 'bitbox':
+ return ;
+ default:
+ return ;
+ }
+ }
+}
+
+export { DeviceSwitch };
diff --git a/frontends/web/src/routes/device/waiting.jsx b/frontends/web/src/routes/device/waiting.jsx
index 8142c727dd..5161edc276 100644
--- a/frontends/web/src/routes/device/waiting.jsx
+++ b/frontends/web/src/routes/device/waiting.jsx
@@ -40,7 +40,6 @@ export default class Waiting extends Component {
}
}
-
render({
t,
}, {