@ledgerhq/live-common 34.54.0 → 34.54.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/e2e/speculosCI.d.ts.map +1 -1
- package/lib/e2e/speculosCI.js +1 -6
- package/lib/e2e/speculosCI.js.map +1 -1
- package/lib/exchange/swap/postSwapState.d.ts.map +1 -1
- package/lib/exchange/swap/postSwapState.js +10 -6
- package/lib/exchange/swap/postSwapState.js.map +1 -1
- package/lib/exchange/swap/transactionStrategies.d.ts +2 -1
- package/lib/exchange/swap/transactionStrategies.d.ts.map +1 -1
- package/lib/exchange/swap/transactionStrategies.js +7 -6
- package/lib/exchange/swap/transactionStrategies.js.map +1 -1
- package/lib/exchange/swap/types.d.ts +3 -1
- package/lib/exchange/swap/types.d.ts.map +1 -1
- package/lib/hw/connectAppEventMapper.d.ts.map +1 -1
- package/lib/hw/connectAppEventMapper.js +21 -2
- package/lib/hw/connectAppEventMapper.js.map +1 -1
- package/lib/hw/getAppAndVersion.d.ts +3 -1
- package/lib/hw/getAppAndVersion.d.ts.map +1 -1
- package/lib/hw/getAppAndVersion.js +2 -2
- package/lib/hw/getAppAndVersion.js.map +1 -1
- package/lib/wallet-api/Exchange/SwapError.d.ts +93 -0
- package/lib/wallet-api/Exchange/SwapError.d.ts.map +1 -0
- package/lib/wallet-api/Exchange/SwapError.js +142 -0
- package/lib/wallet-api/Exchange/SwapError.js.map +1 -0
- package/lib/wallet-api/Exchange/handleSwapErrors.d.ts +40 -0
- package/lib/wallet-api/Exchange/handleSwapErrors.d.ts.map +1 -0
- package/lib/wallet-api/Exchange/handleSwapErrors.js +112 -0
- package/lib/wallet-api/Exchange/handleSwapErrors.js.map +1 -0
- package/lib/wallet-api/Exchange/index.d.ts +4 -0
- package/lib/wallet-api/Exchange/index.d.ts.map +1 -0
- package/lib/wallet-api/Exchange/index.js +27 -0
- package/lib/wallet-api/Exchange/index.js.map +1 -0
- package/lib/wallet-api/Exchange/parser.d.ts +46 -0
- package/lib/wallet-api/Exchange/parser.d.ts.map +1 -0
- package/lib/wallet-api/Exchange/parser.js +97 -0
- package/lib/wallet-api/Exchange/parser.js.map +1 -0
- package/lib/wallet-api/Exchange/server.d.ts.map +1 -1
- package/lib/wallet-api/Exchange/server.js +227 -174
- package/lib/wallet-api/Exchange/server.js.map +1 -1
- package/lib/wallet-api/Exchange/tracking.d.ts +7 -6
- package/lib/wallet-api/Exchange/tracking.d.ts.map +1 -1
- package/lib/wallet-api/Exchange/tracking.js +52 -13
- package/lib/wallet-api/Exchange/tracking.js.map +1 -1
- package/lib/wallet-api/logic.d.ts +1 -1
- package/lib/wallet-api/logic.d.ts.map +1 -1
- package/lib/wallet-api/logic.js +5 -5
- package/lib/wallet-api/logic.js.map +1 -1
- package/lib/wallet-api/react.d.ts.map +1 -1
- package/lib/wallet-api/react.js +9 -6
- package/lib/wallet-api/react.js.map +1 -1
- package/lib/wallet-api/tracking.d.ts +5 -5
- package/lib/wallet-api/tracking.d.ts.map +1 -1
- package/lib/wallet-api/tracking.js +30 -10
- package/lib/wallet-api/tracking.js.map +1 -1
- package/lib-es/e2e/speculosCI.d.ts.map +1 -1
- package/lib-es/e2e/speculosCI.js +1 -6
- package/lib-es/e2e/speculosCI.js.map +1 -1
- package/lib-es/exchange/swap/postSwapState.d.ts.map +1 -1
- package/lib-es/exchange/swap/postSwapState.js +10 -6
- package/lib-es/exchange/swap/postSwapState.js.map +1 -1
- package/lib-es/exchange/swap/transactionStrategies.d.ts +2 -1
- package/lib-es/exchange/swap/transactionStrategies.d.ts.map +1 -1
- package/lib-es/exchange/swap/transactionStrategies.js +7 -6
- package/lib-es/exchange/swap/transactionStrategies.js.map +1 -1
- package/lib-es/exchange/swap/types.d.ts +3 -1
- package/lib-es/exchange/swap/types.d.ts.map +1 -1
- package/lib-es/hw/connectAppEventMapper.d.ts.map +1 -1
- package/lib-es/hw/connectAppEventMapper.js +23 -4
- package/lib-es/hw/connectAppEventMapper.js.map +1 -1
- package/lib-es/hw/getAppAndVersion.d.ts +3 -1
- package/lib-es/hw/getAppAndVersion.d.ts.map +1 -1
- package/lib-es/hw/getAppAndVersion.js +2 -2
- package/lib-es/hw/getAppAndVersion.js.map +1 -1
- package/lib-es/wallet-api/Exchange/SwapError.d.ts +93 -0
- package/lib-es/wallet-api/Exchange/SwapError.d.ts.map +1 -0
- package/lib-es/wallet-api/Exchange/SwapError.js +128 -0
- package/lib-es/wallet-api/Exchange/SwapError.js.map +1 -0
- package/lib-es/wallet-api/Exchange/handleSwapErrors.d.ts +40 -0
- package/lib-es/wallet-api/Exchange/handleSwapErrors.d.ts.map +1 -0
- package/lib-es/wallet-api/Exchange/handleSwapErrors.js +106 -0
- package/lib-es/wallet-api/Exchange/handleSwapErrors.js.map +1 -0
- package/lib-es/wallet-api/Exchange/index.d.ts +4 -0
- package/lib-es/wallet-api/Exchange/index.d.ts.map +1 -0
- package/lib-es/wallet-api/Exchange/index.js +7 -0
- package/lib-es/wallet-api/Exchange/index.js.map +1 -0
- package/lib-es/wallet-api/Exchange/parser.d.ts +46 -0
- package/lib-es/wallet-api/Exchange/parser.d.ts.map +1 -0
- package/lib-es/wallet-api/Exchange/parser.js +90 -0
- package/lib-es/wallet-api/Exchange/parser.js.map +1 -0
- package/lib-es/wallet-api/Exchange/server.d.ts.map +1 -1
- package/lib-es/wallet-api/Exchange/server.js +224 -174
- package/lib-es/wallet-api/Exchange/server.js.map +1 -1
- package/lib-es/wallet-api/Exchange/tracking.d.ts +7 -6
- package/lib-es/wallet-api/Exchange/tracking.d.ts.map +1 -1
- package/lib-es/wallet-api/Exchange/tracking.js +52 -13
- package/lib-es/wallet-api/Exchange/tracking.js.map +1 -1
- package/lib-es/wallet-api/logic.d.ts +1 -1
- package/lib-es/wallet-api/logic.d.ts.map +1 -1
- package/lib-es/wallet-api/logic.js +5 -5
- package/lib-es/wallet-api/logic.js.map +1 -1
- package/lib-es/wallet-api/react.d.ts.map +1 -1
- package/lib-es/wallet-api/react.js +9 -6
- package/lib-es/wallet-api/react.js.map +1 -1
- package/lib-es/wallet-api/tracking.d.ts +5 -5
- package/lib-es/wallet-api/tracking.d.ts.map +1 -1
- package/lib-es/wallet-api/tracking.js +30 -10
- package/lib-es/wallet-api/tracking.js.map +1 -1
- package/package.json +68 -68
- package/src/e2e/speculosCI.ts +1 -6
- package/src/exchange/swap/postSwapState.ts +10 -5
- package/src/exchange/swap/transactionStrategies.ts +8 -7
- package/src/exchange/swap/types.ts +3 -1
- package/src/hw/connectAppEventMapper.ts +28 -4
- package/src/hw/getAppAndVersion.ts +2 -1
- package/src/wallet-api/Exchange/SwapError.test.ts +126 -0
- package/src/wallet-api/Exchange/SwapError.ts +159 -0
- package/src/wallet-api/Exchange/handleSwapErrors.test.ts +46 -0
- package/src/wallet-api/Exchange/handleSwapErrors.ts +161 -0
- package/src/wallet-api/Exchange/index.ts +26 -0
- package/src/wallet-api/Exchange/parser.test.ts +86 -0
- package/src/wallet-api/Exchange/parser.ts +119 -0
- package/src/wallet-api/Exchange/server.ts +289 -232
- package/src/wallet-api/Exchange/tracking.ts +56 -13
- package/src/wallet-api/logic.ts +5 -4
- package/src/wallet-api/react.ts +10 -5
- package/src/wallet-api/tracking.ts +30 -10
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/* eslint-env jest */
|
|
2
|
+
import { handleErrors, isHandledError } from "./handleSwapErrors";
|
|
3
|
+
import { IgnoredSignatureStepError, SignatureStepError } from "./SwapError";
|
|
4
|
+
|
|
5
|
+
describe("handleErrors", () => {
|
|
6
|
+
it("marks default ignored message errors as handled and rethrows", async () => {
|
|
7
|
+
const error = new Error("User refused");
|
|
8
|
+
|
|
9
|
+
expect(() => handleErrors(error)).toThrow(error);
|
|
10
|
+
expect(isHandledError(error)).toBe(true);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it("marks default ignored error names as handled and rethrows", async () => {
|
|
14
|
+
const nestedError = new Error("Device mismatch");
|
|
15
|
+
nestedError.name = "WrongDeviceForAccount";
|
|
16
|
+
const error = new SignatureStepError(nestedError);
|
|
17
|
+
|
|
18
|
+
expect(() => handleErrors(error)).toThrow(error);
|
|
19
|
+
expect(isHandledError(error)).toBe(true);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("invokes onDisplayError for swap errors that should be surfaced", async () => {
|
|
23
|
+
const error = new SignatureStepError(new Error("Unexpected failure"));
|
|
24
|
+
const onDisplayError = jest.fn().mockResolvedValue(undefined);
|
|
25
|
+
|
|
26
|
+
await expect(handleErrors(error, { onDisplayError })).resolves.toBeUndefined();
|
|
27
|
+
expect(onDisplayError).toHaveBeenCalledWith(error);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("skips onDisplayError for swap003Ignored errors and marks them handled", async () => {
|
|
31
|
+
const error = new IgnoredSignatureStepError(new Error("User rejected"));
|
|
32
|
+
const onDisplayError = jest.fn();
|
|
33
|
+
|
|
34
|
+
expect(() => handleErrors(error, { onDisplayError })).toThrow(error);
|
|
35
|
+
expect(onDisplayError).not.toHaveBeenCalled();
|
|
36
|
+
expect(isHandledError(error)).toBe(true);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("respects custom ignored error names", async () => {
|
|
40
|
+
const error = new Error("Rate limited");
|
|
41
|
+
Object.assign(error, { cause: { name: "RateLimitedError" } });
|
|
42
|
+
|
|
43
|
+
expect(() => handleErrors(error, { ignoredErrorNames: ["RateLimitedError"] })).toThrow(error);
|
|
44
|
+
expect(isHandledError(error)).toBe(true);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error Handler
|
|
3
|
+
* Decides how to handle errors - display to user, mark as handled, or throw
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { SwapError } from "./SwapError";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Interface for error handler options
|
|
10
|
+
*/
|
|
11
|
+
export interface ErrorHandlerOptions {
|
|
12
|
+
/**
|
|
13
|
+
* Callback to display error to user (optional)
|
|
14
|
+
* If not provided, errors are simply thrown
|
|
15
|
+
*/
|
|
16
|
+
onDisplayError?: (error: SwapError) => Promise<void>;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Additional error names to ignore (won't be displayed)
|
|
20
|
+
*/
|
|
21
|
+
ignoredErrorNames?: string[];
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Additional error messages to ignore (won't be displayed)
|
|
25
|
+
*/
|
|
26
|
+
ignoredMessages?: string[];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Default error names that should be marked as "handled"
|
|
31
|
+
* These are typically user input errors that can be retried
|
|
32
|
+
*/
|
|
33
|
+
const DEFAULT_IGNORED_ERROR_NAMES = new Set([
|
|
34
|
+
"WrongDeviceForAccount",
|
|
35
|
+
"WrongDeviceForAccountPayout",
|
|
36
|
+
"WrongDeviceForAccountRefund",
|
|
37
|
+
"UserRefusedOnDevice",
|
|
38
|
+
]);
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Default error messages that should be marked as "handled"
|
|
42
|
+
*/
|
|
43
|
+
const DEFAULT_IGNORED_MESSAGES = new Set(["User refused", "User rejected"]);
|
|
44
|
+
|
|
45
|
+
type SwapErrorCauseDetails = {
|
|
46
|
+
swapCode?: string;
|
|
47
|
+
name?: string;
|
|
48
|
+
message?: string;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
type ErrorWithCause = Error & { cause?: unknown };
|
|
52
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
53
|
+
return typeof value === "object" && value !== null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function hasCause(value: Error): value is ErrorWithCause {
|
|
57
|
+
return "cause" in value;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function toCauseDetails(rawCause: unknown): SwapErrorCauseDetails | undefined {
|
|
61
|
+
if (!isRecord(rawCause)) {
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const swapCode = typeof rawCause.swapCode === "string" ? rawCause.swapCode : undefined;
|
|
66
|
+
const name = typeof rawCause.name === "string" ? rawCause.name : undefined;
|
|
67
|
+
const message = typeof rawCause.message === "string" ? rawCause.message : undefined;
|
|
68
|
+
|
|
69
|
+
if (swapCode || name || message) {
|
|
70
|
+
return { swapCode, name, message };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function extractErrorDetails(value: unknown): {
|
|
77
|
+
message: string;
|
|
78
|
+
cause?: SwapErrorCauseDetails;
|
|
79
|
+
} {
|
|
80
|
+
if (value instanceof SwapError) {
|
|
81
|
+
return { message: value.message, cause: value.cause };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (value instanceof Error) {
|
|
85
|
+
return {
|
|
86
|
+
message: value.message,
|
|
87
|
+
cause: hasCause(value) ? toCauseDetails(value.cause) : undefined,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (isRecord(value) && typeof value.message === "string") {
|
|
92
|
+
const rawCause = "cause" in value ? value.cause : undefined;
|
|
93
|
+
const cause = toCauseDetails(rawCause);
|
|
94
|
+
return { message: value.message, cause };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return { message: typeof value === "string" ? value : JSON.stringify(value ?? "Unknown error") };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function markErrorAsHandled<T>(error: T): T {
|
|
101
|
+
if (isRecord(error)) {
|
|
102
|
+
Object.assign(error, { handled: true });
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return error;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Handles errors by deciding whether to display them or mark as handled
|
|
110
|
+
*
|
|
111
|
+
* @param error - The error to handle
|
|
112
|
+
* @param options - Configuration options
|
|
113
|
+
* @throws Enhanced error with `handled` flag if applicable
|
|
114
|
+
*/
|
|
115
|
+
export function handleErrors(error: unknown, options: ErrorHandlerOptions = {}): Promise<void> {
|
|
116
|
+
const { onDisplayError, ignoredErrorNames = [], ignoredMessages = [] } = options;
|
|
117
|
+
|
|
118
|
+
// Merge default and custom ignored values
|
|
119
|
+
const allIgnoredNames = new Set([...DEFAULT_IGNORED_ERROR_NAMES, ...ignoredErrorNames]);
|
|
120
|
+
|
|
121
|
+
const allIgnoredMessages = new Set([...DEFAULT_IGNORED_MESSAGES, ...ignoredMessages]);
|
|
122
|
+
|
|
123
|
+
const { message, cause } = extractErrorDetails(error);
|
|
124
|
+
|
|
125
|
+
// Check if error should be marked as handled (retry-ready)
|
|
126
|
+
const isIgnoredMessage = allIgnoredMessages.has(message);
|
|
127
|
+
const isIgnoredName = typeof cause?.name === "string" && allIgnoredNames.has(cause.name);
|
|
128
|
+
const isIgnoredSwapCode = cause?.swapCode === "swap003Ignored";
|
|
129
|
+
|
|
130
|
+
if (isIgnoredMessage || isIgnoredName || isIgnoredSwapCode) {
|
|
131
|
+
throw markErrorAsHandled(error);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Display error to user if handler provided
|
|
135
|
+
if (error instanceof SwapError && onDisplayError) {
|
|
136
|
+
// Skip displaying "swap003Ignored" errors
|
|
137
|
+
if (cause?.swapCode !== "swap003Ignored") {
|
|
138
|
+
return onDisplayError(error);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Always throw the error so caller can handle it
|
|
143
|
+
throw error;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Type guard to check if an error is marked as handled
|
|
148
|
+
*/
|
|
149
|
+
export function isHandledError(error: unknown): boolean {
|
|
150
|
+
return isRecord(error) && error.handled === true;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Extracts swap code from error if available
|
|
155
|
+
*/
|
|
156
|
+
export function getSwapCode(error: unknown): string | undefined {
|
|
157
|
+
if (error instanceof SwapError) {
|
|
158
|
+
return error.cause.swapCode;
|
|
159
|
+
}
|
|
160
|
+
return undefined;
|
|
161
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// Export all error classes
|
|
2
|
+
export {
|
|
3
|
+
SwapError,
|
|
4
|
+
NonceStepError,
|
|
5
|
+
PayloadStepError,
|
|
6
|
+
SignatureStepError,
|
|
7
|
+
IgnoredSignatureStepError,
|
|
8
|
+
NotEnoughFunds,
|
|
9
|
+
ListAccountError,
|
|
10
|
+
ListCurrencyError,
|
|
11
|
+
UnknownAccountError,
|
|
12
|
+
PayinExtraIdError,
|
|
13
|
+
CompleteExchangeError,
|
|
14
|
+
type CompleteExchangeStep,
|
|
15
|
+
} from "./SwapError";
|
|
16
|
+
|
|
17
|
+
// Export parser utilities
|
|
18
|
+
export { createStepError, StepError, CustomErrorType, type ParseError } from "./parser";
|
|
19
|
+
|
|
20
|
+
// Export error handler
|
|
21
|
+
export {
|
|
22
|
+
handleErrors,
|
|
23
|
+
isHandledError,
|
|
24
|
+
getSwapCode,
|
|
25
|
+
type ErrorHandlerOptions,
|
|
26
|
+
} from "./handleSwapErrors";
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { createStepError, CustomErrorType, parseError, StepError } from "./parser";
|
|
2
|
+
import {
|
|
3
|
+
IgnoredSignatureStepError,
|
|
4
|
+
ListAccountError,
|
|
5
|
+
ListCurrencyError,
|
|
6
|
+
NonceStepError,
|
|
7
|
+
NotEnoughFunds,
|
|
8
|
+
PayloadStepError,
|
|
9
|
+
PayinExtraIdError,
|
|
10
|
+
SignatureStepError,
|
|
11
|
+
SwapError,
|
|
12
|
+
UnknownAccountError,
|
|
13
|
+
} from "./SwapError";
|
|
14
|
+
|
|
15
|
+
type StepExpectation = {
|
|
16
|
+
step: StepError;
|
|
17
|
+
ExpectedError: new (err?: Error) => SwapError;
|
|
18
|
+
swapCode: string;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const STEP_EXPECTATIONS: StepExpectation[] = [
|
|
22
|
+
{ step: StepError.NONCE, ExpectedError: NonceStepError, swapCode: "swap001" },
|
|
23
|
+
{ step: StepError.PAYLOAD, ExpectedError: PayloadStepError, swapCode: "swap002" },
|
|
24
|
+
{ step: StepError.SIGNATURE, ExpectedError: SignatureStepError, swapCode: "swap003" },
|
|
25
|
+
{
|
|
26
|
+
step: StepError.IGNORED_SIGNATURE,
|
|
27
|
+
ExpectedError: IgnoredSignatureStepError,
|
|
28
|
+
swapCode: "swap003Ignored",
|
|
29
|
+
},
|
|
30
|
+
{ step: StepError.CHECK_FUNDS, ExpectedError: NotEnoughFunds, swapCode: "swap004" },
|
|
31
|
+
{ step: StepError.LIST_ACCOUNT, ExpectedError: ListAccountError, swapCode: "swap005" },
|
|
32
|
+
{ step: StepError.LIST_CURRENCY, ExpectedError: ListCurrencyError, swapCode: "swap006" },
|
|
33
|
+
{ step: StepError.UNKNOWN_ACCOUNT, ExpectedError: UnknownAccountError, swapCode: "swap007" },
|
|
34
|
+
{ step: StepError.PAYIN_EXTRA_ID, ExpectedError: PayinExtraIdError, swapCode: "swap010" },
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
const mockDownstreamError = new Error("error message");
|
|
38
|
+
|
|
39
|
+
describe("parseError", () => {
|
|
40
|
+
it.each(STEP_EXPECTATIONS)(
|
|
41
|
+
"$step - returns generic error when customErrorType is not passed in",
|
|
42
|
+
({ step }) => {
|
|
43
|
+
const typedStep = step as StepError;
|
|
44
|
+
|
|
45
|
+
const error = parseError({
|
|
46
|
+
error: mockDownstreamError,
|
|
47
|
+
step: typedStep,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
expect(error).toBe(mockDownstreamError);
|
|
51
|
+
},
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
it.each(STEP_EXPECTATIONS)(
|
|
55
|
+
"$step - returns specific swap error when customErrorType is 'swap'",
|
|
56
|
+
({ step, ExpectedError, swapCode }) => {
|
|
57
|
+
const typedStep = step as StepError;
|
|
58
|
+
|
|
59
|
+
const error = parseError({
|
|
60
|
+
error: mockDownstreamError,
|
|
61
|
+
step: typedStep,
|
|
62
|
+
customErrorType: CustomErrorType.SWAP,
|
|
63
|
+
}) as SwapError;
|
|
64
|
+
|
|
65
|
+
expect(error).toBeInstanceOf(ExpectedError);
|
|
66
|
+
expect(error.cause.swapCode).toBe(swapCode);
|
|
67
|
+
},
|
|
68
|
+
);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
describe("createStepError", () => {
|
|
72
|
+
it("returns downstream error when no step is supplied", () => {
|
|
73
|
+
expect(createStepError({ error: mockDownstreamError })).toBe(mockDownstreamError);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it.each(STEP_EXPECTATIONS)(
|
|
77
|
+
"$step - wraps downstream error with appropriate swap error",
|
|
78
|
+
({ step, ExpectedError, swapCode }) => {
|
|
79
|
+
const typedStep = step as StepError;
|
|
80
|
+
const wrapped = createStepError({ error: mockDownstreamError, step: typedStep }) as SwapError;
|
|
81
|
+
|
|
82
|
+
expect(wrapped).toBeInstanceOf(ExpectedError);
|
|
83
|
+
expect(wrapped.cause.swapCode).toBe(swapCode);
|
|
84
|
+
},
|
|
85
|
+
);
|
|
86
|
+
});
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error Parser
|
|
3
|
+
* Routes errors to appropriate error classes based on step and context
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
IgnoredSignatureStepError,
|
|
8
|
+
ListAccountError,
|
|
9
|
+
ListCurrencyError,
|
|
10
|
+
NonceStepError,
|
|
11
|
+
NotEnoughFunds,
|
|
12
|
+
PayinExtraIdError,
|
|
13
|
+
PayloadStepError,
|
|
14
|
+
SignatureStepError,
|
|
15
|
+
UnknownAccountError,
|
|
16
|
+
} from "./SwapError";
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Transaction step where error occurred
|
|
20
|
+
*/
|
|
21
|
+
export enum StepError {
|
|
22
|
+
NONCE = "NonceStepError",
|
|
23
|
+
PAYLOAD = "PayloadStepError",
|
|
24
|
+
SIGNATURE = "SignatureStepError",
|
|
25
|
+
IGNORED_SIGNATURE = "IgnoredSignatureStepError",
|
|
26
|
+
CHECK_FUNDS = "CheckFundsStepError",
|
|
27
|
+
LIST_ACCOUNT = "ListAccountStepError",
|
|
28
|
+
LIST_CURRENCY = "ListCurrencyStepError",
|
|
29
|
+
UNKNOWN_ACCOUNT = "UnknownAccountStepError",
|
|
30
|
+
PAYIN_EXTRA_ID = "PayinExtraIdStepError",
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Input for error parsing
|
|
35
|
+
*/
|
|
36
|
+
export enum CustomErrorType {
|
|
37
|
+
SWAP = "swap",
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export type ParseError = {
|
|
41
|
+
error: Error;
|
|
42
|
+
step?: StepError;
|
|
43
|
+
customErrorType?: CustomErrorType;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Maps step errors to error constructors
|
|
48
|
+
*/
|
|
49
|
+
const ErrorMap: Record<StepError, new (err?: Error) => Error> = {
|
|
50
|
+
[StepError.NONCE]: NonceStepError,
|
|
51
|
+
[StepError.PAYLOAD]: PayloadStepError,
|
|
52
|
+
[StepError.SIGNATURE]: SignatureStepError,
|
|
53
|
+
[StepError.IGNORED_SIGNATURE]: IgnoredSignatureStepError,
|
|
54
|
+
[StepError.CHECK_FUNDS]: NotEnoughFunds,
|
|
55
|
+
[StepError.LIST_ACCOUNT]: ListAccountError,
|
|
56
|
+
[StepError.LIST_CURRENCY]: ListCurrencyError,
|
|
57
|
+
[StepError.UNKNOWN_ACCOUNT]: UnknownAccountError,
|
|
58
|
+
[StepError.PAYIN_EXTRA_ID]: PayinExtraIdError,
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Creates a step-specific error by wrapping the original error
|
|
63
|
+
*
|
|
64
|
+
* @param error - Original error that occurred
|
|
65
|
+
* @param step - Step where error occurred (optional)
|
|
66
|
+
* @returns Wrapped error or original error if no step specified
|
|
67
|
+
*/
|
|
68
|
+
export function createStepError({ error, step }: ParseError): Error {
|
|
69
|
+
// If no step specified, return original error
|
|
70
|
+
if (!step) {
|
|
71
|
+
return error;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Get error constructor for this step
|
|
75
|
+
const ErrorConstructor = ErrorMap[step];
|
|
76
|
+
|
|
77
|
+
if (!ErrorConstructor) {
|
|
78
|
+
return error;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Wrap original error in step-specific error class
|
|
82
|
+
return new ErrorConstructor(error);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Parses an error to determine the correct error class to return based on the context.
|
|
87
|
+
*/
|
|
88
|
+
export function parseError({ error, step, customErrorType }: ParseError): Error {
|
|
89
|
+
if (!step || customErrorType !== CustomErrorType.SWAP) {
|
|
90
|
+
return error;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return createStepError({ error, step });
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export const hasMessage = (value: unknown): value is { message?: unknown } =>
|
|
97
|
+
Boolean(value && typeof value === "object" && "message" in value);
|
|
98
|
+
|
|
99
|
+
export const toError = (value: unknown): Error => {
|
|
100
|
+
if (value instanceof Error) {
|
|
101
|
+
return value;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (hasMessage(value) && typeof value.message === "string") {
|
|
105
|
+
return new Error(value.message);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return new Error(
|
|
109
|
+
typeof value === "string"
|
|
110
|
+
? value
|
|
111
|
+
: (() => {
|
|
112
|
+
try {
|
|
113
|
+
return JSON.stringify(value);
|
|
114
|
+
} catch {
|
|
115
|
+
return String(value);
|
|
116
|
+
}
|
|
117
|
+
})(),
|
|
118
|
+
);
|
|
119
|
+
};
|