@metamask/snaps-rpc-methods
Version:
MetaMask Snaps JSON-RPC method implementations
158 lines • 6.08 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.set = exports.setStateHandler = void 0;
const rpc_errors_1 = require("@metamask/rpc-errors");
const superstruct_1 = require("@metamask/superstruct");
const utils_1 = require("@metamask/utils");
const manageState_1 = require("../restricted/manageState.cjs");
const utils_2 = require("../utils.cjs");
const hookNames = {
hasPermission: true,
getSnapState: true,
getUnlockPromise: true,
updateSnapState: true,
};
/**
* `snap_setState` sets the state of the Snap.
*/
exports.setStateHandler = {
methodNames: ['snap_setState'],
implementation: setStateImplementation,
hookNames,
};
const SetStateParametersStruct = (0, superstruct_1.object)({
key: (0, superstruct_1.optional)(utils_2.StateKeyStruct),
value: utils_1.JsonStruct,
encrypted: (0, superstruct_1.optional)((0, superstruct_1.boolean)()),
});
/**
* The `snap_setState` method implementation.
*
* @param request - The JSON-RPC request object.
* @param response - The JSON-RPC response object.
* @param _next - The `json-rpc-engine` "next" callback. Not used by this
* function.
* @param end - The `json-rpc-engine` "end" callback.
* @param hooks - The RPC method hooks.
* @param hooks.hasPermission - Check whether a given origin has a given
* permission.
* @param hooks.getSnapState - Get the state of the requesting Snap.
* @param hooks.getUnlockPromise - Wait for the extension to be unlocked.
* @param hooks.updateSnapState - Update the state of the requesting Snap.
* @returns Nothing.
*/
async function setStateImplementation(request, response, _next, end, { hasPermission, getSnapState, getUnlockPromise, updateSnapState, }) {
const { params } = request;
if (!hasPermission(manageState_1.manageStateBuilder.targetName)) {
return end(rpc_errors_1.providerErrors.unauthorized());
}
try {
const validatedParams = getValidatedParams(params);
const { key, value, encrypted = true } = validatedParams;
if (key === undefined && !(0, utils_1.isObject)(value)) {
return end(rpc_errors_1.rpcErrors.invalidParams('Invalid params: Value must be an object if key is not provided.'));
}
if (encrypted) {
await getUnlockPromise(true);
}
const newState = await getNewState(key, value, encrypted, getSnapState);
const size = (0, utils_1.getJsonSize)(newState);
if (size > manageState_1.STORAGE_SIZE_LIMIT) {
throw rpc_errors_1.rpcErrors.invalidParams({
message: `Invalid params: The new state must not exceed ${manageState_1.STORAGE_SIZE_LIMIT / 1000000} MB in size.`,
});
}
await updateSnapState(newState, encrypted);
response.result = null;
}
catch (error) {
return end(error);
}
return end();
}
/**
* Validate the parameters of the `snap_setState` method.
*
* @param params - The parameters to validate.
* @returns The validated parameters.
*/
function getValidatedParams(params) {
try {
return (0, superstruct_1.create)(params, SetStateParametersStruct);
}
catch (error) {
if (error instanceof superstruct_1.StructError) {
throw rpc_errors_1.rpcErrors.invalidParams({
message: `Invalid params: ${error.message}.`,
});
}
/* istanbul ignore next */
throw rpc_errors_1.rpcErrors.internal();
}
}
/**
* Get the new state of the Snap.
*
* If the key is `undefined`, the value is expected to be an object. In this
* case, the value is returned as the new state.
*
* If the key is not `undefined`, the value is set in the state at the key. If
* the key does not exist, it is created (and any missing intermediate keys are
* created as well).
*
* @param key - The key to set.
* @param value - The value to set the key to.
* @param encrypted - Whether the state is encrypted.
* @param getSnapState - The `getSnapState` hook.
* @returns The new state of the Snap.
*/
async function getNewState(key, value, encrypted, getSnapState) {
if (key === undefined) {
(0, utils_1.assert)((0, utils_1.isObject)(value));
return value;
}
const state = await getSnapState(encrypted);
return set(state, key, value);
}
/**
* Set the value of a key in an object. The key may contain Lodash-style path
* syntax, e.g., `a.b.c` (with the exception of array syntax). If the key does
* not exist, it is created (and any missing intermediate keys are created as
* well).
*
* This is a simplified version of Lodash's `set` function, but Lodash doesn't
* seem to be maintained anymore, so we're using our own implementation.
*
* @param object - The object to get the key from.
* @param key - The key to set.
* @param value - The value to set the key to.
* @returns The new object with the key set to the value.
*/
function set(object, key, value) {
const keys = key.split('.');
const requiredObject = object ?? {};
let currentObject = requiredObject;
for (let i = 0; i < keys.length; i++) {
const currentKey = keys[i];
if (utils_2.FORBIDDEN_KEYS.includes(currentKey)) {
throw rpc_errors_1.rpcErrors.invalidParams('Invalid params: Key contains forbidden characters.');
}
if (i === keys.length - 1) {
currentObject[currentKey] = value;
return requiredObject;
}
if (!(0, utils_1.hasProperty)(currentObject, currentKey) ||
currentObject[currentKey] === null) {
currentObject[currentKey] = {};
}
else if (!(0, utils_1.isObject)(currentObject[currentKey])) {
throw rpc_errors_1.rpcErrors.invalidParams('Invalid params: Cannot overwrite non-object value.');
}
currentObject = currentObject[currentKey];
}
// This should never be reached.
/* istanbul ignore next */
throw new Error('Unexpected error while setting the state.');
}
exports.set = set;
//# sourceMappingURL=setState.cjs.map