Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 56 additions & 17 deletions backend/devices/bitbox02bootloader/handlers/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
"net/http"

"github.com/BitBoxSwiss/bitbox-wallet-app/backend/devices/bitbox02bootloader"
"github.com/BitBoxSwiss/bitbox-wallet-app/util/errp"
"github.com/BitBoxSwiss/bitbox02-api-go/api/bootloader"
"github.com/gorilla/mux"
"github.com/sirupsen/logrus"
Expand All @@ -32,7 +31,7 @@ type Handlers struct {

// NewHandlers creates a new Handlers instance.
func NewHandlers(
handleFunc func(string, func(*http.Request) (interface{}, error)) *mux.Route,
handleFunc func(string, func(*http.Request) interface{}) *mux.Route,
log *logrus.Entry,
) *Handlers {
handlers := &Handlers{log: log.WithField("device", "bitbox02-bootloader")}
Expand Down Expand Up @@ -61,34 +60,74 @@ func (handlers *Handlers) Uninit() {
handlers.device = nil
}

func (handlers *Handlers) getStatusHandler(_ *http.Request) (interface{}, error) {
return handlers.device.Status(), nil
type bootloaderResponse struct {
Success bool `json:"success"`
ErrorMessage string `json:"errorMessage,omitempty"`
}

func (handlers *Handlers) postUpgradeFirmwareHandler(_ *http.Request) (interface{}, error) {
return nil, handlers.device.UpgradeFirmware()
func (handlers *Handlers) errorResponse(err error) bootloaderResponse {
handlers.log.WithError(err).Error("BitBox02 bootloader request failed")
return bootloaderResponse{Success: false, ErrorMessage: err.Error()}
}

func (handlers *Handlers) postRebootHandler(_ *http.Request) (interface{}, error) {
return nil, handlers.device.Reboot()
func (handlers *Handlers) getStatusHandler(_ *http.Request) interface{} {
return handlers.device.Status()
}

func (handlers *Handlers) getShowFirmwareHashEnabledHandler(_ *http.Request) (interface{}, error) {
return handlers.device.ShowFirmwareHashEnabled()
func (handlers *Handlers) postUpgradeFirmwareHandler(_ *http.Request) interface{} {
if err := handlers.device.UpgradeFirmware(); err != nil {
return handlers.errorResponse(err)
}
return bootloaderResponse{Success: true}
}

func (handlers *Handlers) postRebootHandler(_ *http.Request) interface{} {
if err := handlers.device.Reboot(); err != nil {
return handlers.errorResponse(err)
}
return bootloaderResponse{Success: true}
}

func (handlers *Handlers) getShowFirmwareHashEnabledHandler(_ *http.Request) interface{} {
type response struct {
Success bool `json:"success"`
Enabled bool `json:"enabled"`
}

enabled, err := handlers.device.ShowFirmwareHashEnabled()
if err != nil {
return handlers.errorResponse(err)
}
return response{Success: true, Enabled: enabled}
}

func (handlers *Handlers) postSetShowFirmwareHashEnabledHandler(r *http.Request) (interface{}, error) {
func (handlers *Handlers) postSetShowFirmwareHashEnabledHandler(r *http.Request) interface{} {
var enabled bool
if err := json.NewDecoder(r.Body).Decode(&enabled); err != nil {
return nil, errp.WithStack(err)
return bootloaderResponse{Success: false, ErrorMessage: err.Error()}
}
return nil, handlers.device.SetShowFirmwareHashEnabled(enabled)
if err := handlers.device.SetShowFirmwareHashEnabled(enabled); err != nil {
return handlers.errorResponse(err)
}
return bootloaderResponse{Success: true}
}

func (handlers *Handlers) getInfoHandler(_ *http.Request) (interface{}, error) {
return handlers.device.Info()
func (handlers *Handlers) getInfoHandler(_ *http.Request) interface{} {
type response struct {
Success bool `json:"success"`
Info *bitbox02bootloader.Info `json:"info,omitempty"`
}

info, err := handlers.device.Info()
if err != nil {
return handlers.errorResponse(err)
}
return response{Success: true, Info: info}
}

func (handlers *Handlers) postScreenRotateHandler(_ *http.Request) (interface{}, error) {
return nil, handlers.device.ScreenRotate()
func (handlers *Handlers) postScreenRotateHandler(_ *http.Request) interface{} {
if err := handlers.device.ScreenRotate(); err != nil {
return handlers.errorResponse(err)
}
return bootloaderResponse{Success: true}
}
2 changes: 1 addition & 1 deletion backend/handlers/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,7 @@ func NewHandlers(
getBitBox02BootloaderHandlers := func(deviceID string) *bitbox02bootloaderHandlers.Handlers {
defer handlersMapLock.Lock()()
if _, ok := bitbox02BootloaderHandlersMap[deviceID]; !ok {
bitbox02BootloaderHandlersMap[deviceID] = bitbox02bootloaderHandlers.NewHandlers(getAPIRouter(
bitbox02BootloaderHandlersMap[deviceID] = bitbox02bootloaderHandlers.NewHandlers(getAPIRouterNoError(
apiRouter.PathPrefix(fmt.Sprintf("/devices/bitbox02-bootloader/%s", deviceID)).Subrouter(),
), log)
}
Expand Down
24 changes: 18 additions & 6 deletions frontends/web/src/api/bitbox02bootloader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import { apiGet, apiPost } from '@/utils/request';
import { subscribeEndpoint, TSubscriptionCallback } from './subscribe';
import type { SuccessResponse } from './response';

export type TStatus = {
upgrading: boolean;
Expand Down Expand Up @@ -42,40 +43,51 @@ type TInfo = {
additionalUpgradeFollows: boolean;
};

type TBootloaderErrorResponse = {
success: false;
errorMessage?: string;
};

export type TBootloaderResponse = SuccessResponse | TBootloaderErrorResponse;

type TInfoResponse = (SuccessResponse & { info: TInfo }) | TBootloaderErrorResponse;

type TShowFirmwareHashResponse = (SuccessResponse & { enabled: boolean }) | TBootloaderErrorResponse;

export const getInfo = (
deviceID: string,
): Promise<TInfo> => {
): Promise<TInfoResponse> => {
return apiGet(`devices/bitbox02-bootloader/${deviceID}/info`);
};

export const upgradeFirmware = (
deviceID: string,
): Promise<void> => {
): Promise<TBootloaderResponse> => {
return apiPost(`devices/bitbox02-bootloader/${deviceID}/upgrade-firmware`);
};

export const reboot = (
deviceID: string,
): Promise<void> => {
): Promise<TBootloaderResponse> => {
return apiPost(`devices/bitbox02-bootloader/${deviceID}/reboot`);
};

export const screenRotate = (
deviceID: string,
): Promise<void> => {
): Promise<TBootloaderResponse> => {
return apiPost(`devices/bitbox02-bootloader/${deviceID}/screen-rotate`);
};

export const getShowFirmwareHash = (deviceID: string) => {
return (): Promise<boolean> => {
return (): Promise<TShowFirmwareHashResponse> => {
return apiGet(`devices/bitbox02-bootloader/${deviceID}/show-firmware-hash-enabled`);
};
};

export const setShowFirmwareHash = (
deviceID: string,
enabled: boolean,
) => {
): Promise<TBootloaderResponse> => {
return apiPost(
`devices/bitbox02-bootloader/${deviceID}/set-firmware-hash-enabled`,
enabled,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// SPDX-License-Identifier: Apache-2.0

import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import * as bitbox02BootloaderAPI from '@/api/bitbox02bootloader';
import { useDarkmode } from '@/hooks/darkmode';
Expand All @@ -23,10 +24,33 @@ export const BitBox02Bootloader = ({ deviceID }: TProps) => {
() => bitbox02BootloaderAPI.getStatus(deviceID),
bitbox02BootloaderAPI.syncStatus(deviceID),
);
const info = useLoad(() => bitbox02BootloaderAPI.getInfo(deviceID));
if (info === undefined) {
const infoResponse = useLoad(() => bitbox02BootloaderAPI.getInfo(deviceID));
const [requestError, setRequestError] = useState<string>();

const runAction = async (action: () => Promise<bitbox02BootloaderAPI.TBootloaderResponse>) => {
const result = await action();
if (!result.success) {
setRequestError(result.errorMessage || t('genericError'));
return;
}
setRequestError(undefined);
};

if (infoResponse === undefined) {
return null;
}
if (!infoResponse.success) {
return (
<View fitContent verticallyCentered width="556px">
<ViewContent>
<Message type="warning">
{infoResponse.errorMessage || t('genericError')}
</Message>
</ViewContent>
</View>
);
}
const { info } = infoResponse;

let contents;
if (status && status.upgrading) {
Expand Down Expand Up @@ -85,14 +109,14 @@ export const BitBox02Bootloader = ({ deviceID }: TProps) => {
{ info.canUpgrade ? (
<Button
primary
onClick={() => bitbox02BootloaderAPI.upgradeFirmware(deviceID)}>
onClick={() => runAction(() => bitbox02BootloaderAPI.upgradeFirmware(deviceID))}>
{t('bootloader.button', { context: (info.erased ? 'install' : '') })}
</Button>
) : null }
{ !info.erased && (
<Button
secondary
onClick={() => bitbox02BootloaderAPI.reboot(deviceID)}>
onClick={() => runAction(() => bitbox02BootloaderAPI.reboot(deviceID))}>
{t('bb02Bootloader.abort', { context: !info.canUpgrade ? 'noUpgrade' : '' })}
</Button>
)}
Expand All @@ -101,7 +125,7 @@ export const BitBox02Bootloader = ({ deviceID }: TProps) => {
{t('bb02Bootloader.orientation')}&nbsp;
<Button
inline
onClick={() => bitbox02BootloaderAPI.screenRotate(deviceID)}
onClick={() => runAction(() => bitbox02BootloaderAPI.screenRotate(deviceID))}
transparent>
{t('bb02Bootloader.flipscreen')}
</Button>
Expand All @@ -113,7 +137,9 @@ export const BitBox02Bootloader = ({ deviceID }: TProps) => {
</summary>
<div>
<br />
<ToggleShowFirmwareHash deviceID={deviceID} />
<ToggleShowFirmwareHash
deviceID={deviceID}
onError={setRequestError} />
</div>
</details>
</div>
Expand All @@ -134,6 +160,11 @@ export const BitBox02Bootloader = ({ deviceID }: TProps) => {
{status.errMsg}
</Message>
)}
{requestError && (
<Message type="warning">
{requestError}
</Message>
)}
{contents}
</ViewContent>
</View>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,36 @@ import { Toggle } from '@/components/toggle/toggle';

type Props = {
deviceID: string;
onError: (message: string | undefined) => void;
};

export const ToggleShowFirmwareHash = ({ deviceID }: Props) => {
export const ToggleShowFirmwareHash = ({ deviceID, onError }: Props) => {
const { t } = useTranslation();
const [enabledState, setEnabledState] = useState<boolean>(false);
const enabledConfig = useLoad(getShowFirmwareHash(deviceID));

useEffect(() => {
if (enabledConfig !== undefined) {
setEnabledState(enabledConfig);
if (enabledConfig === undefined) {
return;
}
}, [enabledConfig]);
if (!enabledConfig.success) {
onError(enabledConfig.errorMessage || t('genericError'));
return;
}
onError(undefined);
setEnabledState(enabledConfig.enabled);
}, [enabledConfig, onError, t]);

const handleToggle = (event: ChangeEvent<HTMLInputElement>) => {
const handleToggle = async (event: ChangeEvent<HTMLInputElement>) => {
const enabled = event.target.checked;
setShowFirmwareHash(deviceID, enabled);
setEnabledState(enabled);
const result = await setShowFirmwareHash(deviceID, enabled);
if (!result.success) {
setEnabledState(!enabled);
onError(result.errorMessage || t('genericError'));
return;
}
onError(undefined);
};

return (
Expand Down