@metamask/utils
Version:
Various JavaScript/TypeScript utilities of wide relevance to the MetaMask codebase
439 lines • 17.9 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.getJsonRpcIdValidator = exports.assertIsJsonRpcError = exports.isJsonRpcError = exports.assertIsJsonRpcFailure = exports.isJsonRpcFailure = exports.assertIsJsonRpcSuccess = exports.isJsonRpcSuccess = exports.assertIsJsonRpcResponse = exports.isJsonRpcResponse = exports.assertIsPendingJsonRpcResponse = exports.isPendingJsonRpcResponse = exports.JsonRpcResponseStruct = exports.JsonRpcFailureStruct = exports.JsonRpcSuccessStruct = exports.PendingJsonRpcResponseStruct = exports.assertIsJsonRpcRequest = exports.isJsonRpcRequest = exports.assertIsJsonRpcNotification = exports.isJsonRpcNotification = exports.JsonRpcNotificationStruct = exports.JsonRpcRequestStruct = exports.JsonRpcParamsStruct = exports.JsonRpcErrorStruct = exports.JsonRpcIdStruct = exports.JsonRpcVersionStruct = exports.jsonrpc2 = exports.getJsonSize = exports.getSafeJson = exports.isValidJson = exports.JsonStruct = exports.UnsafeJsonStruct = exports.exactOptional = exports.object = void 0;
const superstruct_1 = require("@metamask/superstruct");
const assert_1 = require("./assert.cjs");
const misc_1 = require("./misc.cjs");
/**
* A struct to check if the given value is a valid object, with support for
* {@link exactOptional} types.
*
* @deprecated Use `exactOptional` and `object` from `@metamask/superstruct@>=3.2.0` instead.
* @param schema - The schema of the object.
* @returns A struct to check if the given value is an object.
*/
const object = (schema) =>
// The type is slightly different from a regular object struct, because we
// want to make properties with `undefined` in their type optional, but not
// `undefined` itself. This means that we need a type cast.
(0, superstruct_1.object)(schema);
exports.object = object;
/**
* Check the last field of a path is present.
*
* @param context - The context to check.
* @param context.path - The path to check.
* @param context.branch - The branch to check.
* @returns Whether the last field of a path is present.
*/
function hasOptional({ path, branch }) {
const field = path[path.length - 1];
return (0, misc_1.hasProperty)(branch[branch.length - 2], field);
}
/**
* A struct which allows the property of an object to be absent, or to be present
* as long as it's valid and not set to `undefined`.
*
* This struct should be used in conjunction with the {@link object} from this
* library, to get proper type inference.
*
* @deprecated Use `exactOptional` and `object` from `@metamask/superstruct@>=3.2.0` instead.
* @param struct - The struct to check the value against, if present.
* @returns A struct to check if the given value is valid, or not present.
* @example
* ```ts
* const struct = object({
* foo: exactOptional(string()),
* bar: exactOptional(number()),
* baz: optional(boolean()),
* qux: unknown(),
* });
*
* type Type = Infer<typeof struct>;
* // Type is equivalent to:
* // {
* // foo?: string;
* // bar?: number;
* // baz?: boolean | undefined;
* // qux: unknown;
* // }
* ```
*/
function exactOptional(struct) {
return new superstruct_1.Struct({
...struct,
type: `optional ${struct.type}`,
validator: (value, context) => !hasOptional(context) || struct.validator(value, context),
refiner: (value, context) => !hasOptional(context) || struct.refiner(value, context),
});
}
exports.exactOptional = exactOptional;
/**
* Validate an unknown input to be valid JSON.
*
* Useful for constructing JSON structs.
*
* @param json - An unknown value.
* @returns True if the value is valid JSON, otherwise false.
*/
function validateJson(json) {
if (json === null || typeof json === 'boolean' || typeof json === 'string') {
return true;
}
if (typeof json === 'number' && Number.isFinite(json)) {
return true;
}
if (typeof json === 'object') {
let every = true;
if (Array.isArray(json)) {
// Ignoring linting error since for-of is significantly slower than a normal for-loop
// and performance is important in this specific function.
// eslint-disable-next-line @typescript-eslint/prefer-for-of
for (let i = 0; i < json.length; i++) {
if (!validateJson(json[i])) {
every = false;
break;
}
}
return every;
}
const entries = Object.entries(json);
// Ignoring linting errors since for-of is significantly slower than a normal for-loop
// and performance is important in this specific function.
// eslint-disable-next-line @typescript-eslint/prefer-for-of
for (let i = 0; i < entries.length; i++) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
if (typeof entries[i][0] !== 'string' || !validateJson(entries[i][1])) {
every = false;
break;
}
}
return every;
}
return false;
}
/**
* A struct to check if the given value is a valid JSON-serializable value.
*
* Note that this struct is unsafe. For safe validation, use {@link JsonStruct}.
*/
exports.UnsafeJsonStruct = (0, superstruct_1.define)('JSON', (json) => validateJson(json));
/**
* A struct to check if the given value is a valid JSON-serializable value.
*
* This struct sanitizes the value before validating it, so that it is safe to
* use with untrusted input.
*/
exports.JsonStruct = (0, superstruct_1.coerce)(exports.UnsafeJsonStruct, (0, superstruct_1.refine)((0, superstruct_1.any)(), 'JSON', (value) => (0, superstruct_1.is)(value, exports.UnsafeJsonStruct)), (value) => JSON.parse(JSON.stringify(value, (propKey, propValue) => {
// Strip __proto__ and constructor properties to prevent prototype pollution.
if (propKey === '__proto__' || propKey === 'constructor') {
return undefined;
}
return propValue;
})));
/**
* Check if the given value is a valid {@link Json} value, i.e., a value that is
* serializable to JSON.
*
* @param value - The value to check.
* @returns Whether the value is a valid {@link Json} value.
*/
function isValidJson(value) {
try {
getSafeJson(value);
return true;
}
catch {
return false;
}
}
exports.isValidJson = isValidJson;
/**
* Validate and return sanitized JSON.
*
* Note:
* This function uses sanitized JsonStruct for validation
* that applies stringify and then parse of a value provided
* to ensure that there are no getters which can have side effects
* that can cause security issues.
*
* @param value - JSON structure to be processed.
* @returns Sanitized JSON structure.
*/
function getSafeJson(value) {
return (0, superstruct_1.create)(value, exports.JsonStruct);
}
exports.getSafeJson = getSafeJson;
/**
* Get the size of a JSON value in bytes. This also validates the value.
*
* @param value - The JSON value to get the size of.
* @returns The size of the JSON value in bytes.
*/
function getJsonSize(value) {
(0, assert_1.assertStruct)(value, exports.JsonStruct, 'Invalid JSON value');
const json = JSON.stringify(value);
return new TextEncoder().encode(json).byteLength;
}
exports.getJsonSize = getJsonSize;
/**
* The string '2.0'.
*/
exports.jsonrpc2 = '2.0';
exports.JsonRpcVersionStruct = (0, superstruct_1.literal)(exports.jsonrpc2);
exports.JsonRpcIdStruct = (0, superstruct_1.nullable)((0, superstruct_1.union)([(0, superstruct_1.number)(), (0, superstruct_1.string)()]));
exports.JsonRpcErrorStruct = (0, exports.object)({
code: (0, superstruct_1.integer)(),
message: (0, superstruct_1.string)(),
data: exactOptional(exports.JsonStruct),
stack: exactOptional((0, superstruct_1.string)()),
});
exports.JsonRpcParamsStruct = (0, superstruct_1.union)([(0, superstruct_1.record)((0, superstruct_1.string)(), exports.JsonStruct), (0, superstruct_1.array)(exports.JsonStruct)]);
exports.JsonRpcRequestStruct = (0, exports.object)({
id: exports.JsonRpcIdStruct,
jsonrpc: exports.JsonRpcVersionStruct,
method: (0, superstruct_1.string)(),
params: exactOptional(exports.JsonRpcParamsStruct),
});
exports.JsonRpcNotificationStruct = (0, exports.object)({
jsonrpc: exports.JsonRpcVersionStruct,
method: (0, superstruct_1.string)(),
params: exactOptional(exports.JsonRpcParamsStruct),
});
/**
* Check if the given value is a valid {@link JsonRpcNotification} object.
*
* @param value - The value to check.
* @returns Whether the given value is a valid {@link JsonRpcNotification}
* object.
*/
function isJsonRpcNotification(value) {
return (0, superstruct_1.is)(value, exports.JsonRpcNotificationStruct);
}
exports.isJsonRpcNotification = isJsonRpcNotification;
/**
* Assert that the given value is a valid {@link JsonRpcNotification} object.
*
* @param value - The value to check.
* @param ErrorWrapper - The error class to throw if the assertion fails.
* Defaults to {@link AssertionError}.
* @throws If the given value is not a valid {@link JsonRpcNotification} object.
*/
function assertIsJsonRpcNotification(value,
// eslint-disable-next-line @typescript-eslint/naming-convention
ErrorWrapper) {
(0, assert_1.assertStruct)(value, exports.JsonRpcNotificationStruct, 'Invalid JSON-RPC notification', ErrorWrapper);
}
exports.assertIsJsonRpcNotification = assertIsJsonRpcNotification;
/**
* Check if the given value is a valid {@link JsonRpcRequest} object.
*
* @param value - The value to check.
* @returns Whether the given value is a valid {@link JsonRpcRequest} object.
*/
function isJsonRpcRequest(value) {
return (0, superstruct_1.is)(value, exports.JsonRpcRequestStruct);
}
exports.isJsonRpcRequest = isJsonRpcRequest;
/**
* Assert that the given value is a valid {@link JsonRpcRequest} object.
*
* @param value - The JSON-RPC request or notification to check.
* @param ErrorWrapper - The error class to throw if the assertion fails.
* Defaults to {@link AssertionError}.
* @throws If the given value is not a valid {@link JsonRpcRequest} object.
*/
function assertIsJsonRpcRequest(value,
// eslint-disable-next-line @typescript-eslint/naming-convention
ErrorWrapper) {
(0, assert_1.assertStruct)(value, exports.JsonRpcRequestStruct, 'Invalid JSON-RPC request', ErrorWrapper);
}
exports.assertIsJsonRpcRequest = assertIsJsonRpcRequest;
exports.PendingJsonRpcResponseStruct = (0, superstruct_1.object)({
id: exports.JsonRpcIdStruct,
jsonrpc: exports.JsonRpcVersionStruct,
result: (0, superstruct_1.optional)((0, superstruct_1.unknown)()),
error: (0, superstruct_1.optional)(exports.JsonRpcErrorStruct),
});
exports.JsonRpcSuccessStruct = (0, exports.object)({
id: exports.JsonRpcIdStruct,
jsonrpc: exports.JsonRpcVersionStruct,
result: exports.JsonStruct,
});
exports.JsonRpcFailureStruct = (0, exports.object)({
id: exports.JsonRpcIdStruct,
jsonrpc: exports.JsonRpcVersionStruct,
error: exports.JsonRpcErrorStruct,
});
exports.JsonRpcResponseStruct = (0, superstruct_1.union)([
exports.JsonRpcSuccessStruct,
exports.JsonRpcFailureStruct,
]);
/**
* Type guard to check whether specified JSON-RPC response is a
* {@link PendingJsonRpcResponse}.
*
* @param response - The JSON-RPC response to check.
* @returns Whether the specified JSON-RPC response is pending.
*/
function isPendingJsonRpcResponse(response) {
return (0, superstruct_1.is)(response, exports.PendingJsonRpcResponseStruct);
}
exports.isPendingJsonRpcResponse = isPendingJsonRpcResponse;
/**
* Assert that the given value is a valid {@link PendingJsonRpcResponse} object.
*
* @param response - The JSON-RPC response to check.
* @param ErrorWrapper - The error class to throw if the assertion fails.
* Defaults to {@link AssertionError}.
* @throws If the given value is not a valid {@link PendingJsonRpcResponse}
* object.
*/
function assertIsPendingJsonRpcResponse(response,
// eslint-disable-next-line @typescript-eslint/naming-convention
ErrorWrapper) {
(0, assert_1.assertStruct)(response, exports.PendingJsonRpcResponseStruct, 'Invalid pending JSON-RPC response', ErrorWrapper);
}
exports.assertIsPendingJsonRpcResponse = assertIsPendingJsonRpcResponse;
/**
* Type guard to check if a value is a {@link JsonRpcResponse}.
*
* @param response - The object to check.
* @returns Whether the object is a JsonRpcResponse.
*/
function isJsonRpcResponse(response) {
return (0, superstruct_1.is)(response, exports.JsonRpcResponseStruct);
}
exports.isJsonRpcResponse = isJsonRpcResponse;
/**
* Assert that the given value is a valid {@link JsonRpcResponse} object.
*
* @param value - The value to check.
* @param ErrorWrapper - The error class to throw if the assertion fails.
* Defaults to {@link AssertionError}.
* @throws If the given value is not a valid {@link JsonRpcResponse} object.
*/
function assertIsJsonRpcResponse(value,
// eslint-disable-next-line @typescript-eslint/naming-convention
ErrorWrapper) {
(0, assert_1.assertStruct)(value, exports.JsonRpcResponseStruct, 'Invalid JSON-RPC response', ErrorWrapper);
}
exports.assertIsJsonRpcResponse = assertIsJsonRpcResponse;
/**
* Check if the given value is a valid {@link JsonRpcSuccess} object.
*
* @param value - The value to check.
* @returns Whether the given value is a valid {@link JsonRpcSuccess} object.
*/
function isJsonRpcSuccess(value) {
return (0, superstruct_1.is)(value, exports.JsonRpcSuccessStruct);
}
exports.isJsonRpcSuccess = isJsonRpcSuccess;
/**
* Assert that the given value is a valid {@link JsonRpcSuccess} object.
*
* @param value - The value to check.
* @param ErrorWrapper - The error class to throw if the assertion fails.
* Defaults to {@link AssertionError}.
* @throws If the given value is not a valid {@link JsonRpcSuccess} object.
*/
function assertIsJsonRpcSuccess(value,
// eslint-disable-next-line @typescript-eslint/naming-convention
ErrorWrapper) {
(0, assert_1.assertStruct)(value, exports.JsonRpcSuccessStruct, 'Invalid JSON-RPC success response', ErrorWrapper);
}
exports.assertIsJsonRpcSuccess = assertIsJsonRpcSuccess;
/**
* Check if the given value is a valid {@link JsonRpcFailure} object.
*
* @param value - The value to check.
* @returns Whether the given value is a valid {@link JsonRpcFailure} object.
*/
function isJsonRpcFailure(value) {
return (0, superstruct_1.is)(value, exports.JsonRpcFailureStruct);
}
exports.isJsonRpcFailure = isJsonRpcFailure;
/**
* Assert that the given value is a valid {@link JsonRpcFailure} object.
*
* @param value - The value to check.
* @param ErrorWrapper - The error class to throw if the assertion fails.
* Defaults to {@link AssertionError}.
* @throws If the given value is not a valid {@link JsonRpcFailure} object.
*/
function assertIsJsonRpcFailure(value,
// eslint-disable-next-line @typescript-eslint/naming-convention
ErrorWrapper) {
(0, assert_1.assertStruct)(value, exports.JsonRpcFailureStruct, 'Invalid JSON-RPC failure response', ErrorWrapper);
}
exports.assertIsJsonRpcFailure = assertIsJsonRpcFailure;
/**
* Check if the given value is a valid {@link JsonRpcError} object.
*
* @param value - The value to check.
* @returns Whether the given value is a valid {@link JsonRpcError} object.
*/
function isJsonRpcError(value) {
return (0, superstruct_1.is)(value, exports.JsonRpcErrorStruct);
}
exports.isJsonRpcError = isJsonRpcError;
/**
* Assert that the given value is a valid {@link JsonRpcError} object.
*
* @param value - The value to check.
* @param ErrorWrapper - The error class to throw if the assertion fails.
* Defaults to {@link AssertionError}.
* @throws If the given value is not a valid {@link JsonRpcError} object.
*/
function assertIsJsonRpcError(value,
// eslint-disable-next-line @typescript-eslint/naming-convention
ErrorWrapper) {
(0, assert_1.assertStruct)(value, exports.JsonRpcErrorStruct, 'Invalid JSON-RPC error', ErrorWrapper);
}
exports.assertIsJsonRpcError = assertIsJsonRpcError;
/**
* Gets a function for validating JSON-RPC request / response `id` values.
*
* By manipulating the options of this factory, you can control the behavior
* of the resulting validator for some edge cases. This is useful because e.g.
* `null` should sometimes but not always be permitted.
*
* Note that the empty string (`''`) is always permitted by the JSON-RPC
* specification, but that kind of sucks and you may want to forbid it in some
* instances anyway.
*
* For more details, see the
* [JSON-RPC Specification](https://www.jsonrpc.org/specification).
*
* @param options - An options object.
* @param options.permitEmptyString - Whether the empty string (i.e. `''`)
* should be treated as a valid ID. Default: `true`
* @param options.permitFractions - Whether fractional numbers (e.g. `1.2`)
* should be treated as valid IDs. Default: `false`
* @param options.permitNull - Whether `null` should be treated as a valid ID.
* Default: `true`
* @returns The JSON-RPC ID validator function.
*/
function getJsonRpcIdValidator(options) {
const { permitEmptyString, permitFractions, permitNull } = {
permitEmptyString: true,
permitFractions: false,
permitNull: true,
...options,
};
/**
* Type guard for {@link JsonRpcId}.
*
* @param id - The JSON-RPC ID value to check.
* @returns Whether the given ID is valid per the options given to the
* factory.
*/
const isValidJsonRpcId = (id) => {
return Boolean((typeof id === 'number' && (permitFractions || Number.isInteger(id))) ||
(typeof id === 'string' && (permitEmptyString || id.length > 0)) ||
(permitNull && id === null));
};
return isValidJsonRpcId;
}
exports.getJsonRpcIdValidator = getJsonRpcIdValidator;
//# sourceMappingURL=json.cjs.map
;