-
Notifications
You must be signed in to change notification settings - Fork 5.1k
/
Copy pathcreateMethodMiddleware.js
111 lines (99 loc) · 3.68 KB
/
createMethodMiddleware.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
import { permissionRpcMethods } from '@metamask/permission-controller';
import { rpcErrors } from '@metamask/rpc-errors';
import { selectHooks } from '@metamask/snaps-rpc-methods';
import { hasProperty } from '@metamask/utils';
import { handlers as localHandlers, legacyHandlers } from './handlers';
const allHandlers = [...localHandlers, ...permissionRpcMethods.handlers];
// The primary home of RPC method implementations in MetaMask. MUST be subsequent
// to our permissioning logic in the JSON-RPC middleware pipeline.
export const createMethodMiddleware = makeMethodMiddlewareMaker(allHandlers);
// A collection of RPC method implementations that, for legacy reasons, MAY precede
// our permissioning logic in the JSON-RPC middleware pipeline.
export const createLegacyMethodMiddleware =
makeMethodMiddlewareMaker(legacyHandlers);
/**
* Creates a method middleware factory function given a set of method handlers.
*
* @param {Record<string, import('@metamask/permission-controller').PermittedHandlerExport>} handlers - The RPC method
* handler implementations.
* @returns The method middleware factory function.
*/
function makeMethodMiddlewareMaker(handlers) {
const handlerMap = handlers.reduce((map, handler) => {
for (const methodName of handler.methodNames) {
map[methodName] = handler;
}
return map;
}, {});
const expectedHookNames = new Set(
handlers.flatMap(({ hookNames }) => Object.getOwnPropertyNames(hookNames)),
);
/**
* Creates a json-rpc-engine middleware of RPC method implementations.
*
* Handlers consume functions that hook into the background, and only depend
* on their signatures, not e.g. controller internals.
*
* @param {Record<string, (...args: unknown[]) => unknown | Promise<unknown>>} hooks - Required "hooks" into our
* controllers.
* @returns {import('@metamask/json-rpc-engine').JsonRpcMiddleware<unknown, unknown>} The method middleware function.
*/
const makeMethodMiddleware = (hooks) => {
assertExpectedHook(hooks, expectedHookNames);
const methodMiddleware = async (req, res, next, end) => {
const handler = handlerMap[req.method];
if (handler) {
const { implementation, hookNames } = handler;
try {
// Implementations may or may not be async, so we must await them.
return await implementation(
req,
res,
next,
end,
selectHooks(hooks, hookNames),
);
} catch (error) {
if (process.env.METAMASK_DEBUG) {
console.error(error);
}
return end(
error instanceof Error
? error
: rpcErrors.internal({ data: error }),
);
}
}
return next();
};
return methodMiddleware;
};
return makeMethodMiddleware;
}
/**
* Asserts that the specified hooks object only has all expected hooks and no extraneous ones.
*
* @param {Record<string, unknown>} hooks - Required "hooks" into our controllers.
* @param {string[]} expectedHookNames - The expected hook names.
*/
function assertExpectedHook(hooks, expectedHookNames) {
const missingHookNames = [];
expectedHookNames.forEach((hookName) => {
if (!hasProperty(hooks, hookName)) {
missingHookNames.push(hookName);
}
});
if (missingHookNames.length > 0) {
throw new Error(
`Missing expected hooks:\n\n${missingHookNames.join('\n')}\n`,
);
}
const extraneousHookNames = Object.getOwnPropertyNames(hooks).filter(
(hookName) => !expectedHookNames.has(hookName),
);
if (extraneousHookNames.length > 0) {
throw new Error(
`Received unexpected hooks:\n\n${extraneousHookNames.join('\n')}\n`,
);
}
}