@metamask/utils
Advanced tools
Comparing version
@@ -7,4 +7,6 @@ export * from './assert'; | ||
export * from './collections'; | ||
export * from './encryption-types'; | ||
export * from './hex'; | ||
export * from './json'; | ||
export * from './keyring'; | ||
export * from './logging'; | ||
@@ -15,2 +17,3 @@ export * from './misc'; | ||
export * from './time'; | ||
export * from './transaction-types'; | ||
export * from './versions'; |
@@ -23,4 +23,6 @@ "use strict"; | ||
__exportStar(require("./collections"), exports); | ||
__exportStar(require("./encryption-types"), exports); | ||
__exportStar(require("./hex"), exports); | ||
__exportStar(require("./json"), exports); | ||
__exportStar(require("./keyring"), exports); | ||
__exportStar(require("./logging"), exports); | ||
@@ -31,3 +33,4 @@ __exportStar(require("./misc"), exports); | ||
__exportStar(require("./time"), exports); | ||
__exportStar(require("./transaction-types"), exports); | ||
__exportStar(require("./versions"), exports); | ||
//# sourceMappingURL=index.js.map |
import { Infer, Struct } from 'superstruct'; | ||
import { AssertionErrorConstructor } from './assert'; | ||
export declare const JsonStruct: Struct<Json, null>; | ||
/** | ||
@@ -11,2 +10,15 @@ * Any JSON-compatible value. | ||
/** | ||
* 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}. | ||
*/ | ||
export declare const UnsafeJsonStruct: Struct<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. | ||
*/ | ||
export declare const JsonStruct: Struct<Json, null>; | ||
/** | ||
* Check if the given value is a valid {@link Json} value, i.e., a value that is | ||
@@ -20,2 +32,9 @@ * serializable to JSON. | ||
/** | ||
* 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. | ||
*/ | ||
export declare function getJsonSize(value: unknown): number; | ||
/** | ||
* The string '2.0'. | ||
@@ -61,3 +80,3 @@ */ | ||
export declare type JsonRpcError = OptionalField<Infer<typeof JsonRpcErrorStruct>, 'data'>; | ||
export declare const JsonRpcParamsStruct: Struct<Record<string, Json> | Json[] | undefined, null>; | ||
export declare const JsonRpcParamsStruct: Struct<Json[] | Record<string, Json> | undefined, null>; | ||
export declare type JsonRpcParams = Infer<typeof JsonRpcParamsStruct>; | ||
@@ -68,3 +87,3 @@ export declare const JsonRpcRequestStruct: Struct<{ | ||
jsonrpc: "2.0"; | ||
params?: Record<string, Json> | Json[] | undefined; | ||
params?: Json[] | Record<string, Json> | undefined; | ||
}, { | ||
@@ -74,3 +93,3 @@ id: Struct<string | number | null, null>; | ||
method: Struct<string, null>; | ||
params: Struct<Record<string, Json> | Json[] | undefined, null>; | ||
params: Struct<Json[] | Record<string, Json> | undefined, null>; | ||
}>; | ||
@@ -89,3 +108,3 @@ export declare type InferWithParams<Type extends Struct<any>, Params extends JsonRpcParams> = Omit<Infer<Type>, 'params'> & (keyof Params extends undefined ? { | ||
jsonrpc: "2.0"; | ||
params?: Record<string, Json> | Json[] | undefined; | ||
params?: Json[] | Record<string, Json> | undefined; | ||
}, Omit<{ | ||
@@ -95,3 +114,3 @@ id: Struct<string | number | null, null>; | ||
method: Struct<string, null>; | ||
params: Struct<Record<string, Json> | Json[] | undefined, null>; | ||
params: Struct<Json[] | Record<string, Json> | undefined, null>; | ||
}, "id">>; | ||
@@ -322,12 +341,2 @@ /** | ||
export declare function getJsonRpcIdValidator(options?: JsonRpcValidatorOptions): (id: unknown) => id is string | number | null; | ||
/** | ||
* Checks whether a value is JSON serializable and counts the total number | ||
* of bytes needed to store the serialized version of the value. | ||
* | ||
* @param jsObject - Potential JSON serializable object. | ||
* @param skipSizingProcess - Skip JSON size calculation (default: false). | ||
* @returns Tuple [isValid, plainTextSizeInBytes] containing a boolean that signals whether | ||
* the value was serializable and a number of bytes that it will use when serialized to JSON. | ||
*/ | ||
export declare function validateJsonAndGetSize(jsObject: unknown, skipSizingProcess?: boolean): [isValid: boolean, plainTextSizeInBytes: number]; | ||
export {}; |
214
dist/json.js
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.validateJsonAndGetSize = 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.isValidJson = exports.JsonStruct = void 0; | ||
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.isValidJson = exports.JsonStruct = exports.UnsafeJsonStruct = void 0; | ||
const superstruct_1 = require("superstruct"); | ||
const assert_1 = require("./assert"); | ||
const misc_1 = require("./misc"); | ||
exports.JsonStruct = (0, superstruct_1.define)('Json', (value) => { | ||
const [isValid] = validateJsonAndGetSize(value, true); | ||
if (!isValid) { | ||
return 'Expected a valid JSON-serializable value'; | ||
/** | ||
* A struct to check if the given value is finite number. Superstruct's | ||
* `number()` struct does not check if the value is finite. | ||
* | ||
* @returns A struct to check if the given value is finite number. | ||
*/ | ||
const finiteNumber = () => (0, superstruct_1.define)('finite number', (value) => { | ||
return (0, superstruct_1.is)(value, (0, superstruct_1.number)()) && Number.isFinite(value); | ||
}); | ||
/** | ||
* 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}. | ||
*/ | ||
// We cannot infer the type of the struct, because it is recursive. | ||
exports.UnsafeJsonStruct = (0, superstruct_1.union)([ | ||
(0, superstruct_1.literal)(null), | ||
(0, superstruct_1.boolean)(), | ||
finiteNumber(), | ||
(0, superstruct_1.string)(), | ||
(0, superstruct_1.array)((0, superstruct_1.lazy)(() => exports.UnsafeJsonStruct)), | ||
(0, superstruct_1.record)((0, superstruct_1.string)(), (0, superstruct_1.lazy)(() => exports.UnsafeJsonStruct)), | ||
]); | ||
/** | ||
* 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.define)('Json', (value, context) => { | ||
/** | ||
* Helper function that runs the given struct validator and returns the | ||
* validation errors, if any. If the value is valid, it returns `true`. | ||
* | ||
* @param innerValue - The value to validate. | ||
* @param struct - The struct to use for validation. | ||
* @returns The validation errors, or `true` if the value is valid. | ||
*/ | ||
function checkStruct(innerValue, struct) { | ||
const iterator = struct.validator(innerValue, context); | ||
const errors = [...iterator]; | ||
if (errors.length > 0) { | ||
return errors; | ||
} | ||
return true; | ||
} | ||
return true; | ||
try { | ||
// The plain value must be a valid JSON value, but it may be altered in the | ||
// process of JSON serialization, so we need to validate it again after | ||
// serialization. This has the added benefit that the returned error messages | ||
// will be more helpful, as they will point to the exact location of the | ||
// invalid value. | ||
// | ||
// This seems overcomplicated, but without checking the plain value first, | ||
// there are some cases where the validation passes, even though the value is | ||
// not valid JSON. For example, `undefined` is not valid JSON, but serializing | ||
// it will remove it from the object, so the validation will pass. | ||
const unsafeResult = checkStruct(value, exports.UnsafeJsonStruct); | ||
if (unsafeResult !== true) { | ||
return unsafeResult; | ||
} | ||
// JavaScript engines are highly optimized for this specific use case of | ||
// JSON parsing and stringifying, so there should be no performance impact. | ||
return checkStruct(JSON.parse(JSON.stringify(value)), exports.UnsafeJsonStruct); | ||
} | ||
catch (error) { | ||
if (error instanceof RangeError) { | ||
return 'Circular reference detected'; | ||
} | ||
return false; | ||
} | ||
}); | ||
@@ -26,2 +90,14 @@ /** | ||
/** | ||
* 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'. | ||
@@ -277,126 +353,2 @@ */ | ||
exports.getJsonRpcIdValidator = getJsonRpcIdValidator; | ||
/** | ||
* Checks whether a value is JSON serializable and counts the total number | ||
* of bytes needed to store the serialized version of the value. | ||
* | ||
* @param jsObject - Potential JSON serializable object. | ||
* @param skipSizingProcess - Skip JSON size calculation (default: false). | ||
* @returns Tuple [isValid, plainTextSizeInBytes] containing a boolean that signals whether | ||
* the value was serializable and a number of bytes that it will use when serialized to JSON. | ||
*/ | ||
function validateJsonAndGetSize(jsObject, skipSizingProcess = false) { | ||
const seenObjects = new Set(); | ||
/** | ||
* Checks whether a value is JSON serializable and counts the total number | ||
* of bytes needed to store the serialized version of the value. | ||
* | ||
* This function assumes the encoding of the JSON is done in UTF-8. | ||
* | ||
* @param value - Potential JSON serializable value. | ||
* @param skipSizing - Skip JSON size calculation (default: false). | ||
* @returns Tuple [isValid, plainTextSizeInBytes] containing a boolean that signals whether | ||
* the value was serializable and a number of bytes that it will use when serialized to JSON. | ||
*/ | ||
function getJsonSerializableInfo(value, skipSizing) { | ||
if (value === undefined) { | ||
return [false, 0]; | ||
} | ||
else if (value === null) { | ||
// Return already specified constant size for null (special object) | ||
return [true, skipSizing ? 0 : misc_1.JsonSize.Null]; | ||
} | ||
// Check and calculate sizes for basic (and some special) types | ||
const typeOfValue = typeof value; | ||
try { | ||
if (typeOfValue === 'function') { | ||
return [false, 0]; | ||
} | ||
else if (typeOfValue === 'string' || value instanceof String) { | ||
return [ | ||
true, | ||
skipSizing | ||
? 0 | ||
: (0, misc_1.calculateStringSize)(value) + misc_1.JsonSize.Quote * 2, | ||
]; | ||
} | ||
else if (typeOfValue === 'boolean' || value instanceof Boolean) { | ||
if (skipSizing) { | ||
return [true, 0]; | ||
} | ||
// eslint-disable-next-line eqeqeq | ||
return [true, value == true ? misc_1.JsonSize.True : misc_1.JsonSize.False]; | ||
} | ||
else if (typeOfValue === 'number' || value instanceof Number) { | ||
if (skipSizing) { | ||
return [true, 0]; | ||
} | ||
return [true, (0, misc_1.calculateNumberSize)(value)]; | ||
} | ||
else if (value instanceof Date) { | ||
if (skipSizing) { | ||
return [true, 0]; | ||
} | ||
return [ | ||
true, | ||
// Note: Invalid dates will serialize to null | ||
isNaN(value.getDate()) | ||
? misc_1.JsonSize.Null | ||
: misc_1.JsonSize.Date + misc_1.JsonSize.Quote * 2, | ||
]; | ||
} | ||
} | ||
catch (_) { | ||
return [false, 0]; | ||
} | ||
// If object is not plain and cannot be serialized properly, | ||
// stop here and return false for serialization | ||
if (!(0, misc_1.isPlainObject)(value) && !Array.isArray(value)) { | ||
return [false, 0]; | ||
} | ||
// Circular object detection (handling) | ||
// Check if the same object already exists | ||
if (seenObjects.has(value)) { | ||
return [false, 0]; | ||
} | ||
// Add new object to the seen objects set | ||
// Only the plain objects should be added (Primitive types are skipped) | ||
seenObjects.add(value); | ||
// Continue object decomposition | ||
try { | ||
return [ | ||
true, | ||
Object.entries(value).reduce((sum, [key, nestedValue], idx, arr) => { | ||
// Recursively process next nested object or primitive type | ||
// eslint-disable-next-line prefer-const | ||
let [valid, size] = getJsonSerializableInfo(nestedValue, skipSizing); | ||
if (!valid) { | ||
throw new Error('JSON validation did not pass. Validation process stopped.'); | ||
} | ||
// Circular object detection | ||
// Once a child node is visited and processed remove it from the set. | ||
// This will prevent false positives with the same adjacent objects. | ||
seenObjects.delete(value); | ||
if (skipSizing) { | ||
return 0; | ||
} | ||
// Objects will have be serialized with "key": value, | ||
// therefore we include the key in the calculation here | ||
const keySize = Array.isArray(value) | ||
? 0 | ||
: key.length + misc_1.JsonSize.Comma + misc_1.JsonSize.Colon * 2; | ||
const separator = idx < arr.length - 1 ? misc_1.JsonSize.Comma : 0; | ||
return sum + keySize + size + separator; | ||
}, | ||
// Starts at 2 because the serialized JSON string data (plain text) | ||
// will minimally contain {}/[] | ||
skipSizing ? 0 : misc_1.JsonSize.Wrapper * 2), | ||
]; | ||
} | ||
catch (_) { | ||
return [false, 0]; | ||
} | ||
} | ||
return getJsonSerializableInfo(jsObject, skipSizingProcess); | ||
} | ||
exports.validateJsonAndGetSize = validateJsonAndGetSize; | ||
//# sourceMappingURL=json.js.map |
{ | ||
"name": "@metamask/utils", | ||
"version": "3.6.0", | ||
"version": "4.0.0", | ||
"description": "Various JavaScript/TypeScript utilities of wide relevance to the MetaMask codebase.", | ||
@@ -5,0 +5,0 @@ "repository": { |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
240827
-2.18%3217
-1.02%