zksync-sso
Version:
ZKsync Smart Sign On SDK
153 lines (126 loc) • 4.1 kB
text/typescript
import { errorValues, standardErrorCodes } from "./constants.js";
const FALLBACK_MESSAGE = "Unspecified error message.";
const JSON_RPC_SERVER_ERROR_MESSAGE = "Unspecified server error.";
type ErrorValueKey = keyof typeof errorValues;
/**
* Gets the message for a given code, or a fallback message if the code has
* no corresponding message.
*/
export function getMessageFromCode(
code: number | undefined,
fallbackMessage: string = FALLBACK_MESSAGE,
): string {
if (code && Number.isInteger(code)) {
const codeString = code.toString();
if (hasKey(errorValues, codeString)) {
return errorValues[codeString as ErrorValueKey].message;
}
if (isJsonRpcServerError(code)) {
return JSON_RPC_SERVER_ERROR_MESSAGE;
}
}
return fallbackMessage;
}
/**
* Returns whether the given code is valid.
* A code is only valid if it has a message.
*/
export function isValidCode(code: number): boolean {
if (!Number.isInteger(code)) {
return false;
}
const codeString = code.toString();
if (errorValues[codeString as ErrorValueKey]) {
return true;
}
if (isJsonRpcServerError(code)) {
return true;
}
return false;
}
/**
* Returns the error code from an error object.
*/
export function getErrorCode(error: unknown): number | undefined {
if (typeof error === "number") {
return error;
} else if (isErrorWithCode(error)) {
return error.code ?? error.errorCode;
}
return undefined;
}
interface ErrorWithCode {
code?: number;
errorCode?: number;
}
function isErrorWithCode(error: unknown): error is ErrorWithCode {
return (
typeof error === "object"
&& error !== null
&& (typeof (error as ErrorWithCode).code === "number"
|| typeof (error as ErrorWithCode).errorCode === "number")
);
}
/**
* Serializes the given error to an Ethereum JSON RPC-compatible error object.
* Merely copies the given error's values if it is already compatible.
* If the given error is not fully compatible, it will be preserved on the
* returned object's data.originalError property.
*/
export interface SerializedEthereumRpcError {
code: number; // must be an integer
message: string;
data?: unknown;
stack?: string;
}
export function serialize(
error: unknown,
{ shouldIncludeStack = false } = {},
): SerializedEthereumRpcError {
const serialized: Partial<SerializedEthereumRpcError> = {};
if (
error
&& typeof error === "object"
&& !Array.isArray(error)
&& hasKey(error as Record<string, unknown>, "code")
&& isValidCode((error as SerializedEthereumRpcError).code)
) {
const _error = error as Partial<SerializedEthereumRpcError>;
serialized.code = _error.code;
if (_error.message && typeof _error.message === "string") {
serialized.message = _error.message;
if (hasKey(_error, "data")) {
serialized.data = _error.data;
}
} else {
serialized.message = getMessageFromCode((serialized as SerializedEthereumRpcError).code);
serialized.data = { originalError: assignOriginalError(error) };
}
} else {
serialized.code = standardErrorCodes.rpc.internal;
serialized.message = hasStringProperty(error, "message") ? error.message : FALLBACK_MESSAGE;
serialized.data = { originalError: assignOriginalError(error) };
}
if (shouldIncludeStack) {
serialized.stack = hasStringProperty(error, "stack") ? error.stack : undefined;
}
return serialized as SerializedEthereumRpcError;
}
// Internal
function isJsonRpcServerError(code: number): boolean {
return code >= -32099 && code <= -32000;
}
function assignOriginalError(error: unknown): unknown {
if (error && typeof error === "object" && !Array.isArray(error)) {
return Object.assign({}, error);
}
return error;
}
function hasKey(obj: Record<string, unknown>, key: string) {
return Object.prototype.hasOwnProperty.call(obj, key);
}
function hasStringProperty<T>(obj: unknown, prop: keyof T): obj is T {
return (
typeof obj === "object" && obj !== null && prop in obj && typeof (obj as T)[prop] === "string"
);
}