Task/remove vegawallet service (#926)
* feat: improve error handling * chore: lint * fix: cypress test incorrect assertion
This commit is contained in:
parent
459defddd2
commit
85d838b9a6
@ -29,7 +29,7 @@ describe('vega wallet', () => {
|
||||
cy.getByTestId(form).find('#wallet').click().type('invalid name');
|
||||
cy.getByTestId(form).find('#passphrase').click().type('invalid password');
|
||||
cy.getByTestId('rest-connector-form').find('button[type=submit]').click();
|
||||
cy.getByTestId('form-error').should('have.text', 'Authentication failed');
|
||||
cy.getByTestId('form-error').should('have.text', 'Invalid credentials');
|
||||
});
|
||||
|
||||
it('doesnt connect with invalid fields', () => {
|
||||
|
@ -146,11 +146,12 @@ it('Successful connection using custom url', async () => {
|
||||
});
|
||||
|
||||
it('Unsuccessful connection using rest auth form', async () => {
|
||||
const errMessage = 'Error message';
|
||||
// Error from service
|
||||
let spy = jest
|
||||
.spyOn(defaultProps.connectors['rest'] as RestConnector, 'authenticate')
|
||||
.mockImplementation(() =>
|
||||
Promise.resolve({ success: false, error: 'Error message' })
|
||||
Promise.resolve({ success: false, error: errMessage })
|
||||
);
|
||||
|
||||
render(generateJSX({ dialogOpen: true }));
|
||||
@ -167,9 +168,7 @@ it('Unsuccessful connection using rest auth form', async () => {
|
||||
|
||||
expect(spy).toHaveBeenCalledWith(DEFAULT_URL, fields);
|
||||
|
||||
expect(screen.getByTestId('form-error')).toHaveTextContent(
|
||||
'Something went wrong'
|
||||
);
|
||||
expect(screen.getByTestId('form-error')).toHaveTextContent(errMessage);
|
||||
expect(defaultProps.setDialogOpen).not.toHaveBeenCalled();
|
||||
|
||||
// Fetch failed due to wallet not running
|
||||
|
@ -1,7 +1,8 @@
|
||||
import { LocalStorage } from '@vegaprotocol/react-helpers';
|
||||
import * as Sentry from '@sentry/react';
|
||||
import { LocalStorage, t } from '@vegaprotocol/react-helpers';
|
||||
import { WALLET_CONFIG } from '../storage-keys';
|
||||
import type { VegaConnector } from './vega-connector';
|
||||
import type { TransactionSubmission } from '../wallet-types';
|
||||
import type { TransactionError, TransactionSubmission } from '../wallet-types';
|
||||
import { z } from 'zod';
|
||||
|
||||
// Perhaps there should be a default ConnectorConfig that others can extend off. Do all connectors
|
||||
@ -12,7 +13,11 @@ interface RestConnectorConfig {
|
||||
url: string | null;
|
||||
}
|
||||
|
||||
type Endpoint = 'auth/token' | 'command/sync' | 'keys';
|
||||
enum Endpoints {
|
||||
Auth = 'auth/token',
|
||||
Command = 'command/sync',
|
||||
Keys = 'keys',
|
||||
}
|
||||
|
||||
export const AuthTokenSchema = z.object({
|
||||
token: z.string(),
|
||||
@ -87,11 +92,19 @@ export class RestConnector implements VegaConnector {
|
||||
try {
|
||||
this.url = url;
|
||||
|
||||
const res = await this.request('auth/token', {
|
||||
const res = await this.request(Endpoints.Auth, {
|
||||
method: 'post',
|
||||
body: JSON.stringify(params),
|
||||
});
|
||||
|
||||
if (res.status === 403) {
|
||||
return { success: false, error: t('Invalid credentials') };
|
||||
}
|
||||
|
||||
if (res.error) {
|
||||
return { success: false, error: res.error };
|
||||
}
|
||||
|
||||
const data = AuthTokenSchema.parse(res.data);
|
||||
|
||||
// Store the token, and other things for later
|
||||
@ -104,19 +117,23 @@ export class RestConnector implements VegaConnector {
|
||||
|
||||
return { success: true, error: null };
|
||||
} catch (err) {
|
||||
return { success: false, error: err };
|
||||
return { success: false, error: 'Authentication failed' };
|
||||
}
|
||||
}
|
||||
|
||||
async connect() {
|
||||
try {
|
||||
const res = await this.request('keys', {
|
||||
const res = await this.request(Endpoints.Keys, {
|
||||
method: 'get',
|
||||
headers: {
|
||||
authorization: `Bearer ${this.token}`,
|
||||
},
|
||||
});
|
||||
|
||||
if (res.error) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const data = GetKeysSchema.parse(res.data);
|
||||
|
||||
return data.keys;
|
||||
@ -129,14 +146,14 @@ export class RestConnector implements VegaConnector {
|
||||
|
||||
async disconnect() {
|
||||
try {
|
||||
await this.request('auth/token', {
|
||||
await this.request(Endpoints.Auth, {
|
||||
method: 'delete',
|
||||
headers: {
|
||||
authorization: `Bearer ${this.token}`,
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
Sentry.captureException(err);
|
||||
} finally {
|
||||
// Always clear config, if authTokenDelete fails the user still tried to
|
||||
// connect so clear the config (and containing token) from storage
|
||||
@ -146,7 +163,7 @@ export class RestConnector implements VegaConnector {
|
||||
|
||||
async sendTx(body: TransactionSubmission) {
|
||||
try {
|
||||
const res = await this.request('command/sync', {
|
||||
const res = await this.request(Endpoints.Command, {
|
||||
method: 'post',
|
||||
body: JSON.stringify(body),
|
||||
headers: {
|
||||
@ -154,18 +171,23 @@ export class RestConnector implements VegaConnector {
|
||||
},
|
||||
});
|
||||
|
||||
if (res.status === 401) {
|
||||
// User rejected
|
||||
if (res.status === 401) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (res.error) {
|
||||
return {
|
||||
error: res.error,
|
||||
};
|
||||
}
|
||||
|
||||
const data = TransactionResponseSchema.parse(res.data);
|
||||
|
||||
return data;
|
||||
} catch (err) {
|
||||
return {
|
||||
error: 'Failed to fetch',
|
||||
};
|
||||
Sentry.captureException(err);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@ -190,7 +212,24 @@ export class RestConnector implements VegaConnector {
|
||||
LocalStorage.removeItem(this.configKey);
|
||||
}
|
||||
|
||||
private async request(endpoint: Endpoint, options: RequestInit) {
|
||||
/** Parse more complex error object into a single string */
|
||||
private parseError(err: TransactionError): string {
|
||||
if ('error' in err) {
|
||||
return err.error;
|
||||
}
|
||||
|
||||
if ('errors' in err) {
|
||||
return err.errors['*'].join(', ');
|
||||
}
|
||||
|
||||
return t("Something wen't wrong");
|
||||
}
|
||||
|
||||
private async request(
|
||||
endpoint: Endpoints,
|
||||
options: RequestInit
|
||||
): Promise<{ status?: number; data?: unknown; error?: string }> {
|
||||
try {
|
||||
const fetchResult = await fetch(`${this.url}/${endpoint}`, {
|
||||
...options,
|
||||
headers: {
|
||||
@ -199,6 +238,14 @@ export class RestConnector implements VegaConnector {
|
||||
},
|
||||
});
|
||||
|
||||
if (!fetchResult.ok) {
|
||||
const errorData = await fetchResult.json();
|
||||
return {
|
||||
status: fetchResult.status,
|
||||
error: this.parseError(errorData),
|
||||
};
|
||||
}
|
||||
|
||||
// auth/token delete doesnt return json
|
||||
if (endpoint === 'auth/token' && options.method === 'delete') {
|
||||
const textResult = await fetchResult.text();
|
||||
@ -213,5 +260,10 @@ export class RestConnector implements VegaConnector {
|
||||
data: jsonResult,
|
||||
};
|
||||
}
|
||||
} catch (err) {
|
||||
return {
|
||||
error: 'Failed to fetch',
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,14 +4,6 @@ import type {
|
||||
TransactionResponse,
|
||||
} from '../wallet-types';
|
||||
|
||||
type ErrorResponse =
|
||||
| {
|
||||
error: string;
|
||||
}
|
||||
| {
|
||||
errors: object;
|
||||
};
|
||||
|
||||
export interface VegaConnector {
|
||||
/** Description of how to use this connector */
|
||||
description: string;
|
||||
@ -25,5 +17,5 @@ export interface VegaConnector {
|
||||
/** Send a TX to the network. Only support order submission for now */
|
||||
sendTx: (
|
||||
body: TransactionSubmission
|
||||
) => Promise<TransactionResponse | ErrorResponse | null>;
|
||||
) => Promise<TransactionResponse | { error: string } | null>;
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import type { TransactionError, VegaKey } from './wallet-types';
|
||||
import type { VegaKey } from './wallet-types';
|
||||
import { createContext } from 'react';
|
||||
import type { VegaConnector } from './connectors';
|
||||
import type {
|
||||
@ -32,7 +32,7 @@ export interface VegaWalletContextShape {
|
||||
/** Send a transaction to the network, only order submissions for now */
|
||||
sendTx: (
|
||||
tx: TransactionSubmission
|
||||
) => Promise<TransactionResponse | TransactionError> | null;
|
||||
) => Promise<TransactionResponse | { error: string }> | null;
|
||||
}
|
||||
|
||||
export const VegaWalletContext = createContext<
|
||||
|
@ -33,6 +33,7 @@ export function RestConnectorForm({
|
||||
});
|
||||
|
||||
async function onSubmit(fields: FormFields) {
|
||||
const authFailedMessage = t('Authentication failed');
|
||||
try {
|
||||
setError('');
|
||||
const res = await connector.authenticate(fields.url, {
|
||||
@ -43,13 +44,13 @@ export function RestConnectorForm({
|
||||
if (res.success) {
|
||||
onAuthenticate();
|
||||
} else {
|
||||
throw res.error;
|
||||
setError(res.error || authFailedMessage);
|
||||
}
|
||||
} catch (err) {
|
||||
if (err instanceof TypeError) {
|
||||
setError(t(`Wallet not running at ${fields.url}`));
|
||||
} else if (err instanceof Error) {
|
||||
setError(t('Authentication failed'));
|
||||
setError(authFailedMessage);
|
||||
} else {
|
||||
setError(t('Something went wrong'));
|
||||
}
|
||||
|
@ -53,14 +53,12 @@ it('Handles a single error', async () => {
|
||||
result.current.send({} as OrderSubmissionBody);
|
||||
});
|
||||
expect(result.current.transaction.status).toEqual(VegaTxStatus.Error);
|
||||
expect(result.current.transaction.error).toEqual({ error: errorMessage });
|
||||
expect(result.current.transaction.error).toEqual(errorMessage);
|
||||
});
|
||||
|
||||
it('Handles multiple errors', async () => {
|
||||
const errorObj = {
|
||||
errors: {
|
||||
something: 'Went wrong!',
|
||||
},
|
||||
error: 'Went wrong!',
|
||||
};
|
||||
const mockSendTx = jest.fn().mockReturnValue(Promise.resolve(errorObj));
|
||||
const { result } = setup({ sendTx: mockSendTx });
|
||||
@ -68,7 +66,7 @@ it('Handles multiple errors', async () => {
|
||||
result.current.send({} as OrderSubmissionBody);
|
||||
});
|
||||
expect(result.current.transaction.status).toEqual(VegaTxStatus.Error);
|
||||
expect(result.current.transaction.error).toEqual(errorObj);
|
||||
expect(result.current.transaction.error).toEqual(errorObj.error);
|
||||
});
|
||||
|
||||
it('Returns the signature if successful', async () => {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import type { ReactNode } from 'react';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import type { TransactionError, TransactionSubmission } from './wallet-types';
|
||||
import type { TransactionSubmission } from './wallet-types';
|
||||
import { useVegaWallet } from './use-vega-wallet';
|
||||
import { VegaTransactionDialog } from './vega-transaction-dialog';
|
||||
import type { Intent } from '@vegaprotocol/ui-toolkit';
|
||||
@ -22,7 +22,7 @@ export enum VegaTxStatus {
|
||||
|
||||
export interface VegaTxState {
|
||||
status: VegaTxStatus;
|
||||
error: TransactionError | null;
|
||||
error: string | null;
|
||||
txHash: string | null;
|
||||
signature: string | null;
|
||||
dialogOpen: boolean;
|
||||
@ -47,13 +47,6 @@ export const useVegaTransaction = () => {
|
||||
}));
|
||||
}, []);
|
||||
|
||||
const handleError = useCallback(
|
||||
(error: TransactionError) => {
|
||||
setTransaction({ error, status: VegaTxStatus.Error });
|
||||
},
|
||||
[setTransaction]
|
||||
);
|
||||
|
||||
const reset = useCallback(() => {
|
||||
setTransaction(initialState);
|
||||
}, [setTransaction]);
|
||||
@ -81,7 +74,7 @@ export const useVegaTransaction = () => {
|
||||
}
|
||||
|
||||
if (isError(res)) {
|
||||
handleError(res);
|
||||
setTransaction({ error: res.error, status: VegaTxStatus.Error });
|
||||
return;
|
||||
}
|
||||
|
||||
@ -99,7 +92,7 @@ export const useVegaTransaction = () => {
|
||||
|
||||
return null;
|
||||
},
|
||||
[sendTx, handleError, setTransaction, reset]
|
||||
[sendTx, setTransaction, reset]
|
||||
);
|
||||
|
||||
const TransactionDialog = useMemo(() => {
|
||||
@ -125,12 +118,8 @@ export const useVegaTransaction = () => {
|
||||
};
|
||||
};
|
||||
|
||||
const isError = (error: unknown): error is TransactionError => {
|
||||
if (
|
||||
error !== null &&
|
||||
typeof error === 'object' &&
|
||||
('error' in error || 'errors' in error)
|
||||
) {
|
||||
const isError = (error: unknown): error is { error: string } => {
|
||||
if (error !== null && typeof error === 'object' && 'error' in error) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
@ -57,7 +57,7 @@ describe('VegaTransactionDialog', () => {
|
||||
{...props}
|
||||
transaction={{
|
||||
...props.transaction,
|
||||
error: { error: 'rejected' },
|
||||
error: 'rejected',
|
||||
status: VegaTxStatus.Error,
|
||||
}}
|
||||
/>
|
||||
|
@ -70,18 +70,11 @@ export const VegaDialog = ({ transaction }: VegaDialogProps) => {
|
||||
}
|
||||
|
||||
if (transaction.status === VegaTxStatus.Error) {
|
||||
let content = null;
|
||||
|
||||
if (transaction.error) {
|
||||
if ('errors' in transaction.error) {
|
||||
content = transaction.error.errors['*'].map((e) => <p>{e}</p>);
|
||||
} else if ('error' in transaction.error) {
|
||||
content = <p>{transaction.error.error}</p>;
|
||||
} else {
|
||||
content = <p>{t('Something went wrong')}</p>;
|
||||
}
|
||||
}
|
||||
return <div data-testid={transaction.status}>{content}</div>;
|
||||
return (
|
||||
<div data-testid={transaction.status}>
|
||||
<p>{transaction.error}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (transaction.status === VegaTxStatus.Pending) {
|
||||
|
@ -69,18 +69,6 @@ const getProps = (
|
||||
ethTx: EthTxState,
|
||||
ethUrl: string
|
||||
) => {
|
||||
const renderVegaTxError = () => {
|
||||
if (vegaTx.error) {
|
||||
if ('errors' in vegaTx.error) {
|
||||
return vegaTx.error.errors['*'].map((e) => <p>{e}</p>);
|
||||
} else if ('error' in vegaTx.error) {
|
||||
return <p>{vegaTx.error.error}</p>;
|
||||
} else {
|
||||
return <p>{t('Something went wrong')}</p>;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
const vegaTxPropsMap: Record<VegaTxStatus, DialogProps> = {
|
||||
[VegaTxStatus.Default]: {
|
||||
title: '',
|
||||
@ -92,7 +80,11 @@ const getProps = (
|
||||
title: t('Withdrawal transaction failed'),
|
||||
icon: <Icon name="warning-sign" size={20} />,
|
||||
intent: Intent.Danger,
|
||||
children: <Step>{renderVegaTxError()}</Step>,
|
||||
children: (
|
||||
<Step>
|
||||
<p>{vegaTx.error}</p>
|
||||
</Step>
|
||||
),
|
||||
},
|
||||
[VegaTxStatus.Requested]: {
|
||||
title: t('Confirm withdrawal'),
|
||||
|
Loading…
Reference in New Issue
Block a user