Skip to content

Commit

Permalink
[ID-2222] chore: Add tracking errors for IMX workflows (#2136)
Browse files Browse the repository at this point in the history
  • Loading branch information
matthewmuscat committed Sep 4, 2024
1 parent 2c9ef6a commit 8ab5b97
Show file tree
Hide file tree
Showing 5 changed files with 148 additions and 95 deletions.
10 changes: 1 addition & 9 deletions packages/passport/sdk/src/errors/passportError.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { isAxiosError } from 'axios';
import { imx } from '@imtbl/generated-clients';
import { trackError } from '@imtbl/metrics';

export enum PassportErrorType {
AUTHENTICATION_ERROR = 'AUTHENTICATION_ERROR',
Expand Down Expand Up @@ -41,7 +40,6 @@ export class PassportError extends Error {
export const withPassportError = async <T>(
fn: () => Promise<T>,
customErrorType: PassportErrorType,
customEventName?: string,
): Promise<T> => {
try {
return await fn();
Expand All @@ -54,12 +52,6 @@ export const withPassportError = async <T>(
errorMessage = (error as Error).message;
}

const passportError = new PassportError(errorMessage, customErrorType);

if (customEventName) {
trackError('passport', customEventName, passportError);
}

throw passportError;
throw new PassportError(errorMessage, customErrorType);
}
};
80 changes: 62 additions & 18 deletions packages/passport/sdk/src/starkEx/passportImxProvider.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
UnsignedOrderRequest,
UnsignedTransferRequest,
} from '@imtbl/x-client';
import { trackError, trackFlow } from '@imtbl/metrics';
import registerPassportStarkEx from './workflows/registration';
import { mockUser, mockUserImx } from '../test/mocks';
import { PassportError, PassportErrorType } from '../errors/passportError';
Expand All @@ -32,6 +33,7 @@ jest.mock('./workflows/registration');
jest.mock('./getStarkSigner');
jest.mock('@imtbl/generated-clients');
jest.mock('@imtbl/x-client');
jest.mock('@imtbl/metrics');

describe('PassportImxProvider', () => {
afterEach(jest.resetAllMocks);
Expand Down Expand Up @@ -83,6 +85,11 @@ describe('PassportImxProvider', () => {
passportEventEmitter = new TypedEventEmitter<PassportEventMap>();
mockAuthManager.getUser.mockResolvedValue(mockUserImx);

// Metrics
(trackFlow as unknown as jest.Mock).mockImplementation(() => ({
addEvent: jest.fn(),
}));

// Signers
magicAdapterMock.login.mockResolvedValue({ getSigner: getSignerMock });
(Web3Provider as unknown as jest.Mock).mockReturnValue({ getSigner: getSignerMock });
Expand Down Expand Up @@ -125,6 +132,11 @@ describe('PassportImxProvider', () => {
magicAdapterMock.login.mockResolvedValue({});
(getStarkSigner as jest.Mock).mockRejectedValue(new Error('error'));

// Metrics
(trackFlow as unknown as jest.Mock).mockImplementation(() => ({
addEvent: jest.fn(),
}));

const pp = new PassportImxProvider({
authManager: mockAuthManager as unknown as AuthManager,
magicAdapter: magicAdapterMock as unknown as MagicAdapter,
Expand Down Expand Up @@ -359,15 +371,15 @@ describe('PassportImxProvider', () => {
});

describe.each([
['transfer' as const, {} as UnsignedTransferRequest],
['createOrder' as const, {} as UnsignedOrderRequest],
['cancelOrder' as const, {} as imx.GetSignableCancelOrderRequest],
['createTrade' as const, {} as imx.GetSignableTradeRequest],
['batchNftTransfer' as const, [] as NftTransferDetails[]],
['exchangeTransfer' as const, {} as UnsignedExchangeTransferRequest],
['getAddress' as const, {} as any],
['isRegisteredOffchain' as const, {} as any],
])('when the user has been logged out - %s', (methodName, args) => {
['transfer' as const, 'imxTransfer', {} as UnsignedTransferRequest],
['createOrder' as const, 'imxCreateOrder', {} as UnsignedOrderRequest],
['cancelOrder' as const, 'imxCancelOrder', {} as imx.GetSignableCancelOrderRequest],
['createTrade' as const, 'imxCreateTrade', {} as imx.GetSignableTradeRequest],
['batchNftTransfer' as const, 'imxBatchNftTransfer', [] as NftTransferDetails[]],
['exchangeTransfer' as const, 'imxExchangeTransfer', {} as UnsignedExchangeTransferRequest],
['getAddress' as const, 'imxGetAddress', {} as any],
['isRegisteredOffchain' as const, 'imxIsRegisteredOffchain', {} as any],
])('when the user has been logged out - %s', (methodName, eventName, args) => {
beforeEach(() => {
passportEventEmitter.emit(PassportEvents.LOGGED_OUT);
});
Expand All @@ -382,18 +394,34 @@ describe('PassportImxProvider', () => {
),
);
});

it(`should track metrics when error thrown for ${methodName}`, async () => {
try {
await passportImxProvider[methodName!](args);
} catch (error) {
expect(trackFlow).toHaveBeenCalledWith(
'passport',
eventName,
);
expect(trackError).toHaveBeenCalledWith(
'passport',
eventName,
error,
);
}
});
});

describe.each([
['transfer' as const, {} as UnsignedTransferRequest],
['createOrder' as const, {} as UnsignedOrderRequest],
['cancelOrder' as const, {} as imx.GetSignableCancelOrderRequest],
['createTrade' as const, {} as imx.GetSignableTradeRequest],
['batchNftTransfer' as const, [] as NftTransferDetails[]],
['exchangeTransfer' as const, {} as UnsignedExchangeTransferRequest],
['getAddress' as const, {} as any],
['isRegisteredOffchain' as const, {} as any],
])('when the user\'s access token is expired and cannot be retrieved', (methodName, args) => {
['transfer' as const, 'imxTransfer', {} as UnsignedTransferRequest],
['createOrder' as const, 'imxCreateOrder', {} as UnsignedOrderRequest],
['cancelOrder' as const, 'imxCancelOrder', {} as imx.GetSignableCancelOrderRequest],
['createTrade' as const, 'imxCreateTrade', {} as imx.GetSignableTradeRequest],
['batchNftTransfer' as const, 'imxBatchNftTransfer', [] as NftTransferDetails[]],
['exchangeTransfer' as const, 'imxExchangeTransfer', {} as UnsignedExchangeTransferRequest],
['getAddress' as const, 'imxGetAddress', {} as any],
['isRegisteredOffchain' as const, 'imxIsRegisteredOffchain', {} as any],
])('when the user\'s access token is expired and cannot be retrieved', (methodName, eventName, args) => {
beforeEach(() => {
mockAuthManager.getUser.mockResolvedValue(null);
});
Expand All @@ -408,5 +436,21 @@ describe('PassportImxProvider', () => {
),
);
});

it(`should track metrics when error thrown for ${methodName}`, async () => {
try {
await passportImxProvider[methodName!](args);
} catch (error) {
expect(trackFlow).toHaveBeenCalledWith(
'passport',
eventName,
);
expect(trackError).toHaveBeenCalledWith(
'passport',
eventName,
error,
);
}
});
});
});
96 changes: 55 additions & 41 deletions packages/passport/sdk/src/starkEx/passportImxProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {
import registerOffchain from './workflows/registerOffchain';
import MagicAdapter from '../magicAdapter';
import { getStarkSigner } from './getStarkSigner';
import { withMetricsAsync } from '../utils/metrics';

export interface PassportImxProviderOptions {
authManager: AuthManager;
Expand Down Expand Up @@ -173,8 +174,8 @@ export class PassportImxProvider implements IMXProvider {
}

async transfer(request: UnsignedTransferRequest): Promise<imx.CreateTransferResponseV1> {
return (
this.guardianClient.withDefaultConfirmationScreenTask(async () => {
return withMetricsAsync(() => this.guardianClient.withDefaultConfirmationScreenTask(
async () => {
const { user, starkSigner } = await this.#getRegisteredImxUserAndSigners();

return transfer({
Expand All @@ -184,29 +185,38 @@ export class PassportImxProvider implements IMXProvider {
transfersApi: this.immutableXClient.transfersApi,
guardianClient: this.guardianClient,
});
})()
);
},
)(), 'imxTransfer');
}

async registerOffchain(): Promise<imx.RegisterUserResponse> {
const [user, signers] = await Promise.all([
this.#getAuthenticatedUser(),
this.#getSigners(),
]);

return await registerOffchain(
signers.ethSigner,
signers.starkSigner,
user,
this.authManager,
this.imxApiClients,
return withMetricsAsync(
async () => {
const [user, signers] = await Promise.all([
this.#getAuthenticatedUser(),
this.#getSigners(),
]);

return await registerOffchain(
signers.ethSigner,
signers.starkSigner,
user,
this.authManager,
this.imxApiClients,
);
},
'imxRegisterOffchain',
);
}

async isRegisteredOffchain(): Promise<boolean> {
const user = await this.#getAuthenticatedUser();

return !!user.imx;
return withMetricsAsync(
async () => {
const user = await this.#getAuthenticatedUser();
return !!user.imx;
},
'imxIsRegisteredOffchain',
);
}

// eslint-disable-next-line class-methods-use-this, @typescript-eslint/no-unused-vars
Expand All @@ -218,7 +228,7 @@ export class PassportImxProvider implements IMXProvider {
}

async createOrder(request: UnsignedOrderRequest): Promise<imx.CreateOrderResponse> {
return this.guardianClient.withDefaultConfirmationScreenTask(
return withMetricsAsync(() => this.guardianClient.withDefaultConfirmationScreenTask(
async () => {
const { user, starkSigner } = await this.#getRegisteredImxUserAndSigners();
return createOrder({
Expand All @@ -229,13 +239,13 @@ export class PassportImxProvider implements IMXProvider {
guardianClient: this.guardianClient,
});
},
)();
)(), 'imxCreateOrder');
}

async cancelOrder(
request: imx.GetSignableCancelOrderRequest,
): Promise<imx.CancelOrderResponse> {
return this.guardianClient.withDefaultConfirmationScreenTask(
return withMetricsAsync(() => this.guardianClient.withDefaultConfirmationScreenTask(
async () => {
const { user, starkSigner } = await this.#getRegisteredImxUserAndSigners();

Expand All @@ -247,11 +257,11 @@ export class PassportImxProvider implements IMXProvider {
guardianClient: this.guardianClient,
});
},
)();
)(), 'imxCancelOrder');
}

async createTrade(request: imx.GetSignableTradeRequest): Promise<imx.CreateTradeResponse> {
return this.guardianClient.withDefaultConfirmationScreenTask(
return withMetricsAsync(() => this.guardianClient.withDefaultConfirmationScreenTask(
async () => {
const { user, starkSigner } = await this.#getRegisteredImxUserAndSigners();

Expand All @@ -263,13 +273,13 @@ export class PassportImxProvider implements IMXProvider {
guardianClient: this.guardianClient,
});
},
)();
)(), 'imxCreateTrade');
}

async batchNftTransfer(
request: NftTransferDetails[],
): Promise<imx.CreateTransferResponse> {
return this.guardianClient.withConfirmationScreenTask(
return withMetricsAsync(() => this.guardianClient.withConfirmationScreenTask(
{ width: 480, height: 784 },
)(async () => {
const { user, starkSigner } = await this.#getRegisteredImxUserAndSigners();
Expand All @@ -281,20 +291,22 @@ export class PassportImxProvider implements IMXProvider {
transfersApi: this.immutableXClient.transfersApi,
guardianClient: this.guardianClient,
});
})();
})(), 'imxBatchNftTransfer');
}

async exchangeTransfer(
request: UnsignedExchangeTransferRequest,
): Promise<imx.CreateTransferResponseV1> {
const { user, starkSigner } = await this.#getRegisteredImxUserAndSigners();
return withMetricsAsync(async () => {
const { user, starkSigner } = await this.#getRegisteredImxUserAndSigners();

return exchangeTransfer({
request,
user,
starkSigner,
exchangesApi: this.immutableXClient.exchangeApi,
});
return exchangeTransfer({
request,
user,
starkSigner,
exchangesApi: this.immutableXClient.exchangeApi,
});
}, 'imxExchangeTransfer');
}

// eslint-disable-next-line class-methods-use-this, @typescript-eslint/no-unused-vars
Expand Down Expand Up @@ -327,14 +339,16 @@ export class PassportImxProvider implements IMXProvider {
}

async getAddress(): Promise<string> {
const user = await this.#getAuthenticatedUser();
if (!isUserImx(user)) {
throw new PassportError(
'User has not been registered with StarkEx',
PassportErrorType.USER_NOT_REGISTERED_ERROR,
);
}
return withMetricsAsync(async () => {
const user = await this.#getAuthenticatedUser();
if (!isUserImx(user)) {
throw new PassportError(
'User has not been registered with StarkEx',
PassportErrorType.USER_NOT_REGISTERED_ERROR,
);
}

return Promise.resolve(user.imx.ethAddress);
return Promise.resolve(user.imx.ethAddress);
}, 'imxGetAddress');
}
}
Loading

0 comments on commit 8ab5b97

Please sign in to comment.